Looking up at the facade of the National Museum of African American American History and Culture with the Washington Monument in the background to the right against a clear blue sky. The NMAAHC can be described as stacked trapezoids where the edge of each level points diagonally upward and out from the center of the building. Then the next level is smaller the the top of the one below it and grows up and out in the same way. The panels of the facade are blocks and built with metal that allows you to see li
Insight

Troubleshooting Entity Versions in Drupal: Tips for Accurate Data Handling

A man in a white button up shirt stands smiling in front of a metal wall
Philip Curley Technical Lead

Overview

Entities are a high-level Drupal structure that stores related data and which can be extended using other structures like Fields. They are important because they are the primary way that Drupal users interface with their content. When developing custom modules and applications it's very important to handle up-to-date and accurate data. There are situations when Drupal can serve you two versions of the same Entity and whose data may differ! This happened during some recent development and caused incorrect processing logic in my Drupal application. In this post I will explore how this occurred to me, delve into some of the specific details of my problem, and offer some guidelines and best practices so that you can work with the most up-to-date Entity data in Drupal.

Loading Entities in Drupal

Entities are stored and retrieved from the Drupal database. The most straightforward way to load an Entity in Drupal is to use the EntityTypeManager service to load the EntityStorage class and retrieve the Entity using an ID. However there are instances in the code where the Entity ID isn't available and we'd like to get the Entity data. For these cases Drupal provides useful services like CurrentRouteMatch where the code can load an Entity based on the current request's web route.

A code example using the EntityTypeManager to load a Node Entity given an ID:

$entityNode = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

A code example using CurrentRouteMatch to load a Node Entity from the route of the current request:

$entityNode = \Drupal::routeMatch()->getParameter('node');

Case Study: Different Versions of a Single Entity

While developing on a recent project I discovered a strange behavior in my custom module logic. In summary, I found two versions of the same Node Entity in function called after this Node had been updated during the same Request. The first version was the Node obtained from the CurrentRouteMatch service. The second was a version of the Node loaded using EntityTypeManager. I confirmed the differences by checking each Node's changed value and saw two different times! What's happening here?

  1. Route Matches are statically cached during the first instantiation of the CurrentRouteMatch service. The service has a $routeMatches attribute which is a SplObjectStorage object. That object uses the HTTP request as the key and the constructed RouteMatch object as the value. During the instantiation matches to the route's Entity are identified and loaded from the database and stored into the RouteMatch object. Whenever the CurrentRouteMatch service is subsequently called the it will return the cached RouteMatch. This presumably saves saves time and resources.
  2. CurrentRouteMatch service is called before Entities are saved. My findings are that when an Entity Form is saved there is a Drupal Core EventSubscriber (EarlyRenderingControllerWrapperSubscriber) which instructs the form to rebuild. During this process the CurrentRouteMatch service is called and the RouteMatches are loaded into its storage. This is the first instantiation of CurrentRouteMatch describe in point 1 above. Now a version of the Entity exists in Cache before it's saved in the same request.

In my specific application function the Node wasn't available and because this needed to run after the Node (or any Entity) was updated from a form it seemed logical to use CurrentRouteMatch service to obtain the Node and data. The impact to our application was that incorrect logic was executed because the first, older version of the Node contained different data than was passed by the form and saved in the updated Node. This was very confusing at first and took a bit of debugging to identify. After I could demonstrate two versions of the Node existed I updated my code to retrieve the updated Node data from the database using EntityTypeManager service.

Development and Best Practices

When to use CurrentRouteMatch

  • When the entity data is unavailable in the method or function but you can access it via the Request route.
  • When you need a fast way to access Entity data.
  • When you need Entity metadata like the ID or unchanging information like the created date.
  • Scenarios where the Entity data isn't likely to change.

Loading Entities from the Database with EntityTypeManager or similar service

  • For requests where the Entity data can change.
  • For requests where up-to-date entity data is necessary.

Conclusions

One of the things I like about Drupal the flexibility to choose from a variety of tools to accomplish a single task. There are many services and APIs, as well as Symfony components, which can produce the same information and results. The tools I use are entirely my preference. In this instance I identified two methods of obtaining an Entity in my code. However my first, preferred method revealed that seemingly identical results can be deceiving. It's necessary to be aware which tools rely on caching. Despite caching being a powerful time and resource saving technique, it causes issues when information changes. It's necessary to maintain awareness of when information is changing during a request so that you avoid relying on outdated, cached information. When in doubt rely on tools that call up-to-date information directly from the database storage.