Devloping for Drupal 8 isn't hard!

...probably

http://rocketeerbkw.github.io/slides/austin-drupal-users-group/developing-for-d8-isnt-hard/

Disclaimer

These are my opinions and I may not be right.
I apologize if I get ranty.

Example code here is not complete.

PSA

Stop calling Drupal 8 hard!

This "marketing" is turning away people before they get a chance to see to see for themselves.

What's Changed?

Everything... and nothing. Major Drupal concepts haven't changed, they've just moved around or have a different interface.

There are some new things to get familiar with too.

Blocks

MODULE.module

function MODULE_block_info() {
  $blocks = array();

  $blocks['MODULE_block_1'] = array(
    'info' => 'Module Block 1',
  );

  return $blocks;
}
              

function MODULE_block_view($delta) {
  $block = array();

  switch ($delta) {
    case 'MODULE_block_1';
      $block['subject'] = t('Block 1');
      $block['content'] = t('Content!');
  }

  return $block;
}
              

function MODULE_block_configure() {}
function MODULE_block_save() {}
              
src/Plugin/Block/Block1.php

namespace Drupal\MODULE\Plugin\Block;
use Drupal\Core\Block\BlockBase;

/**
 * @Block(
 *   id = "MODULE_block_1",
 *   admin_label = @Translation("Module Block 1")
 * )
 */
class Block1 extends BlockBase {
  public function build() {
    return array(
      '#markup' => t('Content!');
    );
  }
}
              

function blockForm() {}
function blockSubmit() {}
function blockAccess() {}
function defaultConfiguration() {}
              

Plugins

... are small pieces of functionality that are swappable.

  • Modules can provide plugins of type X or say they accept plugins of type Y.
  • Metadata provided by annotations.
  • hook_*_info now mostly plugins.
  • Blocks
  • Field Formatters, Widgets
  • Views
  • Actions & Conditions
  • Routing (optional)

Plugins


namespace Drupal\user\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;

/**
 * @FieldFormatter(
 *   id = "author",
 *   label = @Translation("Author"),
 *   description = @Translation("Display the referenced author user entity."),
 *   field_types = {
 *     "entity_reference"
 *   }
 * )
 */
class AuthorFormatter extends FormatterBase {
  public function viewElements(FieldItemListInterface $items) {}
  public static function isApplicable(FieldDefinitionInterface $field_definition) {}
}
            

Plugins


namespace Drupal\user\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Checks if a user's email address is unique on the site.
 *
 * @Plugin(
 *   id = "UserMailUnique",
 *   label = @Translation("User email unique", context = "Validation")
 * )
 */
class UserMailUnique extends Constraint {
  public $message = 'The email address %value is already taken.';
  public function validatedBy() {}
}
            

Events

MODULE.module

function hook_boot() {
  // Do stuff on every page load
}

function hook_init() {
  // Do stuff on every non-cached page load
}
              
MODULE.services.yml

services:
  MODULE.MODULE_subscriber:
    class: Drupal\MODULE\MODULESubscriber
    tags:
      - { name: 'event_subscriber' }
              
src/MODULESubscriber.php

namespace Drupal\MODULE;

use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class MODULESubscriber implements EventSubscriberInterface {
  public function onLoad(GetResponseEvent $event) {
    // Do stuff on each page load
  }

  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('onLoad');
    return $events;
  }
}
            

Kernel Events

  • Request
    • hook_init
    • hook_boot
    • hook_url_inbound_alter
    • hook_menu_site_status_alter
  • Exception
  • View
  • Controller
  • Response
    • hook_drupal_goto_alter
  • Terminate
  • Finish Request

YAML Ain't Markup Language

A human friendly data serialization standard for all programming languages.

Replacement for Drupal Arrays of Doom ™. Safer(ish) than moving PHP around.

  • Info files
  • Menu system
  • Views, Migrate modules
  • CMI
  • Plugins (Kinda)
  • More!

Views Export


$view = new view();
$view->name = 'test';
$view->description = '';
$view->tag = 'default';
$view->base_table = 'node';
$view->human_name = 'Test';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['title'] = 'Test';
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'perm';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '10';
$handler->display->display_options['style_plugin'] = 'default';
$handler->display->display_options['row_plugin'] = 'node';
/* Field: Content: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'node';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['label'] = '';
$handler->display->display_options['fields']['title']['alter']['word_boundary'] = FALSE;
$handler->display->display_options['fields']['title']['alter']['ellipsis'] = FALSE;
/* Sort criterion: Content: Post date */
$handler->display->display_options['sorts']['created']['id'] = 'created';
$handler->display->display_options['sorts']['created']['table'] = 'node';
$handler->display->display_options['sorts']['created']['field'] = 'created';
$handler->display->display_options['sorts']['created']['order'] = 'DESC';
/* Filter criterion: Content: Published */
$handler->display->display_options['filters']['status']['id'] = 'status';
$handler->display->display_options['filters']['status']['table'] = 'node';
$handler->display->display_options['filters']['status']['field'] = 'status';
$handler->display->display_options['filters']['status']['value'] = 1;
$handler->display->display_options['filters']['status']['group'] = 1;
$handler->display->display_options['filters']['status']['expose']['operator'] = FALSE;

/* Display: Page */
$handler = $view->new_display('page', 'Page', 'page');
$handler->display->display_options['path'] = 'test';

uuid: 557e1a08-e34c-488a-8600-c77c496509fb
langcode: en
status: true
dependencies:
  module:
    - node
    - user
id: test
label: Test
module: views
description: ''
tag: ''
base_table: node
base_field: nid
core: 8.x
display:
  default:
    display_plugin: default
    id: default
    display_title: Master
    position: 0
    display_options:
      access:
        type: perm
        options:
          perm: 'access content'
      cache:
        type: none
        options: {  }
      query:
        type: views_query
        options:
          disable_sql_rewrite: false
          distinct: false
          replica: false
          query_comment: false
          query_tags: {  }
      exposed_form:
        type: basic
        options:
          submit_button: Apply
          reset_button: false
          reset_button_label: Reset
          exposed_sorts_label: 'Sort by'
          expose_sort_order: true
          sort_asc_label: Asc
          sort_desc_label: Desc
      pager:
        type: full
        options:
          items_per_page: 10
          offset: 0
          id: 0
          total_pages: null
          expose:
            items_per_page: false
            items_per_page_label: 'Items per page'
            items_per_page_options: '5, 10, 25, 50'
            items_per_page_options_all: false
            items_per_page_options_all_label: '- All -'
            offset: false
            offset_label: Offset
          tags:
            previous: '‹ previous'
            next: 'next ›'
            first: '« first'
            last: 'last »'
          quantity: 9
      style:
        type: default
      row:
        type: 'entity:node'
        options:
          build_mode: teaser
          links: true
          view_mode: teaser
          rendering_language: translation_language_renderer
      fields:
        title:
          id: title
          table: node_field_data
          field: title
          label: ''
          alter:
            alter_text: false
            make_link: false
            absolute: false
            trim: false
            word_boundary: false
            ellipsis: false
            strip_tags: false
            html: false
          hide_empty: false
          empty_zero: false
          link_to_node: 1
          relationship: none
          group_type: group
          admin_label: ''
          exclude: false
          element_type: ''
          element_class: ''
          element_label_type: ''
          element_label_class: ''
          element_label_colon: true
          element_wrapper_type: ''
          element_wrapper_class: ''
          element_default_classes: true
          empty: ''
          hide_alter_empty: true
      filters:
        status:
          value: true
          table: node_field_data
          field: status
          provider: node
          id: status
          expose:
            operator: ''
          group: 1
      sorts:
        created:
          id: created
          table: node_field_data
          field: created
          order: DESC
          relationship: none
          group_type: group
          admin_label: ''
          exposed: false
          expose:
            label: ''
          granularity: second
      title: Test
      header: {  }
      footer: {  }
      empty: {  }
      relationships: {  }
      arguments: {  }
      field_langcode: '***LANGUAGE_language_content***'
      field_langcode_add_to_query: null
  page_1:
    display_plugin: page
    id: page_1
    display_title: Page
    position: 1
    display_options:
      field_langcode: '***LANGUAGE_language_content***'
      field_langcode_add_to_query: null
      path: test

              

Menus & Routing

Route

MODULE.routing.yml


MODULE.dashboard
  path: '/MODULE/dashboard'
  defaults:
    _content: '\Drupal\MODULE\Controller\DashboardController::dashboard'
    _title: 'Dashboard'
  requirements:
    _permission: 'view MODULE dashboard'
            

namespace Drupal\MODULE\Controller;

Class DashboardController {
  public function dashboard() {}
}
            

Local Tasks, Actions

books.links.tasks.yml


book.admin:
  route_name: book.admin
  title: 'Books'
  base_route: system.admin_content
book.admin.list:
  route_name: book.admin
  title: 'List'
  parent_id: book.admin
book.settings:
  route_name: book.settings
  title: 'Settings'
  parent_id: book.admin
  weight: 8
              

menu.links.action.yml


menu.menu_add:
  route_name: menu.menu_add
  title: 'Add menu'
  appears_on:
    - menu.overview_page
              

Contextual Links, Menu Items

menu.links.contextual.yml


menu.edit:
  title: 'Edit menu'
  route_name: 'menu.menu_edit'
  group: menu
              

book.links.menu.yml


book.render:
  title: 'Books'
  route_name: book.render
  hidden: 1
              

Forms


function MODULE_settings_form($form, &$form_state) {
  $form['MODULE_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );

  return system_settings_form($form);
}
              

namespace Drupal\MODULE\Form;
use Drupal\Core\Form\ConfigFormBase;
class MODULESettingsForm extends ConfigFormBase {
  public function getFormID() {
    return 'MODULE.settings';
  }

  public function buildForm(array $form, array &$form_state) {
    $form['MODULE_name'] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
    );

    return parent::buildForm($form, $form_state);
  }

  public function submitForm(array &$form, &$form_state) {
    // Set CMI stuff here
    parent::submitForm($form, $form_state);
  }
}
              

Other

  • Entity API
  • CMI

Questions

@rocketeerbkw
rocketeerbkw@gmail.com
rocketeerbkw.com