Anatomy of a Drupal 8 Module: Part 2. Code.

Now that we have had an anatomic overview of Drupal 8's Ban module, we can proceed further to have a closer look at it's code. In this second part, we will have a look at YAML, and the concepts of services, subclassing, autoloading, and the dependency injection.

1. YML (YAML) Files.

In Drupal 7, configuration was somewhat messy. First of all, the term itself was somewhat blurred, and it was not always clear, what is config and what is content, especially when content id's were used in config. Configuration itself was scattered - besides the variables table, modules would create their own tables to store settings, or use yet some other means. The problem there was obvious - if you wanted to export/import configuration to move it between development and development, or between development and production servers, you faced a very hard task, that was most frequently solved with the use of the Features module. Still, themes and modules provided config in their .info files. Over time, the info files became so complex, that they were threatening to become their own language with Drupal.

In Drupal 8, configuration is centralized. Why create a new config language, when it already exists? YAML is that language. YAML is a serialization language, which means, that you can write in it data, which will be converted into a PHP array easily. If you see me mention the yml files here, you should know, that I actually mean YAML. YAML config is stored in files that have extension of *.yml. A common Drupal 8 class manages all config, which is much healthier than before. Config in Drupal 8 is copied into database for better performance, but it is easily imported and exported with the help of the new inport and export interface via the UI.

Another benefit of YAML config is that if allows taking config from hooks into separate yml files, separating config and function to a hitherto unachievable degree. Developers from other platforms will be able to understand Drupal 8 config, because YAML is widely known.

So, you can read through the Ban module yml files and see how they translate into config and what they do. You can also ready more about the configuration schema and metadata, as it is used in Drupal 8.

2. Autoloading.

Autoloading is a relatively simple concept. The idea is that when you define classes in your YAML files, Drupal needs to find and load them. Each class is placed in a php file with the corresponding name and in at corresponding path. The usual path for Drupal source code classes is the module's src folder. When classes are placed and named correctly, they will be automatically loaded. The current standard employed by Drupal 8 is PSR-4. Autoloading is a widely used practice, and PSR-4 is a widely recognized standard, that brinks Drupal closer to the common IT development world standards.

3. OOP in PHP 5.

Almost all concepts in this tutorial relate to OOP, or Object Orient Programming. OOP is a standard concept in the most of the programming world, but in PHP, and especially, in Drupal, this concept was long delayed. Now, with the transition between Drupal 7 and Drupal 8, these concepts have been massively introduced.

If you are not well versed in PHP's OOP implementation, please learn it before you continue with this article. I recommend the following sources:

  1. PHP OOP Docs
  2. Getting Started with OOP & PHP5
  3. OOP Basics in PHP 5

4. Namespaces.

Namespaces is a OOP concept that helps avoid concept in object naming. You can find an easy PHP namespaces explanation in the PHP Namespaces Explained tutorial. If you open the BanIpManager.php file, you will notice, that it has two declarations of namespaces:

<?php
namespace Drupal\ban;
use
Drupal\Core\Database\Connection;
?>

The namespace declares a new namespace Drupal\ban. Custom namespaces should be within the Drupal namespace.
The use keyword imports the use of a database connection namespace Drupal\Core\Database\Connection, so that Ban module can have access to the database classes.

5. Subclassing.

Subclassing is a very important OOP practice that I wanted to stress for it's importance. If you look at almost all of the classes within the Ban module, you will see, that each class either extends some base class or implements an interface.

By extending the base class, the child class inherits it's functionality.

<?php
class BanAdmin extends FormBase {
...
}
?>

In the BanAdmin.php file, the BanAdmin class extends the FormBase class, thus, it inherits all the functionality of the Drupal's standard form base class, building on top of it.

By implementing and interface, a class declares, that it inherits the functions that the interface declares. Drupal has interfaces for kernel, database access, and many many other things. By requiring classes to inherit, interfaces make sure that you have all the required function present in your code, fulfilling the functional requirements.

<?php
class BanIpManager implements BanIpManagerInterface {
...
}
?>

In the BanIpManager.php file, the BanIpManager class implements the BanIpManagerInterface interface, which was declared in the BanIpManagerInterface.php file. Why was it done this way? So that other developers could also write their custom implementations of the BanIpManagerInterface intrface.

If you look carefully, you will see, that a lot of Drupal 8's code thus extents some core classes or implements some core interfaces. Understanding this helps to understand the code immensely.

6. Service Container and Services.

Services are object that posess certain functionality. They are registered at the Drupal's Service Container, and can be used, reused, substituted for, and are also pluggable.

File ban.services.yml declares two services: ban.ip_manager and ban.middleware. First serves to manage the ban IP's, and the second to subclass the Drupal kernel and implement the actual banning.

When a path is navigated, Drupal's Service Container checks it's registry to see which services are associated with this path, and then it instantiates them, creating objects of them. Service Container is compiled once and placed in the file system, and is cleared and recompiled only on some rare occasions, like when the system is updated.

In the sites/default/files folder, folders with the compiled Service Container and config can be found. Do not delete them.

Service container folder

7. Dependency Injection

Dependency Injection answers the need to pass one object into another for management and interaction. If you have an object that needs to work with other objects, then these objects are injected into it, usually either at construction or by a property.

In the BanIpManager.php file, in the BanIpManager class, you can see an example of a dependency injection:

<?php
 
public function __construct(Connection $connection) {
   
$this->connection = $connection;
  }
?>

In this case, the database connection class implementing the Connection interface is passed to the BanIpManager class at construction. The BanIpManager class can then use the injected class to work with the database.

8. Conclusion.

As you can probably see, Drupal 8 is very different from Drupal 7 in architecture. It may seem complex, and the necessite to have 3-4 times as many files to do the same thing may seem overwhelming at first. But as you understand how the module is structured, how the config files are separated from the code files, and how the classes are created and referenced, then it makes perfect sense.

Drupal 8's modern architecture goes closer in line with the PHP web development industry trends. Having Drupal 8 industry friendly, Drupal developers have opened doors for it's wider acceptance and made sure that Drupal can benefit from the modern tools and libraries that exist in the wider PHP industry.

I hope that this 2-step article has been helpful, and would like to promote the habit of reading and studying the code as a tools of getting familiar with this new version of Drupal at the back end.