Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Building Modules for Drupal 8
Modules

Drupal 8 Basics

  1. YAML
  2. PSR-0 Autoloading
  3. Routes & Controllers
  4. Configuration Management
  5. Services & Dependency Injection
  6. Automated Testing
  7. Twig Templates
  8. Plugins & Annotations

YAML

Yet Another Markup Language*

* YAML Ain't Markup Language
"YAML is a human friendly data serialization standard for all programming languages."
-yaml.org

Why YAML?

  1. YAML is both human editable and machine readable.
  2. .yml files are not Drupal specific like .info files.
  3. XML would require a Drupal specific schema.
  4. JSON doesn't allow comments.
  5. Non-ASCII characters require escaping in JSON.
"...and XML has cooties ;)" -chx
http://www.drupal4hu.com/node/377

Drupal 7

Info File: demo.info
name = Demo Module
description = "A demo module used to explain the basics."
core = 7.x
package = "D7 Demo"
dependencies[] = node

Drupal 8

Info File: demo.info.yml
name: Demo Module
type: module
description: "A demo module used to explain the basics."
core: 8.x
package: D8 Demo
dependencies:
 - node

FIG & PSR-0

The Framework Interoperatabilty Group is a collection of individuals that discuss the commonalities between their PHP-based projects and find ways we can all work together.

This group established the PSR-0 standard which is a guideline to be followed for autoloader interoperability.

PSR-0 Standard

PSR-0 Examples

\Guzzle\Common\Event => /Guzzle/Common/Event.php
\Symfony\Component\Yaml\Parser => /Symfony/Component/Yaml/Parser.php
\Drupal\Core\Config\FileStorage => /Drupal/Core/Config/FileStorage.php
\namespace\package\Class_Name => /namespace/package/Class/Name.php

Avoiding Namespace Collisions

namespace Guzzle\Common;

use Symfony\Component\EventDispatcher\Event as SymfonyEvent;

/**
 * Default event for Guzzle notifications
 */
class Event extends SymfonyEvent implements ToArrayInterface, \ArrayAccess, \IteratorAggregate
{
...

Drupal 8 Module

\Drupal\demo\Controller\MyController => /modules/demo/lib/Drupal/demo/Controller/MyController.php
namespace Drupal\demo\Controller;

class MyController {

  private function text() {
    return t('Hello, world');
  }

  public function page() {
    return array(
      '#type' => 'markup',
      '#markup' => $this->text(),
    );
  }

}

Class Inheritance

/modules/demo/lib/Drupal/demo/Controller/FinalController.php
namespace Drupal\demo\Controller;

use Drupal\demo\Controller\MyController;

class FinalController extends MyController {

  final public function page() {
    return array(
      '#type' => 'markup',
      '#markup' => t('Goodbye, cruel world!'),
    );
  }

}

What is a Controller?

A controller is a PHP function you create that takes information from the HTTP request [then] constructs and returns an HTTP response (as a Symfony2 Response object). The response could be an HTML page, an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else you can dream up. The controller contains whatever arbitrary logic your application needs to render the content of a page.
http://symfony.com/doc/current/book/controller.html

Our Demo Controller

/Drupal/demo/Controller/MyController::page

Drupal 8 Routing

A route is a path which is defined for Drupal to return some sort of content on. For example, the default front page, '/node' is a route. When Drupal receives a request, it tries to match the requested path to a route it knows about. If the route is found, then the route's definition is used to return content. Otherwise, Drupal returns a 404.
Drupal's routing system works with the Symfony HTTP Kernel [and] is responsible for matching paths to controllers, and you define those relations in routes. You can pass on additional information to your controllers in the route [while] access checking is integrated.
http://drupal.org/developing/api/8/routing

Drupal 7 Routing Example

/sites/all/modules/demo/demo.module
function demo_menu() {
  $items['hello'] = array(
    'title' => 'Demo',
    'page callback' => 'demo_page',
    'access arguments' => array('access content'),
  );
  return $items;
}

function demo_page() {
  return t('Hello, world!');
}

Drupal 8 Routing Example

/modules/demo/demo.routing.yml
demo_page:
  path: '/hello'
  defaults:
    _title: 'Demo'
    _content: 'Drupal\demo\Controller\MyController::page'
  requirements:
    _permission: 'access content'
demo_final_page:
  path: '/goodbye'
  defaults:
    _title: 'Demo'
    _content: 'Drupal\demo\Controller\FinalController::page'
  requirements:
    _permission: 'access content'
http://drupal.org/node/2092643

Why use a Routing component?

  1. hook_menu() did too much. From handling incoming requests to providing menu links. Everything was tied to it.
  2. Because the hook function did so many things, all of these features were tightly coupled together.
  3. Using the Symfony2 Routing component, we are able to split out the route handling aspect into its own system.
  4. We can now create routes on more than just the path, for example make a request for JSON, XML or HTML while still using the same path.
http://www.previousnext.com.au/blog/using-drupal-8s-new-route-controllers

Using Access Checks with Routes

Procedural access callbacks from Drupal 7 have been replaced by AccessCheck services in Drupal 8.

 

 

Drupal 7 Access Callback

/sites/all/modules/demo/demo.module
function demo_menu() {
  $items['hello'] = array(
    'title' => 'Demo',
    'page callback' => 'demo_page',
    'access callback' => 'demo_anonymous_access_only',
  );
  return $items;
}

function demo_anonymous_access_only() {
  global $user;
  if (in_array('anonymous user', $user->roles)) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

Drupal 8 AccessCheck Discovery

/modules/demo/demo.routing.yml
demo_final_page:
  path: '/goodbye'
  defaults:
    _title: 'Demo'
    _content: 'Drupal\demo\Controller\FinalController::page'
  requirements:
    _permission: 'access content'
    _access_user_is_anonymous: 'TRUE'
/modules/demo/demo.services.yml
services:
  access_check.demo.user_is_anonymous:
    class: Drupal\demo\Access\UserIsAnonymousAccessCheck
    tags:
      - { name: access_check }

Drupal 8 AccessCheckInterface

/modules/demo/lib/Drupal/demo/Access/UserIsAnonymousAccessCheck.php
namespace Drupal\demo\Access;

use Drupal\Core\Access\AccessCheckInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;

class UserIsAnonymousAccessCheck implements AccessCheckInterface {

  public function applies(Route $route) {
    return $route->hasRequirement('_access_user_is_anonymous');
  }

  public function access(Route $route, Request $request, AccountInterface $account) {
    return (!$account->isAnonymous() ? static::DENY : static::ALLOW);
  }
}

 

Dynamic Routes in Drupal 8

What does one do if they need to create a route dynamically?

How does one create routes based on things that don't yet exist?

In Drupal 7, we created dynamic routes with a loop in hook_menu().

In Drupal 8, dynamic routes are created in a callback method.

Drupal 7 Dynamic Route Example

/sites/all/modules/octocat/octocat.module
function octocat_menu() {
  $items = array();
  foreach (octocat_get_types() as $type) {
    $items['octocat/add/' . $type->machine_name] = array(
      'title' => $type->title,
      'page callback' => 'octocat_type_add_page',
      'access arguments' => 'create ' . $type->type,
    );
  }
  return $items;
}

Drupal 8 Dynamic Routing

Declaring 'route_callbacks' in module.routing.yml

/modules/octocat/octocat.routing.yml
route_callbacks:
  - '\Drupal\octocat\Routing\OctoCatRoutes::routes'

Drupal 8 Dynamic Routing Example Routing Method

/modules/octocat/lib/Drupal/octocat/Routing/OctoCatRoutes.php
namespace Drupal\octocat\Routing;

use Symfony\Component\Routing\Route;

class OctoCatRoutes {
  public function routes() {
    $routes = array();
    foreach (octocat_get_types() as $type) {
      $routes['octocat.add.' . $type] = new Route(
        '/octocat/add/' . $type,
        array(
          '_title' => t('Add @type', array('@type' => ucwords($type))),
          '_content' => '\Drupal\octocat\Controller\OctoCatController::add',
          'type' => $type,
        ),
        array('_access' => 'TRUE')
      );
    }
    return $routes;
  }
}

Drupal 8

Configuration Management

Drupal 8 has a whole new configuration system that uses human-readable text files in the YAML (.yml) format to store configuration items. All of your site configuration from enabled modules through fields, contact categories to views are stored with this system. The system is designed to make it much easier than prior Drupal versions to export all your site configuration, make changes and import changes back in.
http://drupal.org/documentation/administer/config

Drupal 8 Example Configuration File

/modules/octocat/config/octocat.settings.yml
octocats:
 - original
 - drupalcat
 - dojocat
 - codercat

Drupal 8 Using Configuration Example Usage

/modules/octocat/lib/Drupal/octocat/Routing/OctoCatRoutes.php
class OctoCatRoutes {

  public function routes() {
    $config = \Drupal::config('octocat.settings');
    $routes = array();
    foreach ($config->get('octocats') as $type) {
      $routes['octocat.page.' . $type] = new Route(
        '/octocat/' . $type,
        array(
          '_title' => t('@type', array('@type' => ucwords($type))),
          '_content' => '\Drupal\octocat\Controller\OctoCatController::page',
          'type' => $type,
        ),
        array('_access' => 'TRUE')
      );
    }
    return $routes;
  }
}
Isn't it time we talked about

Dependency Injection?

(No, you won't have to get a shot.)
"Dependency Injection" is a 25-dollar term for a 5-cent concept.
-James Shore
http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html

 

Dependency Non-Injection Part Deux

class User {
  private $name;
  public function __construct($name) {
    $this->name = $name;
  }
  public function getName() {
    return $this->name;
  }
}

class Tweet {
  private $user;
  private $tweet;
  public function __construct($tweet, $user_name) {
    $this->user = new User($user_name);
    $this->tweet = $tweet;
  }
  public function getUser() {
    return $this->user;
  }
  public function getTweet() {
    return $this->tweet;
  }
}

What went wrong?

  1. The $user_name passed to the Tweet constructor is not used inside Tweet's scope. $user_name should remain inside the User class - it has nothing to do with the actual Tweet .
  2. The User class is tightly coupled with the Tweet class. If we add a new parameter to User's constructor, we must then modify every class where we've created a User object. This is technical debt we'd like to avoid.
  3. Unit testing the Tweet class is has become a convoluted chore as it now requires testing the User class as well.

Dependency Injection Corrected

class User {
  private $name;
  public function __construct($name) {
    $this->name = $name;
  }
  public function getName() {
    return $this->name;
  }
}

class Tweet {
  private $user;
  private $tweet;
  public function __construct($tweet, User $user) {
    $this->user = $user;
    $this->tweet = $tweet;
  }
  public function getUser() {
    return $this->user;
  }
  public function getTweet() {
    return $this->tweet;
  }
}

Diagnosis

Using Dependency Injection relieves the symptoms of tightly coupled, untestable code by inserting the dependencies through the dependent class' constructor. This practice is commonly referred to as Constructor Injection.

 

Drupal 8 Services

A Service is any PHP object that performs some sort of "global" task. Services are called upon throughout our application whenever we need a specific set of functionality.

Services Registration

Similar to an "info hook" in previous Drupal versions, we define a service in a *.services.yml file. Many services are already provided by core in /core/core.services.yml
services:
  cache_factory:
    class: Drupal\Core\Cache\CacheFactory
    arguments: ['@settings']
    calls:
      - [setContainer, ['@service_container']]
...
But you may need to define your own services, for example, when providing custom interfaces. Thankfully, its a simple as providing your own mymodule.services.yml file for Drupal to discover.
http://www.palantir.net/blog/d8ftw-breadcrumbs-work

 

 

The Global Services Container

Generally, code in Drupal should accept its dependencies via either constructor injection or setter method injection. There are cases, such as procedural code, where this can't be done. The class Drupal acts as a global accessor to arbitrary services within the system.
// Returns a Drupal\Core\Datetime\Date object.
$date = \Drupal::service('date');
http://drupal.org/node/2133171

Consider the following diff

/modules/octocat/lib/Drupal/octocat/Routing/OctoCatRoutes.php

What did we do?

Now we can unit test the provided service!

...
  public function getOctoCatsTest() {
    $types = array('spectrocat', 'octobiwan');
    $config = $this->getMockBuilder('Drupal\Core\Config\Config')
      ->disableOriginalConstructor()
      ->getMock();
    $config->expects($this->once())
      ->method('get')
      ->with('octocats')
      ->will($this->returnValue($types));
    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('octocat.settings')
      ->will($this->returnValue($config));
    $octocatRoutes = new OctoCatRoutes($this->configFactory);
    $octocats = $octocatRoutes->getOctoCats();
    $this->assertNotEmpty($octocats);
    $this->assertCount(2, $octocats);
  }

Automated Testing in Drupal 8

Drupal 8 now supports PHPUnit out of the box. PHPUnit is a unit testing framework created with the view that the sooner you detect your code mistakes, the quicker you can fix them. Writing unit tests for your code will make your code better. Simpletest is still supported but should only be used for web tests and unit tests that require a complete or partial Drupal environment. PHPUnit's tests are faster than SimpleTest and can easily be run via command line.
 

For example, running a single method:

./vendor/bin/phpunit --filter=MyMethodTest
http://drupal.org/phpunit
http://phpunit.de
Speaking of the command line...

Using Drush in D8

  1. The Drush project has moved all development to Github.
  2. Drush is now built using Composer, like so: composer global require drush/drush:dev-master
  3. You must run Drush version 7 or higher when using it with D8.
  4. Some commands are deprecated, like drush cache-clear all (Protip: use drush cache-rebuild)
  5. Other commands are incompatible due to D8 changes, such as drush dis [module] (Hint: try drush pmu [module])
  6. Don't panic - drush will explain these changes when you attempt to use a deprecated or incompatible command.
http://github.com/drush-ops/drush

Twig Templating

Twig is a flexible, fast, and secure template engine for PHP. http://twig.sensiolabs.org/documentation

Drupal 8 Twig Templating Implementing hook_theme()

/modules/octocat/octocat.module
function octocat_theme() {
  return array(
    'octocat_page' => array(
      'variables' => array('type' => NULL),
      'template' => 'octocat-page',
    ),
  );
}

Drupal 8 Twig Templating Example Usage

/modules/octocat/lib/Drupal/octocat/Controller/OctoCatController.php
namespace Drupal\octocat\Controller;

use Symfony\Component\HttpFoundation\Request;

class OctoCatController {

  public function page(Request $request, $type) {
    return array(
      '#theme' => 'octocat_page',
      '#type' => $type,
    );
  }
}

Drupal 8 Twig Templating Example Template File

/modules/octocat/templates/octocat-page.html.twig
<div id="octocat-{{ type }}" class="octocat">
  <img src="/modules/octocat/images/{{ type }}.jpg" title="{{ type }}" />
</div>

The case for Plugins in core

In Drupal 7 and earlier, we had a number of systems that were pluggable, but they all worked differently. If you wanted to work with blocks, you learned about hook_block_info and hook_block_view (at a minimum). If you wanted to learn about Views or Panels, you leared about the CTools module and its advanced plugin system, etc.
For every system in Drupal, there was an entire lexicon to learn, multiple hooks to leverage, config gotchas and other "unique snowflake" architecture built into the system.

In short, we were in Plugin hell.

http://drupal.org/node/1637614

Drupal 8 The Plugin System

In D8, we have a universal plugin system. Many core components are now discrete objects and many others are still being converted to these "plugins." What started with the Blocks and Views initiatives has spread like a gospel to everything from Fields to Image effects to *gasp* ...even Breadcrumbs!
"You just have to learn one single idea and you can apply it anywhere."
-Daniel Wehner
Drupal 8 wins: unified entities n' plugins!

Drupal 8 Plugins Example Usage

/modules/demo/lib/Drupal/demo/Plugin/Block/DemoBlock.php
/**
 * Provides a demo block.
 *
 * @Block(
 *   id = "demo_block",
 *   admin_label = @Translation("Demo Block")
 * )
 */
class DemoBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return array(
      'text' => 'Default text',
    );
  }
...

Drupal 8 Plugin Annotations

We need to be able to discover plugins automatically. Adding a getInfo() method on each plugin class could be a memory issue, requiring parsing the PHP of every plugin class during discovery.
/**
 * Provides a demo block.
 *
 * @Block(
 *   id = "demo_block",
 *   admin_label = @Translation("Demo Block")
 * )
 */
Enter Doctrine's Annotation mechanism (Patched by chx to use the php tokenizer) Annotations are now available as a mechanism for plugin discovery (replacing info hooks)! https://drupal.org/node/1683644

Questions?

Join #drupal-contribute on irc.freenode.net

@brantwynn

Use a spacebar or arrow keys to navigate