Typed Entity
Provides typed wrapper classes for Drupal entities, enabling object-oriented business logic encapsulation and improved code maintainability.
typed_entity
Install
composer require 'drupal/typed_entity:^4.1'
composer require 'drupal/typed_entity:^4.0'
Overview
Typed Entity is a developer-focused module that transforms the way you work with Drupal entities by wrapping them in typed PHP classes. Instead of scattering entity-related business logic across hook implementations and procedural code, this module enables you to organize code into dedicated wrapper classes that encapsulate all business logic for specific entity types and bundles.
The module implements the Repository Pattern combined with the Wrapper Pattern, allowing developers to create TypedRepository plugins that manage entity wrapping and rendering. Each repository can define multiple wrapper class variants and renderer variants that are selected based on context (such as view mode, field values, or custom conditions).
Key architectural concepts include: Wrapped Entities - PHP classes that wrap Drupal entities and contain all business logic methods; Typed Repositories - Plugin classes that manage entity wrapping, querying, and renderer selection; Renderers - Classes that encapsulate rendering-related logic and integrate with Drupal's render pipeline through hooks; and Variant Negotiation - A system that selects the appropriate wrapper or renderer class based on context conditions.
This approach results in code that is easier to test (through unit tests with mocked dependencies), more maintainable (business logic is centralized), and follows object-oriented best practices. The module integrates seamlessly with Drupal's render pipeline by implementing hook_entity_view_alter, hook_preprocess, hook_entity_display_build_alter, and hook_entity_build_defaults_alter.
Features
- TypedRepository plugin system for defining entity-specific repositories with PHP 8 Attributes or Annotations
- WrappedEntity base class for creating typed wrapper classes around Drupal entities with business logic methods
- TypedEntityRenderer system for encapsulating entity rendering logic in dedicated classes
- Variant negotiation system that selects wrapper and renderer classes based on context conditions
- Built-in variant conditions: FieldValueVariantCondition (check field values) and EmptyFieldVariantCondition (check empty fields)
- RepositoryManager service for wrapping entities and finding appropriate repositories
- Automatic integration with Drupal's render pipeline through core hooks (entity_view_alter, preprocess, entity_display_build_alter, entity_build_defaults_alter)
- CacheableDependencyWrappedEntityTrait for easy cache metadata propagation from wrapped entities
- Support for both PHP 8 Attributes and legacy Annotations for plugin discovery
- Helper method wrapReference() and wrapReferences() for wrapping referenced entities
- createEntity() method on repositories for creating and wrapping new entities in one step
- Query building support through getQuery() method with automatic bundle filtering
Use Cases
Encapsulating Entity Business Logic
Create a WrappedEntity class to centralize all business logic for an entity type. For example, a User wrapper can have a nickname() method that extracts the username from email, or an Article wrapper can have methods to check content restrictions. This keeps business logic testable and maintainable instead of scattered across hooks.
Context-Aware Entity Rendering
Create TypedEntityRenderer classes for different view modes. Each renderer can customize preprocessing, add attributes, or modify the render array. The system automatically selects the appropriate renderer based on view mode or custom conditions defined in the applies() method.
Variant-Based Entity Handling
Use ClassWithVariants to define multiple wrapper classes for the same entity type/bundle. For example, articles tagged with 'Baking' can use a BakingArticle wrapper with specialized methods, while regular articles use the standard Article wrapper. Variant selection happens automatically based on the applies() static method.
Repository-Level Access Control
Implement AccessibleInterface on your TypedRepository or WrappedEntity classes to add custom access logic. For example, deny access to articles when there are more than a certain number published, or check the author's profile for inappropriate content.
Entity Reference Wrapping
Use wrapReference() and wrapReferences() methods in your WrappedEntity classes to easily wrap related entities. This maintains the typed entity pattern throughout your codebase when working with entity references.
Testable Entity Logic
The wrapper pattern makes entity logic highly testable through unit tests. You can mock the entity and any injected services, then test your business logic methods in isolation without needing a full Drupal bootstrap or database.
Tips
- Use PHP 8 Attributes (#[TypedRepository]) instead of Annotations for cleaner syntax and better IDE support
- Create base wrapper classes for entity types (e.g., NodeBase) and extend them for bundle-specific wrappers
- Implement AccessibleInterface on wrappers or repositories to integrate custom access logic with Drupal's access system
- Use the CacheableDependencyWrappedEntityTrait to easily propagate cache metadata from wrapped entities
- Enable the Typed Entity UI submodule during development to visualize your repository and class hierarchy
- Repository IDs follow the pattern 'entity_type_id.bundle' (e.g., 'node.article') - repositories without a bundle cover all bundles of that entity type
- Clear caches after creating new TypedRepository plugins for them to be discovered
- The applies() method on wrappers and renderers should be fast as it may be called frequently during variant negotiation
- Use typed_entity_repository_manager() helper function in .module files for easy access to the RepositoryManager service
- Renderers match view modes through the VIEW_MODE constant - override applies() for more complex matching logic
Technical Details
Admin Pages 2
/admin/config/development/typed-entity
Administrative page to explore and understand typed entity repository definitions. Shows a searchable table of all registered TypedRepository plugins with their entity type, bundle, description, and class information. Allows filtering by plugin ID and provides links to detailed exploration pages for each repository.
/admin/config/development/typed-entity/{typed_entity_id}
Detailed exploration page showing complete information about a specific TypedRepository plugin including the repository class, its parent class, implemented interfaces, entity wrappers (with fallback and variants), and entity renderers (with fallback and variants). Displays PHP class summaries with file locations and attribute definitions.
Permissions 1
Hooks 5
hook_typed_repository_info_alter
Allows modules to alter typed repository plugin definitions before they are used.
hook_entity_view_alter (used by module)
Typed Entity implements this hook to call TypedEntityRenderer::viewAlter() for wrapped entities.
hook_preprocess (used by module)
Typed Entity implements this hook to call TypedEntityRenderer::preprocess() for wrapped entities.
hook_entity_display_build_alter (used by module)
Typed Entity implements this hook to call TypedEntityRenderer::displayBuildAlter() for wrapped entities.
hook_entity_build_defaults_alter (used by module)
Typed Entity implements this hook to call TypedEntityRenderer::buildDefaultsAlter() for wrapped entities.
Troubleshooting 4
Clear all caches using drush cr. TypedRepository plugins are discovered by the plugin manager and cached. After creating a new plugin class, caches must be cleared for it to be recognized.
Check that your variant class implements the applies() static method correctly and returns TRUE for the appropriate context. Ensure the variant is listed in the ClassWithVariants 'variants' array in your TypedRepository attribute/annotation.
Ensure you are calling $repository->wrap($entity) or using the RepositoryManager service to wrap entities. Direct entity access bypasses the wrapper system. Use typed_entity_repository_manager()->wrap($entity) to get wrapped instances.
Verify that your TypedRepository plugin annotation/attribute has the correct entity_type_id and bundle values. Check that the wrappers ClassWithVariants has a valid fallback class that exists and extends WrappedEntityBase.
Security Notes 3
- The module provides no additional access control beyond Drupal's standard permission system. The 'explore typed entity classes' permission should be restricted to trusted administrators as it exposes internal class structure information.
- When implementing AccessibleInterface on wrappers or repositories, ensure access decisions are properly cached and do not leak sensitive information.
- The example module (typed_entity_example) is for demonstration purposes only and should not be enabled on production sites.