Capture the (Drupal 8) Flag

Who is this person?

Tess Flynn


@socketwench
"That's wench, not wrench"

Module co-maintainer for

Flag
Flag Friend
Examples

Drupal Developer with

(We're hiring!)

Why Port Flag?


We need modules ported to Drupal 8 now.
-- Angie "webchick" Byron

Why not contribute
to Drupal 8 Directly?

The Reverse Sisyphus Problem


The Valley of Dearth


There's got to be a better way...

What is Flag?

Flag Module Defined

Enables administrators to define boolean fields that can be attached to site content which each user may set or unset.

Flags


Administrator-defined boolean field


Stored as an Entity since 7.x-2.x

May be attached to one entity type, but...

...multiple bundles of the same type

Created under Admin > Structure > Flags

May be fielded (7.x-3.x)


Flaggings

Entity created to store when a user sets a Flag.


Which flag was set?

On what entity was the flag set?

When was the flag set?

Who set the flag?


Flaggables

Any entity that has a flag defined and for which flaggings may be created

First, do a code review

How does your module really work?

Handler Classes


Mostly, a Typical Drupal 7 Module

Large surface of straight functions
Entity support was refactored in
OOP was used, but...

How PHP4 Classes look today

Weird factory methods
 $flag = flag_flag::factory_by_array($bigOlArray);

    $flag = flag_flag::factory_by_entity_type($entity_type);

    $flag = flag_flag::factory_by_row($databaseRow);
All methods were public
All class variables were public
Basically, a big blob of functions

Where to start?

Question all the things!


Set some expectations

Test Driven Development?

Simpletest is in Drupal 8!
But...
Porting tests may limit your thinking

Start with the most central piece

and

work your way out

Porting the
Flag Entity

Designing the Flag Class


FlagInterface

interface FlagInterface extends ConfigEntityInterface {

    public function enable();

    public function disable();

    public function isFlagged(EntityInterface $entity, AccountInterface $account = NULL);

    public function getPermissions();

    public function isGlobal();

    public function setGlobal($isGlobal);

    ...
Best Practice OOP
Sketch out what the class does
Separates documentation and code

Configuration Entities

Designed for administrator-defined structures
Data my be exported to YAML
It's structure, not data

ConfigEntityBase

class Flag extends ConfigEntityBundleBase implements FlagInterface
Foundation class for config entities
Does all the Drupal work, so you can do your work

Config Entity Schema

Defines the config entity fields
The data type of each field
Tells Drupal what to save

flag.schema.yml

<moduleRoot>/config/schema/
flag.flag.*:
    type: config_entity
    label: 'Flag'
    mapping:
        id:
            type: string
            label: 'Flag identifier'
        is_global:
            type: boolean
            label: 'Is flag global'
        flag_short:
            type: string
            label: 'Flag Link Text'
        unflag_short:
            type: string
            label: 'Unflag Link Text'
(above is just a sample)

Defining Class Variables

Best practice is to match your schema
Code completion in your IDE
Easiest place to document schema

Please use an IDE & Debugger

Yes, really. It's time.
Drupal 7 has ~3000 files
Drupal 8 has 5 times that many

Getters and Setters

Create for all schema vars
Interface is incomplete otherwise!

Never Mark Schema Vars as Private

Drupal won't be able to access them!
ConfigEntityBase::get($property_name);
ConfigEntityBase::set($property_name, $value);

Plugging Flag Entity Into Drupal

Annotations

The assembly manual for the feature provided by the class
Metadata included in the class docblock

Tells Drupal:

  • What the following class is
  • Why its important
  • Where to find other important pieces

Flag's Annotation

/** @ConfigEntityType(
 *    id = "flag",
 *    label = @Translation("Flag"),
 *    admin_permission = "administer flags",
 *    handlers = {
 *      "list_builder" = "Drupal\flag\Controller\FlagListController",
 *      "form" = {
 *        "add" = "Drupal\flag\Form\FlagAddForm",
 *        "edit" = "Drupal\flag\Form\FlagEditForm",
 *        "delete" = "Drupal\flag\Form\FlagDeleteForm"
 *      }
 *    },
 *    links = {
 *      "edit-form" = "/admin/structure/flags/manage/{flag}",
 *      "delete-form" = "/admin/structure/flags/manage/{flag}/delete",
 *    }
 * ) **/
(Again, only a sample)

@ConfigEntityType

Tells Drupal what follows is a Config Entity
Used for feature discovery after a cache clear

Handlers = {...}

Where to find related pieces of functionality
  • List all entities page
  • Forms
/**
 *    handlers = {
 *      "list_builder" = "Drupal\flag\Controller\FlagListController",
 *      "form" = {
 *        "add" = "Drupal\flag\Form\FlagAddForm",
 *        "edit" = "Drupal\flag\Form\FlagEditForm",
 *        "delete" = "Drupal\flag\Form\FlagDeleteForm"
 *      }
 *    },
 */

Building the Admin Interface

FlagListController

Derives from ConfigEntityListBuilder
Provides the Admin > Structure > Flags page

Only two important methods


    // Builds the header of the table.
    FlagListController::buildHeader();

    // Populates each row of the table.
    FlagListController::buildRow($entity);

Add and Edit Flag Pages

Form Classes

FlagFormBase

Does all the real form stuff
  • Builds the form
  • Handles validation
  • Handles submit

FlagAddForm & FlagEditForm

Modifies:
  • Where the default values come from
  • "Create Flag" vs. "Edit Flag" submit button

Flag Delete Form

Derives from EntityConfirmFormBase directly
Provides a quick yes/no form

Making it Routable

Drupal 8 Doesn't Think in Paths

It thinks in route names!
So, how do we add links to existing menus?

Connecting the Add Form

That's...
a lot of code to write

Lots of Scaffolding

*.yml files, interfaces, base classes, base forms

Necessary, but tedious to write

Reducing Tedium

Generating a Module

Config Entities

Get Drupal Console

drupalconsole.com

https://www.drupal.org/project/console

Porting the Flagging Entity

Designing the Flagging Entity

ContentEntityBase

Best for user-created entities
Fields defined by overloading baseFieldDefinitions()
No need to use hook_schema()

Flaggings are Weird

Not created through forms

FlagService

Plain Old PHP Object (POPO)
Registered container-wide with flag.services.yml
$drupal generate:service

Flagging and Unflagging

\Drupal::service('flag')->flag();
\Drupal::service('flag')->unflag();

Making it Expandable

Let's not Recreate Handler Classes

Hard to expand
Not discoverable

Flag Type Plugin

Relates the Flag to the Flaggable
Discoverable using @FlagType annotation
Added dynamically, not a subclass!

Creating an API

Hooks Aren't Everything in D8

Flag 8.x Events (So Far)

final class FlagEvents {

    const ENTITY_FLAGGED = 'flag.entity_flagged';

    const ENTITY_UNFLAGGED = 'flag.entity_unflagged';

    const FLAG_DELETED = 'flag.flag_deleted';

    const FLAG_RESET = 'flag.flag_reset';
}

Hooks

ModuleHandlerInterface::invokeAll()
Inject ModuleHandler into your service
public function __construct(ModuleHandlerInterface $module_handler) {
    $this->moduleHandler = $module_handler;
}

Hooks and FlagService

Hooks an assumed necessity, but...
...we've haven't needed them (yet).

Running Your Project

Do it in Public

But with a warning label

Keeping up with Core

Start with the latest release
Stick with it until you're you're ready to update
Avoid developing against HEAD

Ask for Help

But be very patient,
And show appreciation!

Places to find Help

#drupal-contribute
drupal.org/list-changes
drupal.org/project/issues/drupal

Ask for Funding

Travel money to get your team together
Attend a Conference
Take time off of work to code

When to move back to D.O?

You might be tempted to do this

Don't!
Move Once it's Viable!

Things Are Easier Now

Summary

Port now to Learn.

Or wait to work.

(But don't wait too long!)

Do a code review.

Question everything!

Nothing is sacred.

Use an IDE and a Debugger.

Patiently ask for help,

and show appreciation.

Do it in public,

but with a warning label.

Is money the problem?

Consider crowdfunding.

Move out of the sandbox fast.

Things are easier now.

Special Thanks To

@timplunkett, @larowlan, @YesCT, @davereid, @wizonesolutions, Lauren Shey, and
 
(We're hiring!)

Come Sprint with Us!

Learn and contribute to Drupal Core

Mentors will help you setup and find issues

Friday 9am - 6pm, Room 403AB

Thank you!

@socketwench
http://socketwench.github.io/portingFlagToDrupal8