Open mpdude opened 1 year ago
I'd like to invite to this discussion
Thank you for this issue. There has not been a lot of activity here for a while. Has this been resolved?
To my knowledge no, Sir
stumbled over this again and read through your research. good research job!
its been quite a while with the compiler pass i contributed. that was part of the symfony CMF effort. we had that issue of bundles providing entities (or documents for phpcr-odm) and wanting those to be registered (or a more complicated one in RoutingBundle).
afaik that also led to the prepend configuration mechanism that you mentioned.
its been quite a while, I don't remember exactly why things ended up exactly like this. i have vague recollections that i believed you could overwrite the mapping xml like templates but that can either be a misunderstanding or the functionality was removed at some point.
today, i am also less convinced that it would be a good idea to allow too much tightly coupled overriding for entities, as it will be rather fragile. in the CMF we provided storage-agnostic model classes that are then extended by entity or document classes that also provide the mapping for fields of the base model (see symfony-cmf/content-bundle for an example). to allow for customization, i would today build the bundle to base on interfaces, provide a default implementation but allow to plug your custom entity as long as it implements the interface. the approach with a base agnostic model class would still make sense in such a scenario
I am faced with the question whether (and if so, how) it is possible to override the Doctrine ORM mapping configuration for entities provided in a bundle. In the most simple case, this means being able to re-configure e. g. the table or column names being used for an entity.
The documentation on this topic is sparse, slightly contradictory and recommendations possibly incomplete.
I would like to discuss our options here, get some guidance and be reminded of parts of the puzzle I probably miss. Once we get to conclusions, I'd be happy to prepare PR(s) with suggested updates.
Information, pointers and ideas from Symfony Documentation and related PRs/issues
The "Best Practices for Reusable Bundles" (emphasis mine) says:
Source: https://symfony.com/doc/current/bundles/best_practices.html#doctrine-entities-documents
Obviously, annotations/attributes live directly within the code and cannot easily be replaced from outside a bundle.
XML mapping configuration takes some extra effort (IMHO), but that would make sense if we get extra configuration flexibility from it.
But then, "the standard Symfony mechanism to override bundle parts" links to this:
Source: https://symfony.com/doc/current/bundles/override.html#entities-entity-mapping
That particular text was written in #14152. Before, that section was phrased as:
This previous version was written in #10053, based on the request in #7076.
It is not exactly correct, at least I don't see where and how DoctrineBundle would honor the override mechanism and prefer config files found through it. In other words, you cannot use a mechanism as there is for templates to drop in the replacement configuration.
The suggestion in #7076, however, was that it is indeed possible to overwrite the
doctrine.orm.mappings
configuration for a particular bundle in its entirety to point to a different set of mapping configuration files. The OP in #7076 provides an example.Using Compiler passes to set up mapping configuration
The reason why #14152 dismissed the suggestion from #7076 was
It then goes on suggesting that bundles should use a dedicated compiler pass on their own (example given: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/cf7fe27b2f4e1f298ee6eadf537267f8c9f9b85c/FOSUserBundle.php#L50) to set up their ORM mappings. This would make them completely independent of
doctrine.orm.mappings
settings.Regarding the compiler pass, I was surprised to learn about it, since I have never seen it mentioned anywhere before. In fact, the
RegisterMappingsPass
base class is part of the Symfony Doctrine Bridge since v2.3 (https://github.com/symfony/symfony/commit/099fd9f52cbaafc3faa28fa6814caf8c9460a708).This compiler pass will hook up an extra XML, annotations or other Doctrine mapping driver in the
MappingDriverChain
, after the mapping drivers configured by thedoctrine.orm.mappings
configuration. Since theMappingDriverChain
works per namespace, such compiler passes (and compiler passes in general) win over configuration settings. This is pointed out correctly in https://github.com/symfony/symfony-docs/pull/14152#issue-689079428 as well; mapping configuration made by such a compiler pass cannot be overwritten by configuration, only by another compiler pass that comes afterwards.I agree that bundles should not rely on
auto_mapping
being set. I do not, however, share the assessment that #7076 only worked due toauto_mapping
being used.I think it is possible for a bundle to explicitly register its mapping _as if
auto_mapping
were turned on_, but without resorting to compiler passes. The solution is "prepending configuration", as outlined in the next section.Alternative: Prepending Configuration
As an alternative to using the compiler pass, a bundle might also use the
PrependExtensionInterface
and the mechanism described in https://symfony.com/doc/current/bundles/prepend_extension.html#prepending-extension-in-the-bundle-class to provide configuration for DoctrineBundle.To give an example, a bundle could have the following in its DependencyInjection/Extension class:
This explicitly provides the same configuration that would be created automatically if
auto_mapping
were set totrue
. Mapping configuration will be loaded from XML files inResources/config/doctrine
relative to the bundle class.The application can, however, still overwrite this configuration:
The bottom line is that application configuration completely overwrites the default configuration provided by the bundle. There are no duplicate definitions competing in the
MappingDriverChain
; the configuration is merged before the mapping driver is configured.Alternative: Intercepting Doctrine ORM events
You can intercept "metadata load" events in Doctrine ORM and use that to re-write/modify mapping configuration just in time.
Definetly not something I would recommend in the official documentation, and this puts metadata configuration in really unexpected places (even subscribers/listeners).
Just mentioned for completeness, since this is what search engine results brought up. Seems to work for some people.
Alternative: Mapped Superclasses
The docs mention "mapped superclasses" in case a bundle wants to support overriding attributes and associations. There is a reference to the limitations listed in the Doctrine documentation, but apart from that, it does not further elaborate that path.
In fact, this is very limited, since overrides can only be applied in entity classes that inherit from mapped superclasses, to re-configure parts of the mapping configuration inherited. It cannot be used together with entity inheritance hierachies.
Mapped superclasses mean that a bundle cannot work "out of the box", since it does not provide any ready-to-use entities. Users must completely subclass all those superclasses to create their own entities from it.
Additionally, the bundle needs to provide extra means to make the actual classes that shall be used configurable. For example, when code in the bundle wants to create a new
User
entity, but that class is a mapped superclass, which class should be instantiated instead? How do we know about the appropriate constructor?Also, when associations are defined between entities or superclasses, extra configuration is necessary so that Doctrine can know about the actual classes to be used at runtime.
Subclassing either entity or mapped superclasses for the sake of overriding mapping configuration through annotations or attributes requires fields to be
protected
instead ofprivate
. In that case, it leads to code duplication and/or inheritance-based designs only to create places where mapping configuration can be overwritten.For these reasons, I think mapped superclasses are not a general solution to the problem. We should not suggest using them, at least not as the recommended way to make the mapping more configurable.
Side issue: Explicit naming configuration vs. Doctrine naming strategy
A side question I'd like to see answered is whether we should recommend bundle authors to avoid using
name="..."
configurations for tables and columns (e. g. in@ORM\Column
and@ORM\Table
annotations).Explicit configuration makes the database table and column names predictable, but might work against the particular Doctrine Naming Strategy chosen by the project/application. This may lead to inconsistent naming rules being used.