Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Entity Override Bundle Laravel Package

joschi127/doctrine-entity-override-bundle

A Symfony bundle that lets you override Doctrine entities and their mappings from third-party bundles without touching vendor code. Extend or replace entity classes, customize mappings, and keep your changes clean and upgrade-safe—no more forking vendor packages.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and register the bundle

composer require joschi127/doctrine-entity-override-bundle

The bundle predates Symfony Flex recipes for most setups, so add it manually to config/bundles.php:

Joschi127\DoctrineEntityOverrideBundle\DoctrineEntityOverrideBundle::class => ['all' => true],

2. Read the metadata listener before anything else

Open vendor/joschi127/doctrine-entity-override-bundle/ and read the class that subscribes to Doctrine's loadClassMetadata event. That single class is the whole bundle: it turns the overridden parent entity into a MappedSuperclass and rewrites every targetEntity reference pointing at the parent so it points at your subclass instead. Understanding that one file answers 90% of the "why is my mapping wrong" questions later.

3. Declare the override

# config/packages/doctrine_entity_override.yaml
doctrine_entity_override:
    entities:
        'Vendor\ShopBundle\Entity\Product': 'App\Entity\Product'

Confirm the exact key name (entities vs. a list of pairs) against the README for the tag you install — the bundle was last released in 2019 and the config shape is the thing most likely to have drifted from blog posts you find.

4. Write the subclass

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Vendor\ShopBundle\Entity\Product as BaseProduct;

/**
 * @ORM\Entity(repositoryClass=ProductRepository::class)
 * @ORM\Table(name="vendor_product")
 */
class Product extends BaseProduct
{
    /** @ORM\Column(type="string", length=64, nullable=true) */
    private ?string $internalSku = null;

    public function getInternalSku(): ?string { return $this->internalSku; }
    public function setInternalSku(?string $sku): self { $this->internalSku = $sku; return $this; }
}

5. Your first sanity check: diff the schema

bin/console cache:clear
bin/console doctrine:schema:update --dump-sql

The correct outcome is: one new column on the existing vendor table, and no second table created. If you instead see a brand-new table, the override didn't apply — usually stale metadata cache or a wrong FQCN in the map.

First real use case: a third-party bundle ships Product with no sku field, you need sku, and you don't want to fork the bundle. This is exactly the scenario the package exists for — additive fields on a vendor entity, reusing the same table and the same rows.

Implementation Patterns

Additive field extension (the 80% case). Subclass, add nullable columns, inherit everything else. Keep the parent's table name explicitly via @ORM\Table(name="...") so you don't depend on inheritance rules to land on the right table. Do not redeclare @ORM\Id — the parent's ID mapping is inherited.

Rewriting relations transparently. The bundle's real value is that you don't have to touch the vendor's other entities. If Vendor\Order has @ORM\ManyToOne(targetEntity="Product"), the listener rewrites it to App\Entity\Product. Verify with:

bin/console doctrine:mapping:info
bin/console doctrine:mapping:debug Vendor\\ShopBundle\\Entity\\Product   # isMappedSuperclass should be true
bin/console doctrine:mapping:debug App\\Entity\\Product                  # fields list should include vendor fields

Overriding behaviour, not just data. Override getters to change formatting, add domain methods, or swap the repository:

class Product extends BaseProduct
{
    public function getDisplayName(): string
    {
        return $this->internalSku ?? parent::getDisplayName();
    }
}

Extending queries through the repository. Set repositoryClass on the subclass and extend the vendor repository (if it has one). Existing calls to $em->getRepository(Product::class) inside the vendor bundle now resolve to your repository once the metadata rewrite lands — a clean hook for adding scoped finders.

Migration workflow. Treat the first migration after adding an override as a review checkpoint. doctrine:migrations:diff should generate ALTER TABLE ... ADD COLUMN, never CREATE TABLE. If it creates a table, stop and fix the mapping before committing.

Test and fixture symmetry. Register the override in config/packages/test/doctrine_entity_override.yaml too. If your test kernel boots a different bundle set, the schema built for tests will silently differ from dev/prod, and you'll chase phantom failures. Rebuild the test schema in CI from scratch rather than reusing a cached one.

Layering. Type-hint against the original class in your application code, not the subclass. Because the override is a genuine inheritance relationship, instanceof Vendor\Entity\Product stays true and your services don't need to know the override exists.

Keeping it discoverable. Put overrides in a dedicated namespace (App\Entity\Override\) and add a short comment linking to the vendor class. Six months from now, nobody remembers why App\Entity\Product extends a vendor class.

Gotchas and Tips

  • The parent stops being a persistable entity. Once it's a MappedSuperclass, new Vendor\Entity\Product() and $em->persist() on it will fail. Grep the vendor bundle for direct instantiations before you commit.
  • EntityType form fields break. A form configured with 'class' => Vendor\Entity\Product::class will fail because mapped superclasses aren't queryable entities. Switch those to App\Entity\Product::class.
  • Metadata cache is the #1 false alarm. After editing the override map, always bin/console cache:clear and bin/console doctrine:cache:clear-metadata. In dev with auto_mapping this still bites people.
  • You can only reach protected/public members. A vendor entity with private properties and no setters cannot be extended meaningfully. Check the parent's visibility first — it's a five-minute check that saves a day.
  • final classes and private constructors cannot be overridden at all. Verify before designing around this bundle.
  • Column name collisions. If your new field maps to an existing column name, Doctrine throws an unhelpful mapping exception. Prefix or rename.
  • Pre-existing inheritance on the parent. If the vendor entity already uses @ORM\InheritanceType with a discriminator map, the bundle's rewrite fights with it. Test that specific entity early.
  • Association rewrites are global. Everything pointing at the parent now points at your subclass — including third-party code that assumed the original class. Semantically fine (it's a subtype), but surprising in get_class() comparisons and serialized payloads.
  • Don't redeclare the ID or table unless you must. Redundant @ORM\Id on the child produces confusing duplicate-column errors.
  • The package is effectively unmaintained (last release 2019, 7 stars, no dependents). Pin the version, and be prepared to patch the listener yourself if a newer Doctrine changes the loadClassMetadata API. Vendor it if your release process can't tolerate an abandoned dependency.
  • Consider the alternatives first. If the vendor bundle exposes an interface anywhere, Doctrine\ORM\Tools\ResolveTargetEntityListener is the first-party mechanism. This bundle is the right tool only when the vendor entity is a concrete class you can't change.
  • Extension point: the bundle is a thin Doctrine event subscriber. If you need custom rewrite logic (e.g., only rewrite associations in one bundle), decorate or replace it with your own loadClassMetadata listener registered at a lower priority rather than forking.
  • Treat it as a stopgap. Every override is technical debt accruing against the upstream package. Track them in one file, review on every vendor upgrade, and push the field upstream when you can.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast