Subscribe To Our NewsLetter
Share This Post:
First, what makes up the “better” code? The better code looks like this:
More descriptive – This means that the intent of what your code is doing needs to be clearer.
Testable – The testable code has explicit declarations of dependencies that clear the inputs and outputs.
With that in mind, let’s explore what custom entity bundle classes are;
What is Entity Bundle Class?
Drupal 9.3.0 comes with a new feature called Entity Bundle Classes. This gives you better control over Drupal’s entity bundles and has many advantages over previous mechanisms that use hooks to control everything.
Drupal uses entities extensively to manage content and configuration. User details, content pages, and classification terms are just a few of what are represented as content units on the Drupal site. Different types of similar configurations are called configuration entities and include text format settings, date formats, and so on.
Content entities are usually fieldable, and for content types and classification terms, Drupal allows users to generate their own variants. These various variations of content units are called bundles. The entity defines the type of object found at the Drupal website. Bundles represent the various subtypes of these entities. For example, a node is the core entity type for creating content pages, splitting it into bundles to create what you want to represent in a page, article, event, or website.
In this article we will look why entity bundle classes are useful and if you should be using them in your Drupal projects.
What problem does this solve?
Before we get to work with the entity bundle class, we need to know what problem we are trying to solve. This is a good way to provide a context of why these classes were introduced. Use the following code in many modules and themes of the Drupal 8 / 9 world.
In the code above we are using the hook_preprocess_node() hook to intercept the rendering of a node. We then use the bundle name to find out the type of node being passed to the hook before getting a value of a field from that node and setting it to a variable in the template.
Whilst this code works, it tends to be quite fragile. Writing if statements like this to catch which type of node has been passed is fine, but with fields used by more than one content type (e.g. the body field) this simple check soon becomes a bit tricky to manage. It’s easy to add more conditions to the if statement until you end up with a complex function peppered with conditions. It’s not uncommon to encounter pre-processing hooks that are hundreds of lines long (this is a bad practice if you don’t know). It’s okay to get the value from the field and pass it to the template (there’s a better way to do that without the bundle class), but in practice this can be a weakness in the code. In recent years, instead of comparing directly to the bundle type, we’ve come to use the entity’s has Field () method to check if a field exists before using it. This allows for slightly more robust code that can be adapted to other content types without changing the code.
Here is another issue of lack of encapsulation around hooks that change the way entities behave in Drupal sites. What I mean by this is that there is nothing to stop multiple themes or modules from implementing the same hook and changing the template in some way. The problem is deeper than this as there are lots of hooks that change the way in which entities operate, and they don’t have to be collected together in the same place.
As an example of another hook, it is possible to add a hook_entity_access() hook to allow (or disallow) access to entities in certain operations. The following code will return an access denied page if a user who does not have access to the site administration pages attempts to view it as an article content type.
My point here is that this hook can be placed in a different module than the pre-processing hook above. This means that both hooks interact with the same entity, but at different parts of the codebase. It’s common to place pre-processing hooks in your design, but that depends on the style of the developer creating the function. You can place different hooks on different modules and themes on your Drupal site.
This “action at a distance” anti-pattern is common in Drupal because of the way hooks work. You may find that different modules implement different sets of hooks. This can often interact. Disabling a module can have unpredictable results even if the particular hook on which the entity depends is disabled.
This is why it is so important to use operational tests on your Drupal site. This is because there may be a discrepancy between what you are testing with Drupal’s testing framework and what is actually happening on your site. The Entity Bundle classes attempts to solve some of these problems by moving the “catchable” functionality that an entity may need into a single class. Or at least the class hierarchy.
Adding Entity Bundle Classes
To use the Entity Bundle class, you must first tell Drupal which class to use. This is done using the hook_entity_bundle_info_alter () hook that modifies the existing bundle definition. Use this hook to add a new class to the bundle definition.
This is actually an important consideration and is worth checking out before digging into the code. Entity pack classes are created for use with existing content packs. You have websites that contain different content types, classifications, and even paragraphs, and you may want to customize their behaviour. For example, you can use the Entity Bundle classes to change how page content types are used and header paragraph types. However, these bundles must be present in the configuration before they can be used.
The following code block implements the hook_entity_bundle_info_alter () hook to add a custom class named Article to an existing Article content type.
Implementing the Article class is pretty simple. Since we’re adding the Bundle class to the content type, we need to extend the base entity class of Node. The Node class is not an abstract class, so you don’t need to add any other methods right away.
This obviously isn’t useful, but the real power comes when we want to do things with this class.
We’ve already mentioned that the lack of encapsulation is a problem in Drupal and that entity bundle classes can help solve this problem. The Bundle class code is called at various points during the life cycle of the entity’s class, so it can be used to replace hooks or push functionality to the Bundle class.
Let’s look at a common example of a hook_entity_insert () hook that is called after an entity has been inserted into the database. The following code is a simple example of this hook. It mimics Drupal’s current standard function of printing messages to users.
This will print a message to the user when the article is saved. Insert hooks can be used to perform all sorts of actions and are not uncommon on Drupal sites.
You can completely remove this hook and replace it with the code in the Article class. The Node class has a method called postSave () that is called after the entity is saved, so you only need to override this method to do any custom work. You also need to make sure that the parent class code is called so that the upstream code is called correctly. The following code has exactly the same functionality as the hook above, but is encapsulated in an item bundle class.
The advantage of this method (apart from removing tick marks) is that the same code is used when creating or updating articles. This means that you don’t have to add different functions for different situations. Just customize this function to do what you need.
We’ve talked about how bundle classes work, but here’s one thing to keep in mind. Since we extended the Node class, we also inherited all the code that exists upstream of that class. This means that you need to find a balance between listening to events and processing fields and the business logic behind your website. Adding too much business logic to the bundle class pollutes the code base and makes it more difficult to test.
The entity bundle classes is non-atomic and shares code with some parent classes and some functionality. As shown, you can loop to update or remove code, but you must pass the execution upstream of the parent class. Otherwise, problems are more likely to occur. In fact, calling postDelete upstream invalidates the cache tag, so if you don’t call this method, your site will have cache invalidation issues.
The bundle class is not well suited to the SOLID principle. If you add a postSave () method to a bundle class when you create a node, you may need to change that class when you create the node, which violates SOLID’s disposable policy.
Ultimately, this is a big step in the right direction in terms of Drupal encapsulation and customization. The old system that hooks everything is obsolete and includes events and plugins. This system is part of this common movement of Drupal. This allows developers to write better, more maintainable, and testable code. You can easily mock and test entity objects. It’s much better to have a class and an object to test than a named function.
LN WebworksYour Drupal Solution Partner
LN Webworks have championed open-source technologies for nearly a decade, bringing advanced engineering capabilities and agile practices to some of the biggest names across media, entertainment, education, travel, hospitality, telecommunications and other industries.