Write Better Code with Typed Entity

09 Feb 2022

Drupal allows you to fulfill even the most sophisticated desires of site owners, but at the same time provides a challenging experience for those who implement these tasks.
You get unlimited flexibility to create multipurpose and large websites and projects. But you need to master certain specific knowledge to reveal and show the full power of Drupal CMS. It requires an understanding of modules, entity code, Drupal services, content types, hooks, plugins, and so on and so forth. The complexity of Drupal lies in knowing when, what and how to use them.
The coding process does not require much effort, but it requires maintenance. Let's discover how to organize your Drupal entity code and avoid any problems!

Step 1. Separate framework logic from business logic

The framework logic should exist separately from the business logic, since they are different.
What is Framework logic?
This is logic that already exists in Drupal Core and Contrib and remains without changes from site to site, from project to project.
What is Business logic?
In simple words, this is everything that distinguishes a site from a site, a project from a project.

What is it for?

The framework logic and business logic should be separated because:

  • it helps you to see more clearly where one ends and where the other begins
  • this will simplify the maintenance of the code.
  • if you make changes by mistake in one part, they will have minimal impact on different segments

What is a Typed Entity, and how is it useful?

Typed Entity is a module that will be useful to anyone writing custom code for a Drupal site. This is useful when you are working with a form connected to a node or access or something bound to an entity. It helps turn complex customer wishes into clean code, where logic goes hand in hand with the entity, and there is no need to create many hooks.

The developer describes his module as follows:
"Use Typed Entity as a namespace for your business logic. It provides a scope to place your business logic, and helps you keep your global scope clean of myriads of small functions."

Let's look at a real example. Car is not a node in this code but includes a node of type Car in its $entity property.

use Drupal\Core\StringTranslation\TranslatableMarkup;

use Drupal\physical_media\WrappedEntities\LoanableInterface;

 

final class Car implements LoanableInterface {

 private const FIELD_CAR_TITLE = 'field_full_title';

 private $entity;

 

 public function label(): TranslatableMarkup {

   return $this->entity

       ->{static::FIELD_CAR_TITLE}

       ->value ?? t('Title not available');

 }

 

 public function owner(): Person {...}

 public function checkAvailability(): bool {...}

 

  • Instead of putting all your logic in an unmeasurable number of hooks, you put it closer to the entity, no matter what you want to do.
  • To put all the logic you need, you should create a class representing an entity and a node of the type car.

// This uses the 'title' base field.

$title = $car->label();

 

// An object of type Owner.

$owner = $car->owner();

 

// This uses custom fields on the User entity type.

$owner_name = $owner->fullName();

 

//Some cars have additional abilities and relationships

if ($car instanceof LoanableInterface) {

 $available = $car->checkAvailability() === LoanableInterface::AVAILABLE;

Use this method in your hooks and plugins to see how cleaner your code becomes. Put the business logic for the cars in the Car class and the business logic for the service in the Service class.
If you are accessing field data, that is not the Car class, which means you need to put it in an entity wrapper, in our case, Typed Entity.

What does the Typed Entity module:

  1. gives scope for business in your project
  2. simplifies the treatment of Drupal custom entities as typed objects
  3. helps to maintain the debug codebase
  4. simplify debugging process in the typed entity
  5. keeps your code cleaner

Step 2. Focus on entity types

Entities' wrapping is not yet guaranteed that all your custom code will look pure. However, ordering them improves the code noticeably. More specifically, it cuts the path to your goal by more than half.

What are custom entities for?

Entities in the context of Drupal are helpful in the entire programming process as they are used for many purposes. They are responsible for containing metadata, grouping content when listing, displaying as content on the screen, and more and more.

Another way to write better code

There are similar solutions for those who work with Drupal entity code. It's about a core patch. This allows you to work with specific classes for entity bundles.
For example, if you call Node::load(), this will return an instance of the node class, no matter what type of node it is. Applying a core patch will give you a different class based on the node type.
However, it was not without a fly in the ointment even here. Using this approach, you run the risk of having a clash after the next update of Drupal and much more.

Step 3. Work with Typed Entity

Let's learn how Typed Entity's approach works. First of all, you create a plugin. After that, you should associate it with entity type and bundle, which are typed repositories. As a result, these repositories start their work and work at the entity type level.
Typed Entity is primarily about organizing your Drupal entity code and making it easier to maintain. The big plus is that it works to make life easier for programmers. Some tradeoffs have been made in Drupal to avoid misunderstandings and failures in the system. Namely, it simplifies the coding experience for developers when they are working on business logic for a unique project.

How does Typed Entity work?

You can work with code without knowledge of Typed Entity, but if you want to write clean and maintainable code, then you have to look deeper into the details.
So what kind of details are we talking about? We continue to sort everything out using the Car entities.

/**

* The repository for cars.

*

* @TypedRepository(

*    entity_type_id = "node",

*    bundle = "car",

*    wrappers = @ClassWithVariants(

*      fallback = "Drupal\my_module\WrappedEntities\Car",

*      variants = {

*        "Drupal\my_module\WrappedEntities\SciFiCar",

*      }

*    ),

*   description = @Translation("Repository that holds business logic")

* )

*/

final class CarRepository extends TypedRepositoryBase {...}

The "wrappers" show which classes will wrap your Node Type. We call "ClassWithVariants" to refer to the main Car class. The repository manager will pull up the Car class or other variants when running a car node to the ::wrap() method.
On variants, we mean different types of cars. Each option must contain both shared business logic and unique business logic. It might look like this:

variants = {

 "Drupal\my_module\WrappedEntities\SciFiCar",

 "Drupal\my_module\WrappedEntities\BestsellerCar",

 "Drupal\my_module\WrappedEntities\AudioCar",

}

To let the typed entity know which variant to use, we implement the::applies() method. This method receives $context, which contains the entity object. You can then look at any data and fields and check if the class applies to that context.

Entity Rendering

Why do entities exist? Of course to render them. When entities render, we have variants called “view modes”. They apply in certain contexts.

  • This is partly reminiscent of the wrapped object and its ability to overlay the system and clean up unnecessary code.
  • This helps us to put everything about an entity type into a new wrapped object called a renderer.

Thus, the need to put all rendering logic in one Wrapped Entity class is eliminated, which is very convenient.
Typed Entity supports three hooks:

  1. hook_entity_view_alter()
  2. hook_preprocess()
  3. hook_entity_display_build_alter()
  4. That's why we added a "renderers" key to the repository example.

/**

* The repository for cars.

*

* @TypedRepository(

*    entity_type_id = "node",

*    bundle = "car",

*    wrappers = @ClassWithVariants(

*      fallback = "Drupal\my_module\WrappedEntities\Car",

*      variants = {

*        "Drupal\my_module\WrappedEntities\SciFiCar",

*      }

*    ),

*    renderers = @ClassWithVariants(

*      fallback = "Drupal\my_module\Renderers\Base",

*      variants = {

*        "Drupal\my_module\Renderers\Teaser",

*      }

*    ),

*   description = @Translation("Repository that holds business logic")

* )

*/

final class CarRepository extends TypedRepositoryBase {...}

Two simple truths follow from this:

  • understand wrappers = understand renderers
  • renderers are easier to test than separate hook implementations.

To sum up

Typed Entity is a great help to anyone involved in Drupal development. Typed Entity's approach to improving the code may seem complicated and confusing for beginners, but it's worth it. After its application, you will receive the Drupal entity code, which is easier and cheaper to maintain. Our web developers team unequivocally advises Typed Entity.

 

Comments

An
Anonymous