After making heavy use of the functionality for over half a year, TEN7 made a concerted effort to include one of the biggest and most important changes to Drupal Core in the 9.3.0 release: #2570593: Allow entities to be subclassed using "bundle classes"
Although the feature is completely invisible to end users and site builders, for developers, it allows a fundamental shift in how we can work with Drupal’s immense flexibility and power. Developers are users (and people), too! Although it might seem like only a small slice of those who interact with Drupal would care about this change, it can impact everyone in a positive way. Through this short series of blog posts, we will share the story of what this feature is, how it came to exist and why it matters.
We have divided this topic up into three posts which will run over the next few days. Each piece can stand alone if you’re only interested in one of them, or you can read them all:
- Part 1: The story of bundle subclasses in Drupal and why we got this done. (You are here.)
- Part 2: How to make use of it to improve your life.
- Part 3: Lessons of contributing to Core: using a Gitlab MR instead of patches, what it takes to get something like this committed, how and why to contribute “upstream”, and more.
While some of this series will dive into the technical details of what this means and why it matters, I’ll also explain how a change this big got committed to Drupal Core, and why TEN7 supported my time to work on it. We believe this feature will transform how people build and work with Drupal sites, and we wanted to see it folded in upstream and officially adopted. I hope that these articles can inspire other developers and shops to roll up their sleeves and dive into the complicated and potentially scary process of getting things done in Drupal Core.
Here at TEN7, we Make Things That Matter. We don’t just build websites, we also contribute back to the communities where we live and work, and to the tools we use to get things done. Whenever feasible, we’d rather help get a bug fixed or a feature added upstream than to go off on our own. We’re way too small on our own to do everything we’d like to do, so by working together with others, we can accomplish so much more.
What Is It?
On the 9.3.0 release announcement blog post, under the heading “Various developer improvements”, we find:
Entity bundles can now declare their own class, encapsulating the required business logic. A bundle class must be a subclass of the base entity class, such as
\Drupal\node\Entity\Node. Encapsulating all the required logic for each bundle into its own subclass opens up many possibilities for making more clear, simple, maintainable, and testable code.
This post will explain what that means for anyone to understand. The text refers to this issue: #2570593: Allow entities to be subclassed using "bundle classes". This new feature is documented in the Introducing bundle classes change record. Let’s dive into what this all means.
What are “Entity Bundles”, Anyway?
As the saying goes, “naming is hard.” Drupal is extremely flexible, and tries to make things that can work in a bunch of different contexts and many different ways. Each kind of content on the site is called an “entity.” There are both configuration entities and content entities, although for the rest of this article, all we’re going to care about are content entities. Core provides a handful of different content entities: users, nodes, media items, and so on.
Each content entity type can potentially have multiple subtypes. For example, there might be different kinds of media to represent images, audio clips, or video. And of course, there are the primary pillars of Drupal’s information architecture over the last 20 years: nodes and node types (known as “Content types” in the Drupal UI). These subtypes are called “bundles” (please don’t ask me why, I don’t know), and each one can have its own set of fields, permissions, displays, and much (much!) more.
So What Are “Bundle Classes”?
Every entity type in Drupal core is defined by a PHP class. Each class has a fancy comment at the top of it that includes various “annotations” that control how the entity type behaves. Every content entity has to define certain methods and properties that they inherit from parent classes (e.g.
Although all the principles apply to any content entity, I’m going to stick with nodes for the rest of this since:
- it’s usually easier to understand something specific than speaking only in generalities, and
- nodes have been around nearly as long as Drupal has, so almost anyone using Drupal has some experience working with them.
If you load a node, you’ll get an object that’s an instance of
\Drupal\node\Entity\Node and implements
NodeInterface itself extends a bunch of other interfaces (
EntityPublishedInterface). So if you have a
$node object, you can call any method on any of those interfaces (and any of the interfaces those extend), all the way up the class hierarchy.
But as I mentioned above, each node type is sort of its own world, too. They each have their own sets of fields, functionality, behaviors, permissions and potentially custom logic for how they need to work.
(Side note: I don’t like the term “business logic”, since (thankfully) not everything is a business. Schools, community organizations, bands and all sorts of things that aren’t businesses all can have their own special needs from their content. So I’m going to stick with the phrase “custom logic”, instead.)
You might have blog entry nodes, discussion (forum) nodes, magazine article nodes, profile nodes, event nodes, location nodes, reference nodes, and so on. They’re all nodes. They all need to do everything a node can do. But each one has its own (although often overlapping) set of additional behaviors they need to support. Wouldn’t it be nice if we could extend the
Entity → Content Entity → Editorial Content Entity → Node hierarchy, and add separate classes to represent each of those node types? Now we can!
The Backstory of Our Journey to Bundle Classes (The Dirty Laundry)
One of our biggest clients has a really large, complicated site, with a lot of different moving pieces. There’s a lot of technical debt built up over many years. One of the big challenges of working on this site is how complex it all is. There are a bunch of different node types. The client is very concerned about how everything should work, so we have lots of custom code to make things function and look exactly how they want.
It’s an old site that has been built over many years. Lots of the custom code was originally from Drupal 6. As the site evolved (and various versions of Drupal have seen their end-of-life), the site and custom code was ported to the latest versions of core. The porting to D8/9 was mostly “just get it working again (but make most of it translatable)”, not a fundamental re-architecture. As such, the vast majority of the custom code was procedural functions sprinkled across various custom modules (41 of them and counting) and preprocess methods, both in those custom modules and in the (please don’t ask) four different custom themes.
I personally started working on the site In August 2020. I was brought onto the team as someone with a lot of experience dealing with complex systems and someone who knew Drupal inside and out. I dove in, learned how things worked (and didn’t), and got to know the various personalities involved (both the rest of the team, and the client). After about 6 months, it was clear that the complexity was really slowing us down.
We needed a way to organize the code so we could more easily work with it and develop the functionality the client wanted. We had way too much complex logic scattered across way too many places. What should have been a simple request from the client like, “We’d like to change how bylines are displayed,” ended up requiring changing code in about a dozen places at wildly different layers of the system.
Being a Computer Scientist, I thought, “All this would be so much easier if each node type was represented by a subclass of
\Drupal\node\Entity\Node.” Being around the Drupal community for as long as I have, my next thought was, “I can’t possibly be the first person to think of that.” So I searched the core issue queue (and maybe asked around in Drupal Slack), and found my way to feature request #2570593. I was thrilled to read what I found. A lot of luminaries in the community were already thinking just like me, and they already had the functionality basically working. Yay open source!
That was March 2021, and I dusted off the latest patch (at the time #93), added it to our (large) pile of core patches in our composer.json file for the site, and started to see how it worked. In short: beautifully! It was exactly what I was looking for: easy to use, did what it was supposed to, and allowed us to start refactoring all that custom logic into a formal class hierarchy.
We don’t have the luxury of stopping everything else to clean up and refactor the whole code base all at once. Instead, as we move forward we’re adding to the bundle subclasses, and consolidating existing procedural functions where feasible. The site is evolving to embrace the new way of organizing the code. Already the benefits are clear, and we’re so happy with how it’s going.
What Does This Mean for You? Stay Tuned!
In Part 2 of our series, we look at how to actually make use of this feature to improve your life.