Drupal 8 module development Lakshmi Narasimhan This book is for sale at http://leanpub.com/drupal8book at http://leanpub.com/drupal8book This version was published on 2015-01-29
This is a Leanpub a Leanpub book. book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean process. Lean Publishing is Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do.
This work is licensed under a Creative a Creative Commons Attribution 3.0 Unported License
Tweet This Book! Please help Lakshmi Narasimhan by spreading the word about this book on Twitter! The suggested tweet for this book is: I just bought Drupal 8 module development by @lakshminp The suggested hashtag for this book is #drupal8book. Find out what other people are saying about the book by clicking on this link to search for this hashtag on Twitter: https://twitter.com/search?q=#drupal8book
Contents Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cover image attribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
i i
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ii
Basic concepts . . . . . . . . . Drupal 8 directory structure Classes and OOP . . . . . . Namespaces . . . . . . . . . Using Drupalconsole . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
1 1 1 1 1
Service Containers and Dependency Injection What is a service? . . . . . . . . . . . . . . . Example service . . . . . . . . . . . . . . . . Tagged services . . . . . . . . . . . . . . . . Services used in core . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
3 3 3 3 3
Configuration management . . . . . . . . Variables RIP . . . . . . . . . . . . . . . What parts of your site are configurable? Example configuration . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
5 5 5 5
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
6 6 6 6 6
Designing forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The forms class hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating custom forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7 7 7
Writing plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . When should you write a plugin? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . What constitutes a plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8 8 8
Routing and Controllers Routing . . . . . . . . Dynamic routes . . . . Controllers . . . . . . . AJAX using routing . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
CONTENTS
The plugin annotation system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Plugins used in core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Example plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Entities . . . . . . . . . . . . . . . . . . . . Creating custom entites . . . . . . . . . . Entity CRUD hooks . . . . . . . . . . . . Storing entity information in annotation Entities used in core . . . . . . . . . . . . Example entity . . . . . . . . . . . . . .
8 8 8
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. 10 . 10 . 10 . 10 . 10
Building views . . . . . . . . . . . . . . . . . . . . . . . . Data integration: exposing your module to views . . . . Data handlers: field, filter and argument views handlers Other views plugins . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. 11 . 11 . 11 . 11
RESTify your data . . . . . . . . . . . . . . Why REST? . . . . . . . . . . . . . . . . core and contrib modules related to REST Headless Drupal . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. 12 . 12 . 12 . 12
Testing . . . . . PHPUnit . . . Simpletest . . Example code
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. 13 . 13 . 13 . 13
Exploring Drupal 8’s multilingual capabilities . . . Core modules related to language and translation . Creating multilingual content . . . . . . . . . . . Using translation APIs . . . . . . . . . . . . . . . Config and Content translation . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. 14 . 14 . 14 . 14 . 14
Modelling data with field API Specifying the field type . . Field formatter plugin . . . . Creating a field widget . . . Example field . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
9 9 9 9 9 9
Appendix A: Using the migrate module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 How migration is different in Drupal 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Example migration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Appendix B: List of YAML files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 services.yml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 info.yml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
CONTENTS
routing.yml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . libraries.yml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . settings.yml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16 16 18
Acknowledgments Cover image attribution “Electric Sphere¹” by spettacolopuro² is licensed under CC BY 2.0³ Drupal is a registered trademark of Dries Buytaert. ¹https://www.flickr.com/photos/spettacolopuro/3842161463 ²https://www.flickr.com/photos/spettacolopuro/ ³http://creativecommons.org/licenses/by/2.0/
Introduction
Basic concepts Drupal 8 directory structure Classes and OOP Namespaces Using Drupalconsole Though Drupal 8 is technically advanced compared to its predecessor, writing a module involves a lot of boilerplate code. There w many gotcha moments when you will forget to add a namespace and get puzzling errors. Fortunately, all that code can be generated automatically using a tool called the Drupal Console⁴. Drupal Console is another cool addition to the Proudly Found Elsewhere⁵ school of thought as it leverages the Symfony Console⁶ component to handle the CLI part. Installing
Note that Drupal Console supports only Drupal 8.0.0-beta4 at the time of writing this. Get the latest version: 1
$ curl -LSs http://drupalconsole.com/installer | php
Move it to somewhere convenient so that it can be used throughout the system: 1
$ mv console.phar /usr/local/bin/drupal
Go to the drupal root directory of any Drupal 8 beta4 setup and run drupal list to get something like:
⁴http://drupalconsole.com/ ⁵https://prague2013.drupal.org/session/not-invented-here-proudly-found-elsewhere-drupal-8-story ⁶http://symfony.com/doc/current/components/console/index.html
2
Basic concepts 1
d8$ drupal list
2
Drupal version Drupal Console 0.5.2 - prod
3 4 Usage: 5
[options] command [arguments]
6
...
7 router 8 router: debug
Displays current routes for an application
9 router:rebuild
Rebuild routes
Usage
Drupal Console currently supports generating PSR-4 compliant code for plugins, controllers, modules, services, entities and forms.It also has basic debugging commands for listing current configuration and routes.
Service Containers and Dependency Injection What is a service? A service is a fancy name for a reusable PHP object in your code. A f lag service in flag module can be used to manage flags across all entities. An Alias uniquifier service(in pathauto module) is used to create a unique path alias. A service container is a PHP object that dictates how your service object is constructed. Drupal 8’s service container is built on top of Symfony 2 service container. Why do we need a service container? Why can’t we instantiate the service opject directly whenever we need it? For the simple reason that it makes our code tightly coupled and less reusable. The service container is a global object created by the Kernel(another Symfony 2 component) before every request. A popular method of service instantiation by service container is via dependency injection. If you want towrite a service for your module, you will have to create a .services.yml file and the service class. Here’s how you can generate scaffolding for your service using Drupal Console: 1
d8$ drupal generate: service
It is always a good practice to write a service which does only one thing and does it really well, akin to the UNIX tools philosophy.
Example service Tagged services Services used in core Many services in core are replacements of global variables which existed in Drupal 7. The most notorious example of global variable usage was the $user case. In Drupal 7, one could write
Service Containers and Dependency Injection 1 global $user; 2
if ($user->uid == 0) {
//user is not logged in
3 4
}
The Drupal 8 way would be: 1
if (\Drupal::currentUser()->isAuthenticated() == 0) {
//user is not logged in
2 3
}
Why globals are evil? Global variables are a very clumsy way to write code, not just in Drupal but anywhere in general. They can be mutated accidentally and these mutations are hard to track down.
4
Configuration management Variables RIP What parts of your site are configurable? Example configuration
Routing and Controllers Routing Dynamic routes Controllers AJAX using routing
Designing forms The forms class hierarchy Creating custom forms
Writing plugins When should you write a plugin? What constitutes a plugin The plugin annotation system Plugins used in core Example plugin
Entities Creating custom entites Entity CRUD hooks Storing entity information in annotation Entities used in core Example entity
Modelling data with field API Specifying the field type Field formatter plugin Creating a field widget Example field
Building views Data integration: exposing your module to views Data handlers: field, filter and argument views handlers Other views plugins
RESTify your data Why REST? core and contrib modules related to REST Headless Drupal
Testing PHPUnit Simpletest Example code
Exploring Drupal 8’s multilingual capabilities Core modules related to language and translation Creating multilingual content Using translation APIs Config and Content translation
Appendix A: Using the migrate module How migration is different in Drupal 8 Example migration
Appendix B: List of YAML files services.yml info.yml routing.yml libraries.yml One of the things you are likely to do if you write a custom module or a theme is include third party Javascript and/or CSS assets in it. Previously, this used to be a clumsy hook_library_info() array but is replaced by a YML file in D8. It makes asset management look more organized and easier to edit. Let’s see how to do this for the colorbox module. The new YML file will have the naming convention modulename.libraries.yml. So, ours will be colorbox.libraries.yml and will reside in the top level of the module directory like all other YML files. Each entry has a unique name and a set of properties. 1 colorbox: 2
version: VERSION
3
js:
4 5
js/colorbox.js: {}
dependencies:
6
- core/jquery
7
- core/drupal
Here, colorbox is the name of the asset bundle to be included. This identifier will be used to refer the asset in the module or another yml file. The js property lists all the js files needed for the asset. The {} next to it can be used for specifying metadata like cache, preprocess, minify and weight. The dependencies are the assets list which colorbox expects to be loaded. The core/jquery means the jquery asset present in core.libraries.yml. Here’s how the jquery entry looks in core.libraries.yml:
Appendix B: List of YAML files
17
1 jquery: 2
remote: https://github.com/jquery/jquery
3
version: 2.1.0
4
license:
5
name: MIT
6
url: https://github.com/jquery/jquery/blob/2.1.0/MIT-LICENSE.txt
7
gpl-compatible: true
8
js:
9
assets/vendor/jquery/jquery.js: { weight: -20 }
Another example entry from core.libraries.yml: 1 classList: 2
remote: https://github.com/eligrey/classList.js
3
# @todo Stable release required for Drupal 8.0.
4
version: master
5
license:
6
name: Public Domain
7
url: https://github.com/eligrey/classList.js/blob/master/LICENSE.md
8
gpl-compatible: true
9
js:
10 11
assets/vendor/classList/classList.min.js: { weight: -21, browsers: { IE: 'lt\ e IE 9', '!IE': false }, minified: true }
Note that it is possible to add comments in YML files(They start with a “#”). Likewise, the core/drupal dependency needs to be put up if the respective js exposes a Drupal setting. Colorbox itself listed as a dependency in colorbox.libraries.yml for a theme variant: 1 stockholmsyndrome: 2
version: VERSION
3
js:
4 5
styles/stockholmsyndrome/colorbox_style.js: {}
6
7 8 9
css: theme: styles/stockholmsyndrome/colorbox_style.css: {}
dependencies: - colorbox/colorbox
10
- core/jquery
11
- core/drupal
18
Appendix B: List of YAML files
Including the assets in a page
The colorbox assets declared above can be included in a page by implementing the hook_page_ attachments hook. 1 function colorbox_page_attachments( &$page) { 2 3
...
$page['#attached']['library'][] = 'colorbox/colorbox';
Configurables can be passed from Drupal/PHP domain to js using the drupalSettings key. 1
$js_settings = array(
2
' opacity' => ' 0.85',
3
'current' => t('{current} of {total}'),
4
'previous' => t(' « Prev'),
5
'next' => t('Next
6
'close' => t(' Close'),
7
'maxWidth' => ' 98%',
8 9
»'),
); $page['# attached'][' drupalSettings']['colorbox'] = $js_settings;
Much of libraries.yml functionality overlaps with the hook_libraries_info of libraries module. The same thing can be accomplished by implementing the libraries_info hook, downloading colorbox jquery release in the libraries directory and calling: 1 libraries_load('colorbox', $ variant);
In Drupal 7, this is the de facto way of handling third party js assets. Why 2 ways to do the same thing?
The libraries module is also ported to Drupal 8⁷ and can technically do the same thing, but offers 2 advantages: • The same asset can be used by more than 1 module. For example, colorbox jquery library can be used by both colorbox module and a custom theme. • Libraries module also facilitates loading of third party assets written in PHP.
settings.yml ⁷https://www.drupal.org/node/1775738