wucdbm/entity-extend-bundle
Wucdbm EntityExtendBundle is a Symfony bundle for extending Doctrine entities with additional data without modifying their core classes. It helps organize reusable entity extensions, keep domain models clean, and manage custom fields or model add-ons more easily across your application.
This is a Symfony2 bundle, so step one for a Laravel developer is acknowledging the stack gap: there is no service provider, no config/ publish tag, and no Artisan surface. You interact with it entirely through AppKernel, Doctrine's entity manager, and Symfony's DI container.
Minimal integration path:
composer require wucdbm/entity-extend-bundle. The package name on Packagist is wucdbm/entity-extend-bundle even though the repo is PjEntityExtendBundle — expect that mismatch to confuse your autoload/debugging searches.app/AppKernel.php:
new Pj\EntityExtendBundle\PjEntityExtendBundle(),
doctrine.orm.mappings entry or relied on the Resources/config/doctrine convention). Check Resources/config/ first — that is where the mapping driver and any DI services live.EntityExtension) that you subclass, plus a discriminator-style mapping so one join table can hold many "extra attribute" entities for different host entities. Read the Entity/ directory before writing any code.Where to look first: the Entity/ directory for the base extension class and its Doctrine mapping, then Resources/config/ for services and mapping config, then DependencyInjection/ for extension points. Skim composer.json for the PHP and Symfony version constraints — that single file tells you whether this is salvageable or archival.
First use case: you have a Product entity you don't want to touch (third-party bundle, or a stable core), and you need to attach a handful of optional, sparse attributes (SEO fields, warehouse metadata). Instead of altering the product table, you create a ProductExtension extends EntityExtension with those fields, map it one-to-one back to Product, and read it through the relation. That "extend without touching the original schema" idea is the reason the bundle exists, and it's the fastest way to evaluate whether the abstraction fits your problem.
The mental model maps cleanly onto things Laravel developers already know, which helps when porting or reimplementing:
type/discriminator column, and each concrete extension entity maps to a row keyed to its host. Think of it as a morphTo relation, but resolved by Doctrine's inheritance map at the entity level rather than by a *_type string column at query time.Product::$extension (inverse, mappedBy) ↔ ProductExtension::$product (inversedBy), with the extension table's FK being the host's PK. This keeps the extension row and host row in lockstep — one extension per host, by construction.Product with no extension at all. Guard every access: if ($product->getExtension()) { ... }. This is the single most common source of null-fatigue in this pattern, and it's why the bundle is better suited to a fixed set of optional fields than to "anything goes" metadata.->leftJoin('p.extension', 'e')->addSelect('e') whenever you list products in a grid or API response.cascade: ["persist"] on the inverse side if the bundle's base class doesn't already configure it — check the mapping before you add it, or you'll double-cascade. Use a Doctrine event subscriber (prePersist/preUpdate) to stamp createdAt/updatedAt on the extension if the base class doesn't.$builder->add('extension', new ProductExtensionType())) and let the entity cascade. In Laravel-speak this is the equivalent of $request->validated() for a nested relation plus $model->extension()->updateOrCreate(...).Laravel translation of the pattern: if you're evaluating this for ideas rather than direct use, the modern Laravel analogue is a HasOne extension model with a morphTo, or a JSON metadata column for truly sparse data. The bundle's separate-table approach buys you real columns, indexes, and Doctrine types; a JSON column buys you zero migrations. Choose based on how often you query/filter on the extension fields.
composer.json constraints before anything else. If it pins symfony/symfony ~2.x and php >=5.5, installing it in anything modern is a fight you will lose. Treat this as a reference implementation to read, not a dependency to ship, unless you're genuinely on Symfony2.Unknown discriminator value error, not at boot.doctrine:schema:validate and doctrine:migrations:diff will pick up the extension tables, but if the bundle ships its own mapping in Resources/config/doctrine, you can get duplicate mapping errors or tables you didn't intend to create. Run doctrine:mapping:info early to see exactly what Doctrine thinks exists.SINGLE_TABLE/JOINED inheritance on the same entity. Pick one strategy per entity hierarchy.cascade: ["persist"] on the owning side and whether the base class sets it. This bites everyone once.doctrine.dbal.logging: true in dev, or the Symfony profiler's Doctrine panel). Because the abstraction hides a join behind a property accessor, reading the executed SQL is faster than reasoning about the mapping.doctrine.event_listener services declared in Resources/config/services.yml are where the bundle hooks into Doctrine. Those are your supported extension points; anything else you override is your own risk.HasOne/morphOne plus a small trait — you'll get the same "extend without touching the base schema" benefit in an afternoon, and you'll own the code.How can I help you explore Laravel packages today?