Table of Contents for
Drupal 8 Module Development

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Drupal 8 Module Development by Daniel Sipos Published by Packt Publishing, 2017
  1. Drupal 8 Module Development
  2. Title Page
  3. Copyright
  4. Drupal 8 Module Development
  5. Credits
  6. About the Author
  7. About the Reviewers
  8. www.PacktPub.com
  9. Why subscribe?
  10. Customer Feedback
  11. Table of Contents
  12. Preface
  13. What this book covers
  14. What you need for this book
  15. Who this book is for
  16. Conventions
  17. Reader feedback
  18. Customer support
  19. Downloading the example code
  20. Downloading the color images of this book 
  21. Errata
  22. Piracy
  23. Questions
  24. Developing for Drupal 8
  25. Introducing Drupal (for developers)
  26. Developing for Drupal 8
  27. Technologies that drive Drupal
  28. PHP
  29. Databases and MySQL
  30. The web server
  31. HTML, CSS, and JavaScript
  32. Drupal architecture
  33. Drupal core, modules, and themes
  34. Hooks, plugins, and events
  35. Services and the dependency injection container
  36. From request to response
  37. Drupal's major subsystems
  38. Routing
  39. Entities
  40. Fields
  41. Menus
  42. Views
  43. Forms
  44. Configuration
  45. Plugins
  46. The theme system
  47. Caching
  48. Other subsystems
  49. Tools for developing in Drupal
  50. Version control
  51. Composer
  52. The API site and coding standards
  53. The developer (Devel) module
  54. Drush (the Drupal shell)
  55. Drupal Console
  56. Developer settings
  57. Summary
  58. Creating Your First Module
  59. Creating a module
  60. Your first hook implementation
  61. Route and controller
  62. The route
  63. Route variables
  64. Namespaces
  65. The Controller
  66. Services
  67. What is a service?
  68. The HelloWorldSalutation service
  69. Tagged services
  70. Using services in Drupal 8
  71. Injecting the service into our Controller
  72. The form
  73. Altering forms
  74. Custom submit handlers
  75. Rendering forms
  76. Service dependencies
  77. Blocks
  78. Our first block plugin
  79. Block configuration
  80. Working with links
  81. The URL
  82. The link
  83. Which way to link?
  84. Event Dispatcher and redirects
  85. Redirecting from a Controller
  86. Redirecting from a subscriber
  87. Dispatching events
  88. Summary
  89. Logging and Mailing
  90. Logging
  91. The Drupal 8 logging theory
  92. Our own logger channel
  93. Our own logger
  94. Logging for Hello World
  95. Logging summary
  96. Mail API
  97. The theory of the Mail API
  98. Implementing hook_mail()
  99. Sending emails
  100. Altering someone else's emails
  101. Custom mail plugins
  102. The mail plugin
  103. Using mail plugins
  104. Tokens
  105. The Token API
  106. Using tokens
  107. Defining new tokens
  108. Token summary
  109. Summary
  110. Theming
  111. Business logic versus presentation logic
  112. Twig
  113. Theme hooks
  114. Theme hook suggestions
  115. Render arrays
  116. The structure of a render array
  117. #type
  118. #theme
  119. #markup
  120. The render pipeline
  121. Assets and libraries
  122. Libraries
  123. Attaching libraries
  124. Common theme hooks
  125. Lists
  126. Links
  127. Tables
  128. Attributes
  129. Theming our Hello World module
  130. Summary
  131. Menus and Menu Links
  132. The menu system
  133. Menus
  134. Menu links
  135. Multiple types of menu links
  136. Local tasks
  137. Local actions
  138. Contextual links
  139. MenuLink trees
  140. Menu link tree manipulators
  141. Menu active trail
  142. Rendering menus
  143. Working with menu links
  144. Defining menu links
  145. Working with menu links
  146. Defining local tasks
  147. Defining local actions
  148. Defining contextual links
  149. Summary
  150. Data Modeling and Storage
  151. Different types of data storage
  152. State API
  153. Tempstore
  154. PrivateTempStore
  155. A note about anonymous users
  156. SharedTempStore
  157. Tempstore conclusion
  158. UserData
  159. Configuration
  160. Introduction
  161. What is configuration used for?
  162. Managing configuration
  163. Different types of configuration
  164. Configuration storage
  165. Schema
  166. Overrides
  167. Global overrides
  168. Module overrides
  169. Language overrides
  170. Priority
  171. Interacting with simple configuration
  172. Entities
  173. Content versus configuration entity types
  174. Entity type plugins
  175. Identifiers
  176. Bundles
  177. Database tables
  178. Entity keys
  179. Links
  180. Entity translation
  181. Entity revisions
  182. Configuration export
  183. Handlers
  184. Fields
  185. Configuration entity fields
  186. Content entity fields
  187. Base fields
  188. Configurable fields
  189. Field storage
  190. Entity types summary
  191. TypedData
  192. Why?
  193. What?
  194. The low-level API
  195. DataType plugins
  196. Data definitions
  197. Content entities
  198. TypedData summary
  199. Interacting with the Entity API
  200. Querying and loading entities
  201. Building queries
  202. Loading entities
  203. Reading entities
  204. Manipulating entities
  205. Creating entities
  206. Rendering content entities
  207. Pseudo-fields
  208. Content entity validation
  209. Validation summary
  210. Summary
  211. Your Own Custom Entity and Plugin Types
  212. Custom content entity type
  213. Custom plugin type
  214. Custom configuration entity type
  215. The Importer plugin
  216. Content entity bundles
  217. Drush command
  218. Summary
  219. The Database API
  220. The Schema API
  221. Running queries
  222. Select queries
  223. Handling the result
  224. More complex select queries
  225. Range queries
  226. Pagers
  227. Insert queries
  228. Update queries
  229. Delete queries
  230. Transactions
  231. Query alters
  232. Update hooks
  233. Summary
  234. Custom Fields
  235. Field type
  236. Field widget
  237. Field formatter
  238. Field settings
  239. Using as a base field
  240. Summary
  241. Access Control
  242. Introduction to the Drupal access system
  243. Roles and permissions under the hood
  244. Defining permissions
  245. Checking the user credentials
  246. Route access
  247. Custom route access
  248. Static approach
  249. Service approach
  250. Programmatically checking access on routes
  251. Bonus - dynamic route options for access control
  252. CSRF protection on routes
  253. Altering routes
  254. Entity access
  255. Injecting services into Entity handlers
  256. Entity access hooks
  257. Field access
  258. Entity access in routes
  259. Node access grants
  260. Block access
  261. Summary
  262. Caching
  263. Introduction
  264. Cacheability metadata
  265. Cache tags
  266. Cache contexts
  267. Max-age
  268. Using the cache metadata
  269. Caching in block plugins
  270. Caching access results
  271. Placeholders and lazy building
  272. Lazy builders
  273. Using the Cache API
  274. Creating our own cache bin
  275. Summary
  276. JavaScript and the Ajax API
  277. JavaScript in Drupal
  278. Drupal behaviors
  279. Our library
  280. The JavaScript
  281. Drupal settings
  282. Ajax API
  283. Ajax links
  284. Ajax in forms
  285. States (Form) system
  286. Summary
  287. Internationalization and Languages
  288. Introduction
  289. Language
  290. Content Translation
  291. Configuration Translation
  292. Interface Translation
  293. Internationalization
  294. Content entities and the Translation API
  295. Summary
  296. Batches, Queues, and Cron
  297. Batch powered update hooks
  298. Batch operations
  299. Creating the batch
  300. Batch operations
  301. Cron
  302. Queues
  303. Introduction to the Queue API
  304. Cron based queue
  305. Processing a queue programmatically
  306. Lock API
  307. Summary
  308. Views
  309. Entities in Views
  310. Exposing custom data to Views
  311. Views data
  312. Views fields
  313. Views relationships
  314. Views sorts and filters
  315. Views arguments
  316. Altering Views data
  317. Custom Views field
  318. Field configuration
  319. Custom Views filter
  320. Custom Views argument
  321. Views theming
  322. Views hooks
  323. Summary
  324. Working with Files and Images
  325. The filesystem
  326. Stream wrappers
  327. Managed versus unmanaged files
  328. Using the File and Image fields
  329. Working with managed files
  330. Attaching managed files to entities
  331. Helpful functions for dealing with managed files
  332. Managed file uploads
  333. Managed file form element
  334. Entity CRUD hooks
  335. Managed file usage service
  336. Processing the CSV file
  337. Our own stream wrapper
  338. Working with unmanaged files
  339. Private file system
  340. Images
  341. Image toolkits
  342. Image styles
  343. Rendering images
  344. Summary
  345. Automated Testing
  346. Testing methodologies in Drupal 8
  347. PHPUnit
  348. Registering tests
  349. Unit tests
  350. Mocked dependencies
  351. Kernel tests
  352. TeamCleaner test
  353. CsvImporter test
  354. Functional tests
  355. Configuration for functional tests
  356. Hello World page test
  357. Hello World form test
  358. Functional JavaScript tests
  359. Time test
  360. CsvImporter test
  361. Summary
  362. Drupal 8 Security
  363. Cross-Site Scripting (XSS)
  364. Sanitization methods in Drupal 8
  365. Double escaping
  366. SQL Injection
  367. Cross-Site Request Forgery (CSRF)
  368. Summary

Field type

The primary plugin type for creating a field is, as we discussed, the FieldType. It is responsible for defining the field structure, how it is stored in the database, and various other settings. Moreover, it also defines a default widget and formatter plugin that will be autoselected when we create the field in the UI. You see, a single field type can work with more than one widget and formatter. If more exist, the site builder can choose one when creating the field and adding it to an entity type bundle.

Otherwise, it will be the default; each field needs one because without a widget, users can't add data, and without a formatter, they can't see it. Also, as you'd expect, widgets and formatters can also work with more than one field type.

The field we will create in this section is for the license plate data, which, as we saw, needs two individual pieces of information--a code (such as the state code) and the number. License plates around the world are more complex than this, but I chose this example to keep things simple.

Our new FieldType plugin needs to go inside the Plugin/Field/FieldType namespace of a new module we will create called license_plate. Although not mandatory, the class name should end with the word Item. It's a pretty standard thing done in Drupal core, and we will follow suit. So, let's take a look at our LicensePlateItem plugin implementation and then talk about its code:

namespace Drupal\license_plate\Plugin\Field\FieldType;

use Drupal\Component\Utility\Random;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinition;

/**
 * Plugin implementation of the 'license_plate_type' field type.
 *
 * @FieldType(
 *   id = "license_plate",
 *   label = @Translation("License plate"),
 *   description = @Translation("Field for creating license plates"),
 *   default_widget = "default_license_plate_widget",
 *   default_formatter = "default_license_plate_formatter"
 * )
 */
class LicensePlateItem extends FieldItemBase {}

I omitted the class contents, as we will be adding the methods one by one and discussing them individually. However, first, we have the plugin annotation, which is very important. We have the typical plugin metadata such as the ID, label, and description and also will indicate the plugin IDs for the widget and formatter that will be used by default with this field type. Make a note of those, because we will create them soon.

Speaking from experience, often, when creating a field type, you'll extend the class of an already existing field type plugin, such as a text field or an entity reference. This is because Drupal core already comes with a great set of available types, and, usually, all you need is either to make some tweaks to an existing one or maybe combine them or add an extra functionality.

This makes things easier, and you don't have to copy and paste code or come up with it again yourself. Naturally, though, at some point, you'll be extending from FieldItemBase because that is the base class all field types need to extend from.

In our example, however, we will extend straight from the FieldItemBase abstract class because we want our field to stand on its own. Also, it's not super practical to extend from any existing ones in this case. That is not to say, though, that it doesn't have commonalities with other field types, such as TextItem, for example.

Let's now take a look at the first method:

/**
 * {@inheritdoc}
 */
public static function defaultStorageSettings() {
  return [
    'number_max_length' => 255,
    'code_max_length' => 5,
  ] + parent::defaultStorageSettings();
}

The first thing we do in our class is override the defaultStorageSettings() method. The parent class method returns an empty array; however, it's still a good idea to include whatever it returns to our own array. If the parent method changes and returns something later on, we are a bit more robust.

The purpose of this method is two-fold--specify what storage settings this field has and set some defaults for them. Also, note that it is a static method, which means that we are not inside the plugin instance. However, what are storage settings, you may ask?

Storage settings are the configuration that applies to the field everywhere it's used. As you may know, a field can be added to multiple bundles of an entity type. In Drupal 7, you could reuse a field even across entity types, but this is no longer possible, so fields are now reusable only on the bundles of a single entity type. You'll need to create another field of that type if you need it on some other content entity type. So, the storage settings are those that apply to this field across each bundle it is attached to.

They usually deal with things related to the schema--how the database table columns are constructed for this field but they also deal with a lot of other things. Also, even more important to know is that once there is data in the field tables, they cannot be changed. It makes sense, as you cannot easily change database tables when there is data in them. This restriction is something we enforce, as we will see in a bit.

In our example, we only have two storage settings--number_max_length and code_max_length. These will be used when defining the schema for the two-table columns, where the license plate data will be stored (as the maximum length that can be stored in those table fields). By default, we will go with the ever-so-used 255 maximum length on the number column and 5 for the code column, but these are just defaults. The user will be able to change them when creating the field or when editing, as long as there is no data yet.

Next, we can write our storage settings form, which allows users to provide the actual settings when creating a field:

/**
 * {@inheritdoc}
 */
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
  $elements = [];

  $elements['number_max_length'] = [
    '#type' => 'number',
    '#title' => t('Plate number maximum length'),
    '#default_value' => $this->getSetting('number_max_length'),
    '#required' => TRUE,
    '#description' => t('Maximum length for the plate number in characters.'),
    '#min' => 1,
    '#disabled' => $has_data,
  ];

  $elements['code_max_length'] = [
    '#type' => 'number',
    '#title' => t('Plate code maximum length'),
    '#default_value' => $this->getSetting('code_max_length'),
    '#required' => TRUE,
    '#description' => t('Maximum length for the plate code in characters.'),
    '#min' => 1,
    '#disabled' => $has_data,
  ];

  return $elements + parent::storageSettingsForm($form, $form_state, $has_data);
}

The preceding method is called by the main field configuration form, and we need to return an array of form elements from it that can be used to set values to the storage settings we defined earlier. We have access to the main $form and $form_state of the form where this is embedded, as well as a handy Boolean $has_data, which tells us whether there is already any data in this field. We use this to disable the elements we don't want to be changed if there is data in the field (in our case, both).

So, basically, our form consists of two number form elements (both required), whose values default to the lengths we specified earlier. The number form element also comes with #min and #max properties, which we can use to restrict the number to a range. Also, we obviously want our minimum lengths to be a positive number, that is, above 1. This method is relatively straightforward to understand if you get the basics of the Form API, which you should by now.

Finally, for our storage handling, we will need to implement the schema method and define our table columns:

/**
 * {@inheritdoc}
 */
public static function schema(FieldStorageDefinitionInterface $field_definition) {
  $schema = [
    'columns' => [
      'number' => [
        'type' => 'varchar',
        'length' => (int) $field_definition->getSetting('number_max_length'),
      ],
      'code' => [
        'type' => 'varchar',
        'length' => (int) $field_definition->getSetting('code_max_length'),
      ],
    ],
  ];

  return $schema;
}

This is another static method, but one that receives the current fields's FieldStorageDefinitionInterface instance. From there, we can access the settings the user has saved when creating the field, and based on those, we define our schema. If you've been paying attention in the preceding chapter, when we discussed hook_schema(), this should already be very clear to you. What we need to return is an array of column definitions keyed by their name. So, we define two columns of the varchar type with the maximum lengths the user has configured--no rocket science involved. Of course, we could have had more storage settings and had this schema definition even more configurable if we'd wanted to.

With these three methods, our storage handling is complete; however, our field type is not quite. We still have a couple more things to take care of.

Apart from storage, as we discussed, fields also deal with data representation at the code level with TypedData structures. So, our field type needs to define its individual properties for which we create storage. For this, we have two main methods--first, to actually define the properties, and then, to set some potential constraints on them:

/**
 * {@inheritdoc}
 */
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['number'] = DataDefinition::create('string')
    ->setLabel(t('Plate number'));

  $properties['code'] = DataDefinition::create('string')
    ->setLabel(t('Plate code'));

  return $properties;
}

The preceding code will look very familiar to the one in Chapter 6, Data Modeling and Storage, when we talked about TypedData. Again, this is a static method, which needs to return the DataDefinitionInterface instance for the individual properties. We choose to call them number and code, respectively, and set some sensible labels--nothing too complicated.

The preceding code is actually enough to define the properties, but if you remember, our storage has some maximum lengths in places-meaning that the table columns are only so long. So, if the data that gets into our field is longer, the database engine will throw a fit in a not so graceful way. In other words, it will throw a big exception, and we can't have that. So, there are two things we can do to prevent that--put the same maximum length on the form widget to prevent users from inputting more than they should and add a constraint on our data definitions. The second one is more important because it ensures that the data is valid in any case, whereas the first one only deals with forms. However, since Drupal 8 is so much more API oriented, if we create an entity programmatically and set its field values, we bypass forms completely. However, not to worry; we will also take care of the form, so our users can have a nicer experience and are aware of the maximum size of the values they need to input.

So, let's add the following constraints:

/**
 * {@inheritdoc}
 */
public function getConstraints() {
  $constraints = parent::getConstraints();
  $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
  $number_max_length = $this->getSetting('number_max_length');
  $code_max_length = $this->getSetting('code_max_length');
  $constraints[] = $constraint_manager->create('ComplexData', [
    'number' => [
      'Length' => [
        'max' => $number_max_length,
        'maxMessage' => t('%name: may not be longer than @max characters.', [
          '%name' => $this->getFieldDefinition()->getLabel() . ' (number)',
          '@max' => $number_max_length
        ]),
      ],
    ],
    'code' => [
      'Length' => [
        'max' => $code_max_length,
        'maxMessage' => t('%name: may not be longer than @max characters.', [
          '%name' => $this->getFieldDefinition()->getLabel() . ' (code)',
          '@max' => $code_max_length
        ]),
      ],
    ],
  ]);

  return $constraints;
} 

Since our field class actually implements the TypedDataInterface, it also has to implement the getConstraints() method (which the TypedData parent already starts up). However, we can override it and provide our own constraints based on our field values.

We are taking a slightly different approach here of adding constraints than what we've seen in Chapter 6, Data Modeling and Storage. Instead of adding them straight to the data definitions, we will create them manually using the validation constraint manager (which is the plugin manager of the Constraint plugin type we saw in Chapter 6, Data Modeling and Storage). This is because fields use a specific ComplexDataConstraint plugin, which can combine the constraints of multiple properties (data definitions). Do note that even if we had only one property in this field, we'd still be using this constraint plugin.

There aren't many type of classes in Drupal 8 in which you cannot inject dependencies, but FieldType plugins are one of them. So, for this reason, we have to request the services we need statically in this case. This is because these plugins are actually built on top of the Map TypedData plugin, and their manager doesn't use a container-aware factory for instantiation but instead delegates it to the TypedDataManger service, which, as we saw, is not container aware either.

The data needed to create this constraint plugin is a multidimensional array keyed by the property name, which contains constraint definitions (as we saw in Chapter 6, Data Modeling and Storage) for each of them. So, we have a Length constraint for both properties, whose options denote a maximum length and a corresponding message if exceeded. If we wanted, we could have had a minimum length in the same way also--min and minMessage. As for the actual length, we will use the values chosen by the user when creating the field (the storage maximum). Now, regardless of the form widget, our field will not validate unless the maximum lengths are respected.

It's time to finish this class with the following last two methods:

/**
 * {@inheritdoc}
 */
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
  $random = new Random();
  $values['number'] = $random->word(mt_rand(1, $field_definition->getSetting('number_max_length')));
  $values['code'] = $random->word(mt_rand(1, $field_definition->getSetting('code_max_length')));
  return $values;
}

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  // We consider the field empty if either of the properties is left empty.
  $number = $this->get('number')->getValue();
  $code = $this->get('code')->getValue();
  return $number === NULL || $number === '' || $code === NULL || $code === '';
}

With generateSampleValue(), we create some random words that fit within our field--that's it. This can be used when profiling or site building for populating the field with values. Arguably, this is not going to be your top priority, but it is good to know.

Finally, we have the isEmpty() method, which is used to determine whether the field has values or not. It may seem pretty obvious, but it's an important method, especially for us, and you can probably deduce from the implementation why. When creating the field in the UI, the user can specify whether it's required or not. However, typically, that applies (or should apply) to the entire set of values within the field. Also, if the field is not required, and the user only inputs a license plate code without a number, what kind of useful value is that to save? So, we want to make sure that both of them have something before even considering this field as having a value (not being empty), and that is what we are checking in this method.

Now that we are finished with the actual plugin class, there is one last thing that we need to take care of, something that we tend to forget, myself included--configuration schema. Our new field is a configurable field whose settings are stored--guess where?--in configuration. Also, as you remember, all configuration needs to be defined by a schema. Drupal already takes care of those storage settings that come from the parent. However, we need to include ours. So, let's create the typical license_plate.schema.yml (inside config/schema), where we will put the definitions for the field type and any others that we will need in this module.

Then, we can have the following code inside it:

field.storage_settings.license_plate_type:
  type: mapping
  label: 'License plate storage settings'
  mapping:
    number_max_length:
      type: integer
      label: 'Max length for the number'
    code_max_length:
      type: integer
      label: 'Max length for the code'

The actual definition will already be familiar, so the only thing that is interesting to explain is its actual naming. The pattern is field.storage_settings.[field_type_plugin_id]. Drupal will dynamically read the schema and apply it to the settings of the actual FieldStorageConfig entity being exported.

That's it for our FieldType plugin. When creating a new field of this type, we have the two storage settings we can configure (which will be disabled when editing if there is actual field data already in the database):

Unless we work only programmatically or via an API to manage the entities that use this field, it won't really be useful, as there are no widgets or formatters it can work with. So, we will need to create those as well.