Skip to main content
drupalreleases

Use direct release data with your AI assistant.

Learn more

Typed Entity

Security covered Under active development 2,286 sites

Part of the Typed ecosystem · 5 projects

This module allows you to treat your Drupal entities as typed objects, encapsulating business logic within custom classes. It helps keep your code cleaner, more maintainable, and easier to debug by providing a structured way to handle entity-specific operations and variations.

Use Typed Entity as a namespace for your business logic. It provides a scope to place your business logic, and help you keep your global scope clean of myriads of small functions.

This module provides a simple way to treat you existing entities like typed objects. This will allow you to have a more maintainable and easier to debug codebase.

Lullabot-sponsored project

This module is sponsored by Lullabot, and we keep a special eye on its maintenance. Read the Architecture Decision Record on why we use this module.

๐Ÿ“– Read the article (1) ๐Ÿ“– ๐Ÿ“น Watch the video ๐Ÿ“น (4.x) ๐Ÿ“– Read the article (2) ๐Ÿ“–

Make sure to check the example module to get inspiration on how to implement this on your code base.

Why this, instead of core's bundle classes

Starting in Drupal 9.3 you can use custom bundle classes in Drupal core. However this approach falls short in several scenarios.

Read this article for more details.

Usage

Whenever you find yourself writing custom code that accesses $node->field_lorem anywhere in your site (let's say a hook for the example) you can benefit from a wrapped entity. A wrapped entity is a custom class that encapsulates the code for a particular content type. The goal is that your hook no longer does $node->field_foo, but instead $article->doTheThingDependingOnFoo().

For each entity type and bundle you need to let the system know that you want a custom class for your entities of a certain type. For that you will register a plugin in your custom module under src/Plugin/TypedRepositories. Like this example:

/**
 * The repository for articles.
 */
#[TypedRepository(
  entity_type_id: 'user',
  wrappers: new ClassWithVariants(User::class),
  description: new TranslatableMarkup('Repository that holds business logic applicable to all users.'),
)]
final class UserRepository extends TypedRepositoryBase {}

With that you can start writing and testing Drupal\typed_entity_example\WrappedEntities\User. Now everytime you need to do things with a user in a hook (or elsewhere) you can do:

// You can inject the Repository Manager service in your services as well.
$user = typed_entity_repository_manager()->wrap($user_entity);
$user->doThatThingTheProjectNeeds();

Variants

We know that often times the business logic depends on content. It does as well in our projects! Typed Entity has the concept of variants for that. Let's change the example above to add the variants key:

/**
 * The repository for articles.
 */
#[TypedRepository(
  entity_type_id: 'user',
  wrappers: new ClassWithVariants(User::class, [ModeratorUser::class]),
  description: new TranslatableMarkup('Repository that holds business logic applicable to all users.'),
)]
final class UserRepository extends TypedRepositoryBase {}

So we can now call syncWithComplicated3rdPartyModerationService and keep that logic encapsulated and testable.

$repository_manager = typed_entity_repository_manager();
$article = $repository_manager->wrap($node);
$user = $repository_manager->wrap($user_entity);
if ($user implements ModeratorUserInterface && $article implements ModeratableInterface) {
  $user->syncWithComplicated3rdPartyModerationService($article, ModeratableInterface::PUBLISHED);
}

How does Typed Entity know when to give you a User or a ModeratorUser? All variants declared in the annotation need to implement VariantInterface, which means that they will have a method called applies(). The first class to return TRUE in that method gets used. If none applies, the fallback is used.

We encourage you to use Renderers

Renderers are a very useful and flexible way of encapsulating business logic for rendering an entity. See this example to learn how to declare a renderer.

/**
 * The repository for articles.
 */
#[TypedRepository(
  entity_type_id: 'node',
  bundle: 'article',
  wrappers: new ClassWithVariants(...),
  renderers: new ClassWithVariants(fallback: Base::class, variants: [Full::class]),
  description: new TranslatableMarkup('Repository that holds business logic applicable to all articles.'),
)]
final class ArticleRepository extends TypedRepositoryBase {}

You can use the fallback key in your typed repository plugin to use a particular renderer if no other renderer is applicable. You can implement the applies() method in renderers as well, but if your variants are only for the view mode then use the VIEW_MODE constant in your renderer (like this).

When you have (1) declared your renderer, and (2) specified your VIEW_MODE / applies(), you can proceed to implement the renderer methods. In most cases you will:

  • Add preprocess logic to prepare the data for the Twig template. Use preprocess().
  • Alter the render array for the associated entity. Use viewAlter().
  • And more! Check the TypedEntityRendererInterface.

Typed Entity UI

Version 4.x of the module comes with the Typed Entity UI submodule. This will simplify debugging any gotchas in typed entity.

Go to the /admin/config/development/typed-entity to explore the definitions in a visual way.

Drupal 10โ€“11

Ask your assistant about Typed Entity

Check compatibility with your Drupal and PHP version, maintenance and security coverage, from current release data. How it works

Depends on

Dependencies of the latest stable release

No dependencies recorded for this project.

Required by

Tracked projects that depend on this one

No tracked projects depend on this one yet.

Activity

Tracked releases
4
Tracked since
Oct 2023
Latest release
11 months ago
Releases (12 mo)
1
Maintenance
Active

Release Timeline

Releases

Version Type Core PHP Release date
4.1.2 Stable 10โ€“11 Oct 24, 2025
4.1.1 Stable 9โ€“11 Sep 15, 2025
4.1.0 Stable 9โ€“11 Sep 9, 2024
4.0.12 Stable 8โ€“10 Oct 31, 2023