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

D8: Composer, Entities, and Complex Relationships

By James Candan

D8: Composer, Entities, and Complex Relationships

We'll review some of my findings while learning and building web applications in Drupal 8. Not solely for enterprise content management, but consider applications for business management, intranets, mobile app backends, SaaS, and more. We'll look at some excellent combinations of well supported modules and glue code to create flexible architectures.

  • 2,124