- Part 1: Overview of Content and Config Entities in Drupal 8
- Part 2: Most Simple Entity with Bundles
- Part 3: Simple Content Entity with Bundles
- Part 4: Practical Content Entity with Bundles
Note: In this example the Content Entity has been renamed to “Practical” and the Config Entity has been renamed to “Practical Type”. This is because each example is actually its own module in the Github repo.
- Per-bundle permissions and access control.
- Class Interfaces to follow best practices.
- Additional base fields for the Content Entity, making it more like Nodes.
- Name – serves as the entity’s “label”.
- Uid – serves as the “Owner” of the entity.
- Created – date entity was created.
- Changed – date the entity was last updated.
- Description field for the Config Entity.
- Better ListBuilders
A minor update to the permissions yaml file allows us to specify a PHP callable that will return an array of dynamically generated permissions.
Next we need to create the callback we just told the permissions yaml file about, and we’ll do so with a new class. Note, this is the only class in all of the examples that doesn’t extend some core Drupal class. What goes in here is all up to you!
There isn’t much exceptional going on here.
- The entry point method referred to in the permissions yaml file simply loads all
PracticalTypeEntitys, loops through them and executes the
buildPermissions()method on each one.
buildPermissions()method returns an array of dynamically defined permissions for each entity.
- And I’m using the
StringTranslationTraitto provide my class with the
The next part in implementing our custom permissions is to create a new “Access Control Handler”. This is done by extending the core
EntityAccessControlHandler class and overriding the
As long as the logic and the permission pattern in
checkAccess() are correct, this is good to go. “But wait!”, you say, “Where did $entity->getOwnerId(); come from?”. Oh yeah, that’s a good question. For this permission/access control plan to work out, we need to update our Content Entity.
… Is it just me, or is this post getting really long? Oh well, no stopping now!
Time to make some significant improvements to our Content Entity. Take a look at this new version, and let’s see what all has changed.
New Entity Keys –
changed. Take note of the
label as it is the only entity key we have aliased. Its alias is “name”, which means the base field “name” will be mapped to the Entity’s
label property, and the column created in the database for the
label property will be “name”.
Access Handler – Points to our new
Base Field Definitions – Now that we are using some
entity_keys that are not automagically converted into base fields, we need to manually define fields for the new ones. Summary of new base fields:
uid– entity_reference field that targets user entities.
name– Simple string field. This is the field that will be aliased as the entity’s label.
created– This field will track the date/time for when the entity was created.
changed– This field will track the date/time for when the entity was last updated.
Getters & Setters – Simple functions that set, or return the values of some of our new fields. Note how there are no gettings & setters for the
changed field. The
changed getters & setters are provided by the
preCreate() method – Overrides the preCreate() method for the parent class. We’re using it to pre-populate the
uid field with the current user’s uid.
implements PracticalEntityInterface – Attempting to follow best practices, this entity is now programmed to an interface.
Content Entity Interface
PracticalEntityInterface simply describes methods the
PracticalEntity must define.
In an ideal world, all of our classes are programmed to an interface. But for now, I’ve focused only on the two Entity classes.
Content Entity List Builder
Now that we have much more relevant information concerning each content entity we could display, let’s update the List Builder to show that information.
The important changes here are that we’re now showing our new fields on the Entity’s collection list. For us to show the
changed fields as formatted dates instead of timestamps, we need to ask Drupal’s dependency injection service to provide us with the date formatter.
To accept dependency injected services, we need to create the static method
createInstance() and within it return a static instance of the object, passing in services from the dependency container as parameters. In the class’s constructor, we expect the new services to be passed in, and assign them to properties on the object.
Long story short, we can now use the
renderer services within our object as the
renderer properties respectively.
Currently this class is only using the
date.formatter service to output the
changed timestamps as dates. And though this example doesn’t use the
renderer, it is a common dependency we might want in the future.
The Config Entity (which handles our Bundles) has to be updated much less in order to provide a new
"description" – added to the
config_export array, so that it is exported along with a Bundle’s configuration.
Getters & Setters – New methods for setting and returning the value of our
Protected Properties – Local class properties for storing the values of our entity.
implements PracticalTypeEntityInterface – Best practices!
Config Entity Interface
This interface doesn’t do much at the moment, but it’s worth noting that it extends the core provided interfaces for the Config Entity and Entity Description.
Config Entity Form
Little has changed with the Config Entity Form, but we need to create a new
description field so that the administrator can easily provide the Bundle’s description value.
Config Entity List Builder
The only thing that has changed in the Config’s List Builder is that we are now showing the value of the new
There we have it. A fairly practical Custom Entity with Bundles. Complete with good administration experience, and some of the fields we all expect having worked with Nodes so much.
Future improvements: Some additional features you might like your custom entity to have are Revisions and Translations. But for now, this will do quite nicely.