Devloping for Drupal 8 isn't hard!

Brandon Williams
@rocketeerbkw
http://rocketeerbkw.github.io/slides/north-texas-drupal/developing-for-d8-isnt-hard/

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?

You'll recognize major Drupal concepts.
You won't recognize the boilerplate code.

There are some new things to get familiar with too.

Blocks

ntd.module
function ntd_block_info() {
  $blocks = array();

  $blocks['ntd_block_1'] = array(
    'info' => 'NTD Block 1',
  );

  return $blocks;
}

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

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

  return $block;
}

function ntd_block_configure() {}
function ntd_block_save() {}
src/Plugin/Block/Block1.php
namespace Drupal\ntd\Plugin\Block;
use Drupal\Core\Block\BlockBase;

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

  public function blockForm() {}
  public function blockSubmit() {}
  public function blockAccess() {}
  public 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

ntd.module

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

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

services:
  ntd.subscriber:
    class: Drupal\ntd\NTDSubscriber
    tags:
      - { name: 'event_subscriber' }
              
src/NTDSubscriber.php

namespace Drupal\ntd;

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

class NTDSubscriber 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

Other Events

From core.api.php

  • BlockEvents
  • ConfigEvents
  • EntityTypeEvents
  • LocalEvents
  • RenderEvents
  • RoutingEvents
    • hook_menu_alter

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
  • Config API
  • Plugins (Kinda)
  • More!

Views YAML


$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

ntd.routing.yml


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

namespace Drupal\ntd\Controller;

Class DashboardController {
  public function dashboard() {
    return array(
      '#markup' => t('Your 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 ntd_form($form, &$form_state) {
  $form['ntd_name'] = array(
  '#type' => 'textfield',
  '#title' => t('Name'),
);

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

  return system_settings_form($form);
}
              
namespace Drupal\ntd\Form;
use Drupal\Core\Form\ConfigFormBase;

class NTDForm extends ConfigFormBase {
  public function getFormID() {
    return 'ntd.settings';
}

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

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

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

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

Config API

ntd.module (D7)

variable_get('ntd_best_meetup', FALSE);
variable_set('ntd_best_meetup', TRUE);
            
ntd.config.yml

best_meetup: n
organizers:
  - David Hahn
  - You!
            
Read-Only Config

$config = \Drupal::config('ntd.config');
$config->get('best_meetup'); // FALSE
            
Write Config

$config = \Drupal::service('config.factory')->getEditable('ntd.config');
$config->set('best_meetup', TRUE);
$config->save(); // Don't forget this!
            

Other

  • Config Entity API
  • Entity API

Questions?

@rocketeerbkw
rocketeerbkw@gmail.com
rocketeerbkw.com