About me
- Developer at Hook 42
- Swimming, cats, and the internet
The Thought Behind the Talk
I had to create a custom module for the first time, and I had only modified code in the past
I didn't want to just know how to make a custom module, I wanted to know the answers to all my why questions
Quick note: sometimes words are mentioned before they are defined. Please have patience.
Do you need a custom module?
- 5,500 + Drupal 8 contrib modules with security updates and help from the Drupal universe
- Might have a unique problem that contrib doesn't solve
- Contrib module might be too bulky and hurt site performance
- Bring in the functionality of many contrib modules into one custom module
We can't just start with the list of files
Nothing is ever that easy.
PSR-4: Improved Autoloading
- PSR stands for PHP Standard Recommendation
- PSR is a PHP specification that is published by the PHP Framework Interop Group (php-fig) who's focus is to move "PHP foward through collaboration and standards"
- Drupal 8 implements the PSR-4 standard for namespaces and autoloading
- PSR-4: "Autoloaders remove the complexity of including files by mapping namespaces to file system paths."
Autoloading
- Autoloading is the capability of loading and linking portions of a program automatically when needed. The autoloader searches through a path of directories and finds the code that defines the subroutine (method/function).
- Autoloading means your code does not need to include a long list of source files in your php file
- The magic of autoloading works because of namespacing
Namespacing
- "Namespaces are commonly structured as hierarchies to allow reuse of names in different contexts." - Wikipedia
- Namespacing helps you avoid name collisions if you repeat the name for a function/class that was declared before
- Eliminates need for super long class names, points to where in the code you can find the function source
Composer
-
Dependency/package manager: software the automates the process of installing, upgrading, configuring and removing software.
-
Composer is a dependency manager for PHP, that assists with locating, downloading, validating, and loading packages and ensures you have the correct package version
Symfony
- Symfony is a PHP framework and a set of reuasable PHP components and libraries
- Defined as a MVC (Model, View, and Controller) framework
- Components are standalone PHP libraries that generate specific features
Some Symfony Components
- ClassLoader: provides tools to autoload classes and cache their locations for performance
- Console: helps you create command line interfaces
- CssSelector: converts CSS selectors to XPath (XML Path language) expression
- DependencyInjection: lets you standardize and centralize the way objects are constructed
- EventDispatcher: lets components communicate with each other
- HttpFoundation: defined an object-oriented layer for the HTTP specification
- HttpKernel: converts a HTTP Request into a HTTP Respose by using a EventDispatcher component
- Process: executes commands in subprocesses, creates API for shell executions
- Routing: maps an HTTP request to a set of configurable variables
- Serializer: turn objects into specific formats (YAML) and specific formats into objects
- Translation: provides tools for internationalization
- Validator: validates following JSR 303: Bean Validation
- Yaml: parses YAML strings to conver them to PHP arrays, and vice versa
Why are you telling me this?
-
Autoloading and namespacing play an important role in how and why we structure files and code in a module
-
While Drupal Core uses Composer, you can also manage dependencies for your custom module with Composer
-
While Drupal 8 does NOT use the Symfony framework, D8 uses some Symfony components
Types of files:
- YAML (YAML Ain't Markup Language): .yml, .yaml
- PHP - Hypertext Preprocessor: .php
- Markdown: .md
- Javascript Object Notation (JSON): .json
- Module: .module
- Javascript: .js
- CSS (Cascading Style Sheets): .css
Naming the module
- Start with a letter
- Use only lower-case letters and underscores
- No spaces
- Unique name
- Does not contain any reserved words: src, lib, vendor, assets, css, files, images, js, misc, templates, includes, fixtures, Drupal
The Module Skeleton
Capable Module
modules
/contrib
/custom
/capable
README.md
STANDARDS.md
TESTING.md
capable.api.php
capable.info.yml
capable.libraries.yml
capable.links.menu.yml
capable.module
capable.permissions.yml
capable.routing.yml
capable.services.yml
capable.task.yml
composer.json
/config
/install
capable.settings.yml
/schema
capable.schema.yml
/css
capable.form.css
capable.miscellaneous.css
capable.module.css
capable.template.css
capable.theme.css
/js
main.js
/src
/Controller
CapableController.php
/Entity
CapableEntity.php
/Form
CapableForm.php
/Plugin
/Block
CapableBlock.php
/templates
welcome.html.twig
/tests
/src
/Functional
ExampleFunctionalTest.php
/Kernel
ExampleKernelTest.php
/Unit
ExampleUnitTest.php
capable.info.yml
- The info.yml file lives in your root folder and tells Drupal about your module
- The info.yml file information can be found on the Drupal admin pages (/admin/modules)
- Technically, the only required file in your module (although the module won't do anything)
capable.info.yml
What's going on?
capable.info.yml
/admin/modules
capable.info.yml complete example
capable.info.yml
-
depedencies:
- List the other module(s) that your module depends on to work.
- Namespacing: {project}:{module}
- {project} is the name as it appears in the Drupal.org url (https://www.drupal.org/project/token)
- {module} is the module machine name (token)
- Can also include version restrictions: token:token (8.x-1.3)
-
test_dependencies:
- List the module(s) you need to run automated tests
- Same namespacing as dependecies: {project}:{module}
-
configure:
- Specify the route to a configuration form
- This will show on the /admin/modules pages when you expand [this needs different words or an image]
-
php:
- Defines the minimum PHP verison that your module requires
- Can't enable the module if your PHP version is older
-
hidden:
- hidden: true, will hide your your module on the /admin/modules page
- Why would you hide your module? Perhaps it only implements tests and you don't want it disabled
The Controller File
- Following PSR-4, the controller file will live inside a folder called src, and then in a folder called Controller
- src stands for source
capable/src/Controller/CapableController.php
- Need to declare a namespace at the top.
- Namespace format: Drupal\[module name]\Controller;
- The controller file will work with the router file
CapableController.php
CapableController.php
-
namespace Drupal\capable\Controller: declaring the namespace for the capable Controller class
- this must be done before any other code (except a declare statement)
-
use Drupal\Core\Controller\ControllerBase: importing (aliasing) the Drupal core ControllerBase, which is being extended by CapableController class.
- Do not include a leading \
- Only specify one class per use statement.
- No clear rule to order for multiple use statements.
- Final use statement should habe a blank line underneath it.
-
class CapableController extends ControllerBase: creation of CapableController class that extends Drupal's core ControllerBase class
-
public function content(): take note of the name of the function, this will come back in the routing file
The Routing File
- A route is a path where Drupal returns some sort of content
- The router determines which code should be run to generate the response.
- The routing system is responsible for matching paths to controllers.
capable/capable.routing.yml
capable.routing.yml
capable.routing.yml
Routing and Controller Working Together
capable.routing.yml
CapableController.php
/hello
Parameters in Routes
- Parameters are used for URLs that may contain dynamic values.
/parameters/[partial] would work for both /parameters/hello and /parameters/goodbye
- The [partial] element in the url is called a slug, and is is avaible as a $partial in the controller method
- You can even have multiple parameters on a single route: product/[productnumber]/[color]
capable.routing.php
CapableController.php
Parameters in Routes
/parameters/hello
/parameters/goodbye
Module Menu Link
Menu link to the module in the Development section of the Administration >> Configuration (/admin/config)
/capable/capable.links.menu.yml
Module Menu Link
capable.links.menu.yml
capable.routing.yml
CapableController.php
Module Menu Link Screenshot
/admin/config
Module Menu Link Flexibility
You can also set the [module].links.menu.yml to link to external links or to an internal path
External Link
capable.links.menu.yml
Internal Link
capable.links.menu.yml
capable.module
-
The capable.module file is optional
-
Used for implementing hooks
Introduction to Hooks
-
Hooking: range of techniques that lets you change the behavior of applications by intercepting events passed between software components.
-
Hook: the code that handles this interception
-
Hooks in Drupal let modules interact with other modules or Drupal core
-
Hooks can be thought of event listeners that fire off when a certain event triggers them, and can be used at various code execution points
-
In Drupal 8, some hooks have remained the same, some have been removed, others have been modified, and some have been added. For the entire list of core hooks, click here.
-
To implement a hook, replace the hook name of the function with the name of your module:
capable.module
capable/capable.module
capable.module
/admin/help/capable
Composer
-
If your module has a dependency to a third party library hosted on Packagist, you can define this dependency
-
MUST declare dependencies in your info.yml file, but composer.json can let you get more specific with constraints
-
If your module does not have any dependencies, composer.json is not required, but having it will not have a negative impact
-
Drupal core will NOT automatically find your custom module composer.json,
Tell root composer.json about your custom module's composer.json
- 1. In root composer run
composer require wikimedia/composer-merge-plugin
- 2. Edit root composer file
root/composer.json
Capable composer.json
- 3. create custom module composer file
capable/composer.json
Composer File
capable/composer.json
Composer Update
- 4. Run
composer update
in your root composer.json
capable/composer.json
Forms
-
Introduction to Form API
-
API: application programming interface is a set of functions and tools for building software with clearly defined methods of communication
-
Need to identify the type of form you need
-
General form: extend Form Base
-
A configuration form that lets administrators update a module settings: extend ConfigFormBase
-
A form for deleting content or configuation with a confirmation step: extend ConfirmFormBase
-
Required methods:
-
getFormId()
-
buildForm()
-
validateForm()
-
submitForm()
CapableForm.php
capable.routing.yml
capable/src/Form/CapableForm.php
CapableForm.php
CapableForm.php
/capable/form
Configuration
-
Configuration is a place to store information you would like to synchronize from development to production. This information can be used in your own plugins, entities and settings.
-
Lives under a /config directory
-
/install directory
-
Configuration defaults live under the /config/install directory
-
capable.settings.yml: This file stores configuration defaults, which are assigned to the correct fields via the next file
-
/schema directory
-
Schema: the organization of the structure of the database
-
capable.schema.yml: define a custom table for your module
/capable/config/install/capable.settings.yml
/capable/config/schema/capable.schema.yml
Configuration
capable.settings.yml
capable.schema.yml
Configuration
capable.routing.yml
CapableContoller.php
Configuration
/module/config
Permissions
-
Permissions are defined in a [modulename].permissions.yml instead of using hook_permission()
-
Static permissions: do not depend on system information
-
Dynamic permissions: generated by system specific information (node type, user role)
/capable/capable.permissions.yml
Static Permission
capable.permissions.yml
/admin/people/permissions
Setting Permissions in Routing
capable.routing.yml
Services
-
A service is any reusable PHP object that does something
-
Managed by the services container
-
Services perform operations like accessing the database, sending email or translating user interface text
-
[modulename].services.yml will live in the root of the module
capable/src/CapableServices.php
capable/capable.services.yml
Services
capable.services.yml
CapableServices.php
Services
capable.routing.yml
CapableController.php
Services
/module/service
Plugins and Custom Blocks
-
Plugins are small pieces of functionality that are swappable
-
Plugins that perform similar functionality are of the same plugin type
-
Blocks are instances of the block plugin.
-
Plugin code must live in /src/Plugin/[plugin_type]/Example.php
-
Blocks: /src/Plugin/Block/*
-
Field formatters, Field widgets: /src/Plugin/Field/*
-
Views plugins: /src/Plugin/views/*
capable/src/Plugin/Block/CapableBlock.php
CapableBlock.php
CapableBlock.php
/admin/structure/block >> Capable Block
Libraries
-
A library is a "collection of implementations of behavior... that has a well-defined interface by which the behavior is invoked." - Wikipedia
-
Define one or more libraries (assets) in [modulename].libraries.yml
-
Must explicitly tell Drupal the libraries you want it to load, including CSS and JS files
-
Drupal 8 no longer loads jQuery on all pages on default, so you must tell Drupal that your library has a dependency on jQuery.
-
Attach the library to a render array in a hook
capable.libraries.yml
capable/capable.libraries.yml
capable.libraries.yml
capable/css/capable.form.css
capable/css/capable.miscellaneous.css
capable/css/capable.module.css
capable/css/capable.template.css
capable/css/capable.theme.css
capable/js/main.js
Globally Load a Library
Attach the library to all pages with hook_page_attachments in capable.module
capable.module
Globally Load a Library
/hello
Load Library to a Form
Attach the library in the render array buildForm function in the CapableForm.php file
$form['#attached']['library'][] = 'module-name/library-name'
CapableForm.php
Load Library to a Form
/capable/form
Load a Library to an element
Attach a library to a specific #type
: the library only loads if that #type exists
CapableForm.php
capable.module
Load a Library to an element
/capable/form
Load a Library to a twig template
{{ attach_library('your_module/library_name') }}
/templates/welcome.html.twig
Load a Library to a twig template
/welcome
CSS
-
All styles should be placed under the css folder
-
Drupal 8 follows SMACSS-style categorization of its CSS rules
-
Base: CSS reset/normalize plus HTML element styling. Weight: -200
-
Layout: macro arrangement of a web page, including any grid systems. Weight: -100
-
Component: discrete, reusable UI elements. Weight: 0
-
State: styles that deal with client-side changes to components. Weight: 100
-
Theme: purely visual styling (“look-and-feel”) for a component. Weight: 200
-
CSS file organization
-
module_name.module.css
: minimal styles needed to get the module's functionality working, includes layout, component and state styles.
-
module_name.theme.css
: extra styles to make the module's functionality aesthetically pleasing, consists of theme styles.
-
module_name.admin.css
: minimal styles needed to get the module's admin screens working, includes layout, component and state styles. On admin screens, the module may choose to load the *.module.css in addition to the *.admin.css file.
-
module_name.admin.theme.css
: extra styles to make the module's admin screens aesthetically pleasing, consists of theme styles.
CSS Weights
capable.libraries.yml
jQuery
/hello
Configurable Javascript
-
Use configurable JavaScript when you use JavaScript that works with computed PHP information
-
Need to attach drupalSettings to the library and attach that library
CapableForm.php
main.js
Configurable Javascript
/capable/form
JavaScript: CDN / Externally hosted libraries
Improves load speed, but will need to declare that the library is external
capable.libraries.yml
Twig Templates
-
Twig is a flexible templating engine for PHP, helps you create custom pages
-
The *.html.twig files have replaced the *.tpl.php files of Drupal 7
-
Lives under /templates folder
-
Steps to create custom twig templates for custom module
-
Define the hook_theme in the .module file
-
Call the template
-
Create the twig template, located in a templates folder
1. Define the hook_theme
capable.module
2. Call the template
CapableController.php
capable.routing.yml
3. Create the twig template
capable/templates/welcome.html.twig
welcome.html.twig
Testing
-
QA your module to make sure its working, tests run by Drupal
-
Tests live under the /tests/src folder and then under its relative folder (Functional, Kernel, Unit)
-
Unit: tests on functions methods and classes. Extend the base class UnitTestCase
-
Kernel: integration test that test on components. Extend the base classes: KernelTestBase and EntityKernelTestBase
-
Functional: test on the complete system. Tutorials for BrowserTestBase and JavascriptTestBase
-
BrowserTestBase: test web-based behaviors and interactions. Spins up a complete Drupal installation and viritual web browser, and tests in the virtual web browser.
-
JavascriptTestBase: tests how the system works for a user with Javascript enabled.
-
Legacy support for Simpletest's WebTestBase framework
Testing
capable/tests/src/Unit/CapableExampleTest.php
CapableExampleTest.php
Run the Tests
-
Enable the Testing module
-
Navigate to admin/config/development/testing
/admin/config/development/testing
-
Select the group name
-
Click Run tests
Documentation
-
README.md: general information about the module including - Table of Contents, Introduction (summary of the module), Requirements, Recommended modules, Installation, Configuration, Troubleshooting, FAQ and Maintainers
-
STANDARDS.md: details coding standards for the module
-
TESTING.md: details of how to test the module
-
capable.api.php: If your module invokes a hook you should document the use-case, and parameters for that hook. File is purely for documentation.
Last but not Least
-
capable.task.yml: local tasks generate the tabs on top of pages. Usually admin pages
-
Entities: objects that are used for persistent storage of content and configuration information
All The Sources
Plugins and Custom Blocks
Questions?