A custom PHPStan rule is now shipped with the module to catch a common integration mistake at static analysis time rather than at runtime.
Classes that use BuildFieldTrait and call any of its entity-building methods (buildEntities(), buildReferencedEntities(), buildEntitiesWithViewModes(), buildReferencedEntitiesWithViewModes()) must declare a protected CacheableMetadata $cacheableMetadata property. Without it, those methods fail at runtime because they internally access $this->cacheableMetadata to track cache dependencies.
The new PHPStan rule detects this missing declaration during static analysis, so you get a clear error message instead of a cryptic runtime failure.
How it works
The rule is automatically registered via PHPStan's extension-installer. If your project uses phpstan/extension-installer (recommended), no configuration is needed — just run PHPStan as usual.
If you don't use extension-installer, add this to your phpstan.neon:
includes: - web/modules/contrib/pluggable_entity_view_builder/phpstan-extension.neon
Example error
Classes using BuildFieldTrait must declare a protected $cacheableMetadata property of type \Drupal\Core\Cache\CacheableMetadata. 💡 Declare "protected \Drupal\Core\Cache\CacheableMetadata $cacheableMetadata;" in the class body.
Before (triggers error)
use Drupal\pluggable_entity_view_builder\BuildFieldTrait; class NodeViewBuilder extends EntityViewBuilderPluginAbstract { use BuildFieldTrait; public function build(array $build, EntityInterface $entity): array { $build['field_image'] = $this->buildEntities($entity->get('field_image')->referencedEntities()); return $build; } }
After (correct)
use Drupal\Core\Cache\CacheableMetadata; use Drupal\pluggable_entity_view_builder\BuildFieldTrait; class NodeViewBuilder extends EntityViewBuilderPluginAbstract { use BuildFieldTrait; protected CacheableMetadata $cacheableMetadata; public function build(array $build, EntityInterface $entity): array { $build['field_image'] = $this->buildEntities($entity->get('field_image')->referencedEntities()); return $build; } }
Note: If your class extends EntityViewBuilderPluginAbstract, the property is already declared there — no action needed. This rule primarily catches cases where BuildFieldTrait is used in standalone classes without the abstract base.