Module & Architecture Development

Drupal Composer, Custom Entities, & Plugins

Jimmy Candan

/jənˈdən/

  • Coding 9 yrs
  • Drupal 6 yrs
  • Love wife and 2 kids
  • From L.A., Live in Chattanooga

Agenda

  • Drupal Composer
  • Custom Entities
  • Custom Field Plugin
  • Packagist repository
  • Require
  • Require Dev
  • Install or Update
  • Lock
  • Vendor
# composer.json
{
  "require": {
    "guzzlehttp/guzzle": "^6.0",
  },
}
# composer.json
{
  "require": {
    "guzzlehttp/guzzle": "^6.0"
  },
  "require-dev": {
    "behat/mink": "~1.7"
  }
}
$ composer install

        OR

$ composer install --no-dev
proj_name/
├── composer.json
├── composer.lock
└── vendor/
    ├── bin/
    │   ├── behat -> ../behat/behat/bin/behat
    ├── behat/
    └── guzzlehttp/
        └── guzzle/
$ composer update

        OR

$ composer update behat/behat
$ composer require guzzlehttp/guzzle
$ composer require --dev behat/mink

Composer

A modern PHP dependency manager

Drupal Composer

Manage Drupal root and contrib modules with a modern dependency manager

$ composer create-project
{
  "require": {
    "drupal/admin_toolbar": "^1.22"
  }
}

composer.json

$ composer create-project \
  drupal-composer/drupal-project:8.x-dev
$ composer create-project \
  drupal-composer/drupal-project:8.x-dev \
  proj_name --stability dev --no-interaction
$ composer create-project \
  drupal-composer/drupal-project:8.x-dev \
  proj_name --stability dev --no-interaction

$ cd proj_name
$ composer create-project \
  drupal-composer/drupal-project:8.x-dev \
  proj_name --stability dev --no-interaction

$ cd proj_name

$ composer require drupal/admin_toolbar

Diff Drupal Composer

Things that come preconfigured for Drupal.

  • Drupal repository
  • Drush and Console
  • Behat
  • Drupal root
  • Installer paths
{
  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
{
  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {
    "drupal/core": "~8.4",
    "drupal/console": "^1.0.2",
    "drush/drush": "^9.0.0"
    . . .
  },
{
  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {
    "drupal/core": "~8.4",
    "drupal/console": "^1.0.2",
    "drush/drush": "^9.0.0"
    . . .
  },
  "require-dev": {
    "behat/mink": "~1.7",
    . . .
  },
$ composer require drupal/admin_toolbar
proj_name/
├── composer.json
├── composer.lock
├── vendor/
└── web/
    ├── autoload.php
    ├── core/
    ├── modules/
    ├── profiles/
    ├── sites/
    ├── themes/
    ├── index.php
    ├── robots.txt
    ├── update.php
    └── web.config
{
  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {
    "drupal/core": "~8.4",
    "drupal/console": "^1.0.2",
    "drush/drush": "^9.0.0"
    . . .
  },
  "require-dev": {
    "behat/mink": "~1.7",
    . . .
  },
  "extra": {
    "installer-paths": {
      "web/modules/contrib/{$name}": ["type:drupal-module"],
      "web/themes/contrib/{$name}": ["type:drupal-theme"]
    }
  }
}

NOTE:

 

The CLI tools are in the vendor directory.

$ cd web
$ ../vendor/bin/drush <command>

For drush, avoid the inconvenience of typing ../vendor/bin/drush by installing drush-launcher.

../vendor/bin/drush

Composer Merge

Custom modules can have their own dependencies

 

  • composer.json
    tells composer this must be available
     
  • mymodule.info.yml
    tells drupal this must be installed
$ cd my_project
$ composer require wikimedia/composer-merge-plugin
# project_root/composer.json
{
  "extra": {
    "merge-plugin": {
      "include": [
        "web/modules/custom/*/composer.json"
      ]
    }
  }
}
name: My Custom Module
type: module
description: Does something awesome.
core: 8.x
package: Custom
dependencies:
  - state_machine
# my_project/web/modules/custom/my_custom_module/composer.json
{
  "repositories": [
    {   
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }   
  ],  
  "require": {
    "drupal/state_machine": "^1.0@beta"
  },
}
$ cd my_project
# my_custom_module/composer.json

{ 
  "require": {
    "drupal/state_machine": "^1.0@beta"
  },
}

My Fitness App

Requirements

Users can:

  • Select a workout
  • Play through workout's exercise's countdown timer
  • Start, stop, skip exercises in a workout

Admins can:

  • Create exercises with description and video
  • Create workouts with exercises
  • Specify the time for each exercise in the workout

Exercise

Exercise

Exercise

Workout

Workout

count: 3

count: 2

Some Questions

  • Do we Node or custom Content Entity?
  • Where do we field the relationship?
  • Where do we field the time?

1. Do we Node or Content Entity?

  • What is the difference?
  • When do we choose Entity?

2 Types of Entities

  • Configuration Entity
  • Content Entity

Read the Docs (drupal.org/docs/8/api/entity-api)

Configuration Entity

Main difference

  • Keeps configuration in the database
  • No fields

Content Entity

Examples

  • Node
  • Comment
  • User
  • Taxonomy Term

When should you go custom?

  • Nodes are for document-like things
  • Consider custom when:
    • More "behaviory" (e.g. Bookable Rooms)
    • Units of Inventory (e.g. Products)
    • Node's title, body, author, and multitude of settings options are overkill
    • Client simply does not consider it "CONTENT"

New Content Type or Full-fledged Content Entity?

LET'S SEE IT IN ACTION

 $ ../vendor/bin/drupal generate:module

Drupal Console

Generate Module

 $ ../vendor/bin/drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Fitness
 Enter the module machine name [fitness]:
 > 
 $ ../vendor/bin/drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Fitness
 Enter the module machine name [fitness]:
 > 
 Enter the module Path [modules/custom]:
 > 
 Enter module description [My Awesome Module]:
 > Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.
 $ ../vendor/bin/drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Fitness
 Enter the module machine name [fitness]:
 > 
 Enter the module Path [modules/custom]:
 > 
 Enter module description [My Awesome Module]:
 > Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.
 Enter package name [Custom]:
 > 
 Enter Drupal Core version [8.x]:
 > 
 Do you want to generate a .module file (yes/no) [yes]:
 > 
 Define module as feature (yes/no) [no]:
 > 
 $ ../vendor/bin/drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Fitness
 Enter the module machine name [fitness]:
 > 
 Enter the module Path [modules/custom]:
 > 
 Enter module description [My Awesome Module]:
 > Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.
 Enter package name [Custom]:
 > 
 Enter Drupal Core version [8.x]:
 > 
 Do you want to generate a .module file (yes/no) [yes]:
 > 
 Define module as feature (yes/no) [no]:
 > 
 Do you want to add a composer.json file to your module (yes/no) [yes]:
 > 
 Would you like to add module dependencies (yes/no) [no]:
 > 
 $ ../vendor/bin/drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Fitness
 Enter the module machine name [fitness]:
 > 
 Enter the module Path [modules/custom]:
 > 
 Enter module description [My Awesome Module]:
 > Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.
 Enter package name [Custom]:
 > 
 Enter Drupal Core version [8.x]:
 > 
 Do you want to generate a .module file (yes/no) [yes]:
 > 
 Define module as feature (yes/no) [no]:
 > 
 Do you want to add a composer.json file to your module (yes/no) [yes]:
 > 
 Would you like to add module dependencies (yes/no) [no]:
 > 
 Do you want to generate a unit test class (yes/no) [yes]:
 > no
 Do you want to generate a themeable template (yes/no) [yes]:
 > no
 $ ../vendor/bin/drupal generate:module

 // Welcome to the Drupal module generator

 Enter the new module name:
 > Fitness
 Enter the module machine name [fitness]:
 > 
 Enter the module Path [modules/custom]:
 > 
 Enter module description [My Awesome Module]:
 > Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.
 Enter package name [Custom]:
 > 
 Enter Drupal Core version [8.x]:
 > 
 Do you want to generate a .module file (yes/no) [yes]:
 > 
 Define module as feature (yes/no) [no]:
 > 
 Do you want to add a composer.json file to your module (yes/no) [yes]:
 > 
 Would you like to add module dependencies (yes/no) [no]:
 > 
 Do you want to generate a unit test class (yes/no) [yes]:
 > no
 Do you want to generate a themeable template (yes/no) [yes]:
 > no
 Do you confirm generation? (yes/no) [yes]:
 > 
<?php

/**
 * @file
 * Contains fitness.module.
 */

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function fitness_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the fitness module.
    case 'help.page.fitness':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.') . '</p>';
      return $output;

    default:
  }
}
name: 'Fitness'
type: module
description: 'Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.'
core: 8.x
package: 'Custom'

Generate Content Entity

 $ ../vendor/bin/drupal generate:entity:content
 $ ../vendor/bin/drupal generate:entity:content

 // Welcome to the Drupal Content Entity generator
 Enter the module name [admin_toolbar]:
 > fitness
 $ ../vendor/bin/drupal generate:entity:content

 // Welcome to the Drupal Content Entity generator
 Enter the module name [admin_toolbar]:
 > fitness

 Enter the class of your new content entity [DefaultEntity]:
 > WorkoutEntity

 Enter the machine name of your new content entity [workout_entity]:
 > workout

 Enter the label of your new content entity [Workout entity]:
 > Workout
 $ ../vendor/bin/drupal generate:entity:content

 // Welcome to the Drupal Content Entity generator
 Enter the module name [admin_toolbar]:
 > fitness

 Enter the class of your new content entity [DefaultEntity]:
 > WorkoutEntity

 Enter the machine name of your new content entity [workout_entity]:
 > workout

 Enter the label of your new content entity [Workout entity]:
 > Workout

 Enter the base-path for the content entity routes [/admin/structure]:
 > 

 $ ../vendor/bin/drupal generate:entity:content

 // Welcome to the Drupal Content Entity generator
 Enter the module name [admin_toolbar]:
 > fitness

 Enter the class of your new content entity [DefaultEntity]:
 > WorkoutEntity

 Enter the machine name of your new content entity [workout_entity]:
 > workout

 Enter the label of your new content entity [Workout entity]:
 > Workout

 Enter the base-path for the content entity routes [/admin/structure]:
 > 


 Do you want this (content) entity to have bundles (yes/no) [no]:
 > 

 Is your entity translatable (yes/no) [yes]:
 > no

 Is your entity revisionable (yes/no) [yes]:
 > no

2. Where do I field the relationship?

Entity Reference Field in Workouts

Exercise

Exercise

Exercise

Workout

Workout

count: 3

count: 2

3. Where do I specify the time?

Exercise

Exercise

Exercise

Workout

00:40

00:30

00:25

3. Where do I specify the time?

Ooh, that's a bit, er, uh? Hmm.

Field Collection?

Plugins

Paragraphs?

Wait, doesn't Wordpress use plugins?

Wordpress plugins are the equivalent of Drupal modules

Drupal 8 still has modules

 

So, what are Plugins for us?

Plugins are built inside modules

 $ ../vendor/bin/drupal list | grep generate:plugin
 $ ../vendor/bin/drupal list | grep generate:plugin
  generate:plugin:block (gpb)             Generate a plugin block
  generate:plugin:ckeditorbutton (gpc)    Generate CKEditor button plugin.
  generate:plugin:condition (gpco)        Generate a plugin condition.
  generate:plugin:field (gpf)             Generate field type, widget and formatter plugins.
  generate:plugin:fieldformatter (gpff)   Generate field formatter plugin.
  generate:plugin:fieldtype (gpft)        Generate field type plugin.
  generate:plugin:fieldwidget (gpfw)      Generate field widget plugin.
  generate:plugin:imageeffect (gpie)      Generate image effect plugin.
  generate:plugin:imageformatter (gpif)   Generate image formatter plugin.
  generate:plugin:mail (gpm)              Generate a plugin mail
  generate:plugin:migrate:process (gpmp)  Generate a migrate process plugin
  generate:plugin:migrate:source (gpms)   Generate a migrate source plugin
  generate:plugin:rest:resource (gprr)    Generate plugin rest resource
  generate:plugin:rulesaction (gpra)      Generate a plugin rule action
  generate:plugin:skeleton (gps)          Generate an implementation of a skeleton plugin
  generate:plugin:type:annotation (gpta)  Generate a plugin type with annotation discovery
  generate:plugin:type:yaml (gpty)        Generate a plugin type with Yaml discovery
  generate:plugin:views:field (gpvf)      Generate a custom plugin view field.
 $ 
$ ../vendor/bin/drupal generate:plugin:field
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
 Enter the field type plugin label [Entity relationship field with time]:
 > 
 Enter the field type plugin id [entity_relationship_field_with_time]:
 > 
 Enter the field type plugin description [My Field Type]:
 > A custom Entity Relationship Field with attached hours, minutes, seconds field.
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
 Enter the field type plugin label [Entity relationship field with time]:
 > 
 Enter the field type plugin id [entity_relationship_field_with_time]:
 > 
 Enter the field type plugin description [My Field Type]:
 > A custom Entity Relationship Field with attached hours, minutes, seconds field.
 Enter the field widget plugin class name [ExampleWidgetType]:
 > EntityRelationshipFieldWithTimeWidget
 Enter the field widget plugin label [Entity relationship field with time widget]:
 > 
 Enter the field widget plugin id [entity_relationship_field_with_time_widget]:
 > 
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
 Enter the field type plugin label [Entity relationship field with time]:
 > 
 Enter the field type plugin id [entity_relationship_field_with_time]:
 > 
 Enter the field type plugin description [My Field Type]:
 > A custom Entity Relationship Field with attached hours, minutes, seconds field.
 Enter the field widget plugin class name [ExampleWidgetType]:
 > EntityRelationshipFieldWithTimeWidget
 Enter the field widget plugin label [Entity relationship field with time widget]:
 > 
 Enter the field widget plugin id [entity_relationship_field_with_time_widget]:
 > 
 Enter the field formatter plugin class name [ExampleFormatterType]:
 > EntityRelationshipFieldWithTimeFormatter
 Enter the field formatter plugin label [Entity relationship field with time formatter]:
 > 
 Enter the field formatter plugin id [entity_relationship_field_with_time_formatter]:
 > 
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
 Enter the field type plugin label [Entity relationship field with time]:
 > 
 Enter the field type plugin id [entity_relationship_field_with_time]:
 > 
 Enter the field type plugin description [My Field Type]:
 > A custom Entity Relationship Field with attached hours, minutes, seconds field.
 Enter the field widget plugin class name [ExampleWidgetType]:
 > EntityRelationshipFieldWithTimeWidget
 Enter the field widget plugin label [Entity relationship field with time widget]:
 > 
 Enter the field widget plugin id [entity_relationship_field_with_time_widget]:
 > 
 Enter the field formatter plugin class name [ExampleFormatterType]:
 > EntityRelationshipFieldWithTimeFormatter
 Enter the field formatter plugin label [Entity relationship field with time formatter]:
 > 
 Enter the field formatter plugin id [entity_relationship_field_with_time_formatter]:
 > 
 Enter the field type the formatter and widget plugin can be used with 
[entity_relationship_field_with_time]:
 > 
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
 Enter the field type plugin label [Entity relationship field with time]:
 > 
 Enter the field type plugin id [entity_relationship_field_with_time]:
 > 
 Enter the field type plugin description [My Field Type]:
 > A custom Entity Relationship Field with attached hours, minutes, seconds field.
 Enter the field widget plugin class name [ExampleWidgetType]:
 > EntityRelationshipFieldWithTimeWidget
 Enter the field widget plugin label [Entity relationship field with time widget]:
 > 
 Enter the field widget plugin id [entity_relationship_field_with_time_widget]:
 > 
 Enter the field formatter plugin class name [ExampleFormatterType]:
 > EntityRelationshipFieldWithTimeFormatter
 Enter the field formatter plugin label [Entity relationship field with time formatter]:
 > 
 Enter the field formatter plugin id [entity_relationship_field_with_time_formatter]:
 > 
 Enter the field type the formatter and widget plugin can be used with 
[entity_relationship_field_with_time]:
 > 
 Enter the default field widget of the field type plugin 
[entity_relationship_field_with_time_widget]:
 > 
 Enter the default field formatter of field type plugin 
[entity_relationship_field_with_time_formatter]:
 > 
$ ../vendor/bin/drupal generate:plugin:field

 // Welcome to the Drupal Field Plugin generator
 Enter the module name [admin_toolbar]:
 > fitness
 Field type plugin class name [ExampleFieldType]:
 > EntityRelationshipFieldWithTime
 Enter the field type plugin label [Entity relationship field with time]:
 > 
 Enter the field type plugin id [entity_relationship_field_with_time]:
 > 
 Enter the field type plugin description [My Field Type]:
 > A custom Entity Relationship Field with attached hours, minutes, seconds field.
 Enter the field widget plugin class name [ExampleWidgetType]:
 > EntityRelationshipFieldWithTimeWidget
 Enter the field widget plugin label [Entity relationship field with time widget]:
 > 
 Enter the field widget plugin id [entity_relationship_field_with_time_widget]:
 > 
 Enter the field formatter plugin class name [ExampleFormatterType]:
 > EntityRelationshipFieldWithTimeFormatter
 Enter the field formatter plugin label [Entity relationship field with time formatter]:
 > 
 Enter the field formatter plugin id [entity_relationship_field_with_time_formatter]:
 > 
 Enter the field type the formatter and widget plugin can be used with 
[entity_relationship_field_with_time]:
 > 
 Enter the default field widget of the field type plugin 
[entity_relationship_field_with_time_widget]:
 > 
 Enter the default field formatter of field type plugin 
[entity_relationship_field_with_time_formatter]:
 > 
 Do you confirm generation? (yes/no) [yes]:
 > 

LET'S SEE SOME CODE!

/*
 * @file web/modules/custom/fitness/composer.json
 */
/*
 * @file web/modules/custom/fitness/composer.json
 */

{
  . . . 

  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
/*
 * @file web/modules/custom/fitness/composer.json
 */

{
  . . . 

  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {

  }
/*
 * @file web/modules/custom/fitness/composer.json
 */

{
  . . . 

  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {
    "drupal/hms_field": "1.x-dev"
  }
$ cd my-fitness-app/
$ cd my-fitness-app/
$ composer update drupal/hms_field
$ cd my-fitness-app/
$ composer update drupal/hms_field
$ ls web/modules/contrib/
admin_toolbar	hms_field
name: 'Fitness'
type: module
description: 'Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.'
core: 8.x
package: 'Custom'
name: 'Fitness'
type: module
description: 'Provides Exercise and Workout entities, and custom Entity Relationship Field with Time.'
core: 8.x
package: 'Custom'
dependencies:
- hms_field
use Drupal\Core\Field\FieldItemBase;

/**
 * Plugin implementation of the 'entity_relationship_field_with_time' field type.
 *
 * @FieldType(
 *   id = "entity_relationship_field_with_time",
 *   label = @Translation("Entity relationship field with time"),
 *   description = @Translation("A custom Entity Relationship Field with attached hours, minutes, seconds field."),
 *   default_widget = "entity_relationship_field_with_time_widget",
 *   default_formatter = "entity_relationship_field_with_time_formatter"
 * )
 */
class EntityRelationshipFieldWithTime extends FieldItemBase {}
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;

/**
 * Plugin implementation of the 'entity_relationship_field_with_time' field type.
 *
 * @FieldType(
 *   id = "entity_relationship_field_with_time",
 *   label = @Translation("Entity relationship field with time"),
 *   description = @Translation("A custom Entity Relationship Field with attached hours, minutes, seconds field."),
 *   default_widget = "entity_relationship_field_with_time_widget",
 *   default_formatter = "entity_relationship_field_with_time_formatter",
 *   category = @Translation("Reference"),
 *   list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
 * )
 */
class EntityRelationshipFieldWithTime extends EntityReferenceItem {}
class EntityReferenceWithTimeFieldType extends EntityReferenceItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {

  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {

  }
class EntityReferenceWithTimeFieldType extends EntityReferenceItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);



    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = parent::schema($field_definition);



    return $schema;
  }
class EntityReferenceWithTimeFieldType extends EntityReferenceItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);

    $properties['time'] = DataDefinition::create('integer')
      ->setLabel(new TranslatableMarkup('Time'));

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = parent::schema($field_definition);





    return $schema;
  }
class EntityReferenceWithTimeFieldType extends EntityReferenceItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);

    $properties['time'] = DataDefinition::create('integer')
      ->setLabel(new TranslatableMarkup('Time'));

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    $schema = parent::schema($field_definition);

    $schema['columns']['time'] = [
      'type' => 'int',
      'length' => 11,
    ];

    return $schema;
  }
use Drupal\Core\Field\WidgetBase;

class EntityRelationshipFieldWithTimeWidget extends WidgetBase { }
use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;

class EntityReferenceWithTimeFieldWidget extends EntityReferenceAutocompleteWidget { 



}
use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;

class EntityReferenceWithTimeFieldWidget extends EntityReferenceAutocompleteWidget { 

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {



  }

}
use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;

class EntityReferenceWithTimeFieldWidget extends EntityReferenceAutocompleteWidget { 

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = parent::formElement($items, $delta, $element, $form, $form_state);


    return $element;
  }

}
use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;

class EntityReferenceWithTimeFieldWidget extends EntityReferenceAutocompleteWidget { 

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $element = parent::formElement($items, $delta, $element, $form, $form_state);

    $element['time'] = [
      '#type' => 'hms',
      '#title' => $items[$delta]->get('time')->getDataDefinition()->getLabel(),
    ];

    return $element;
  }

}
use Drupal\Core\Field\FormatterBase;

class EntityRelationshipFieldWithTimeFormatter extends FormatterBase {
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;

class EntityReferenceWithTimeFieldFormatter extends EntityReferenceEntityFormatter {






}
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;

class EntityReferenceWithTimeFieldFormatter extends EntityReferenceEntityFormatter {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $field_items, $langcode) {





  }

}
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;

class EntityReferenceWithTimeFieldFormatter extends EntityReferenceEntityFormatter {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $field_items, $langcode) {
    $elements = parent::viewElements($field_items, $langcode);







    return $elements;
  }

}
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;

class EntityReferenceWithTimeFieldFormatter extends EntityReferenceEntityFormatter {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $field_items, $langcode) {
    $elements = parent::viewElements($field_items, $langcode);

    foreach ($elements as $delta => $item) {
      $items = [];

      $additional_field = $field_items[$delta]->get('time')->getValue();
      $items[] = [
        '#theme' => 'hms',
        '#format' => 'hh:mm:ss',
        '#value' => $additional_field,
      ];
      $items[] = $elements[$delta];

      $elements[$delta] = $items;
    }

    return $elements;
  }

}

Summary

  • Drupal Composer
    • drupal root = web/
    • ../vendor/bin/drush
    • sets up composer.json for drupal magic
  • Composer merge
    • Ability to read sub-directory composer.json
    • Require dependencies from custom module
    • List dependencies in info.yml
  • Drupal console is badass
    • Generate entities, plugins, and more
  • Plugins
    • Field Widget/Formatter, Image Formatter, etc
    • OOP allowed us to build on top of Ent Ref

Questions?

June 2019

drupalcampchattanooga.com