You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Known-issue] Doctrine entities could not be woven #327
Doctrine entities can not be weaved - class metadata gets all messed up.
Engine generates Entity__AopProxied, which is copy of original class, and then generates class Entity that extends Entity__AopProxied and contains weaved methods/properties.
Issue is that Entity__AopProxied contains doctrine metadata, so Doctrine actually generates such metadata that Entity__AopProxied is main class and every relation is bound to that class, not Entity.
Side effects are that, per example, table name gets suffix "__aop_proixed", while relations gets all screwed up, so any join is broken because different aliases are generated with SQL walker. Lazy loading does not work as well.
Possible solution would be (in order to preserve BC compatibility) to provide possibility to specify different method of weaving for certain classes. Per example, in this case, in order to work as it should, Entity__AopProxied should not be generated and inherited, it should be generated only Entity class.
Yes, maybe we will loose nice debugging and breakpoints, however, for now, we can sacrifice that for working code.
Tried to use different mapping method - just for testing purposes, no love. Which is reasonable, Entity__AopProxied is not valid superclass....
Here is the example of wrong SQL:
An exception occurred while executing 'SELECT t1.id AS id_2, t1.feed_item_id AS feed_item_id_3, t1.url AS url_4, t1.platform AS platform_5, t1.views AS views_6, t1.likes AS likes_7, t1.dislikes AS dislikes_8, t1.comments AS comments_9, t1.shares AS shares_10, t1.created_at AS created_at_11, t1.updated_at AS updated_at_12, t1.created_by AS created_by_13, t1.updated_by AS updated_by_14, t1.offer_id AS offer_id_15 FROM report_item t1 WHERE t0.offer_id = ?'
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.offer_id' in 'where clause'").
Note how generated alias for report_item is t1, but for where statement is t0 ? This is simple, lazy loaded relation.
Ok, this does not allow me to sleep at all. By playing around, going trough code, I think that there is a workaround in for this situation, however, Doctrine mapping must be modified (tried with hardcoding just to test theory, works).
When Entity__AopProxied class is generated, its mapping data must exists and must be appropriately set. It should contain all Entity mapping data, with exception, it must be set as MappedSuperclass (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses). In that matter Entity__AopProxied would become a mapping superclass, while generated Entity would have all required metadata as concrete class with mapping info from superclass.
It is not just simple "replace annotation" process, Doctrine supports mapping via XML, YML, annotations and PHP...
So, in order to do so successfully, we have to find out a certain point of time, or a method, that we can modify, intercept, whatever, so we can do this modification.
Maybe @Ocramius, @beberlei (or someone else) could give us some pointers or advices how to do this properly...?
I don't think I want to go there with the ORM: we are already doing our
magic, and then you want to add a layer of it on top. This will get out of
hand extremely quickly.
On 8 Jul 2017 3:44 AM, "Nikola Svitlica" ***@***.***> wrote:
Ok, this does not allow me to sleep at all. By playing around, going
trough code, I think that there is a workaround in for this situation,
however, Doctrine mapping must be modified (tried with hardcoding just to
test theory, works).
When Entity__AopProxied class is generated, its mapping data must exists
and must be appropriately set. It should contain all Entity mapping data,
with exception, it must be set as MappedSuperclass (see
http://docs.doctrine-project.org/projects/doctrine-orm/en/
latest/reference/inheritance-mapping.html#mapped-superclasses). In that
matter Entity__AopProxied would become a mapping superclass, while
generated Entity would have all required metadata as concrete class with
mapping info from superclass.
It is not just simple "replace annotation" process, Doctrine supports
mapping via XML, YML, annotations and PHP...
So, in order to do so successfully, we have to find out a certain point of
time, or a method, that we can modify, intercept, whatever, so we can do
this modification.
Maybe @Ocramius <https://github.com/ocramius>, @beberlei
<https://github.com/beberlei> (or someone else) could give us some
pointers or advices how to do this properly...?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#327 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJakIAEC7hRrn_jRgTcg1LcLglTtj3Cks5sLt7mgaJpZM4ORdkS>
.
@Ocramius not that I have that choice, either way, it seams as easy to solve/add support for doctrine entities.
Only thing required is to modify ClassMetadata for __AopProxied entity. For what I have seen, it should be an easy pick with Doctrine's loadclassmetadata event - just to set isMappedSupperclass to true.
I will try that approach today, I think that it will work.
@lisachenko Ok, got it, here is a bastebin of listener that could handle this mapping issue with Doctrine entities which are weaved: https://pastebin.com/LwdJ0hXn
It gives a basis support, it works with my use case scenario, but in general, it is quite possible, Doctrine seams very flexible when it comes to metadata. I would create a PR for this project, an optional listener for Doctrine that can be used, if required.
For symfony bundle, I would add a compiler pass which would determine if there is a doctrine ORM included, and if that is true, it would auto register listener.
If we are dealing with complex object graph with bidirectional relations and several inheritance, metadata hierarchy gets really, really complex and things tends to go bad...
It seams that Doctrine entities could not be supported by manipulating metadata... I will have to investigate further more in regards to this topic....
FYI, there are still some issues with the workaround. I was experiencing the same t0/t1 issue in the requests, I added the workaround, it worked fine with most of the cases, but I still got an error with some entity relationships.
EntityA is representing sensors and EntityB is representing past sensor values. EntityB has a ManyToOne relation to EntityA, which works fine. But if I add a OneToMany relationship from EntityA to EntityB I get this error :
[Doctrine\ORM\Mapping\MappingException]
It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass 'Appli\Metier\Bundle\Entity\EntityB__AopProxied#pastValues'.
I'm using Go AOP to add execution traces around all the methods in the Appli namespace, using this annotation in my aspect: @Around("execution(public|protected Appli\**->*(*))")
If I disable this annotation, everything works fine with the OneToMany relationship between EntityA and EntityB.
Hi, @MatthieuSarter, if you don't need AOP logic for entities, then you could easily exclude all of them from weaving process by adding logical restriction into the pointcut:
This is the oldest open issue, so it was re-verified against the current master instead of being closed on assumption. Summary: the bug described here no longer exists in 4.0. The reproduction below and the regression test in #773 cover every failure mode named in this thread.
Root cause, for the record
Everything reported here traces back to one thing in the 3.x extend-based engine: the proxy was a subclass of a renamed copy of the entity, Entity__AopProxied, and the mapping annotations/attributes stayed on that renamed parent. Doctrine therefore mapped the parent class:
table name with the __aop_proxied suffix;
every association bound to the parent class, not to Entity;
lazy-load SQL with mismatched aliases (FROM report_item t1 WHERE t0.offer_id = ? in the second comment), because the association metadata belonged to one class and the hydrated object to another.
The mapped-superclass listener from commit 6e2a0fe (the 3.x Go\Bridge\Doctrine\MetadataLoadInterceptor, based on the pastebin in this thread) worked around it by flagging Entity__AopProxied as a mapped superclass, clearing its table and repository class, and stripping field mappings imported from traits (Gedmo Timestampable/Blameable) so they would not be mapped twice. It hit a hard limit that could not be worked around: Doctrine refuses an inverse OneToMany/ManyToMany on a mapped superclass, which is the MappingException reported by @MatthieuSarter in 2018. Complex object graphs with bidirectional relations and inheritance went bad for the same reason. #334 (woven entities missing from doctrine:schema:update) was folded into this issue in 2019.
Why it is gone in 4.0
The trait-based engine does not rename anything. The woven class keeps its own name, its parent, its interfaces and all of its attributes; the original method bodies move into EntityOriginalTrait, which Doctrine never maps because it is a trait. There is no second class carrying entity metadata, so there is nothing for Doctrine to get wrong, and no metadata surgery is needed. The bridge was rewritten accordingly in #671: MetadataLoadInterceptor no longer touches metadata (it only reports an intercepted mapped property when native lazy objects are off), and WovenEntityClassLocator lets the mapping driver discover entities through the autoloader, which is what fixes #334 (Doctrine's default locator require_onces the source files, bypassing the weaver and missing already-loaded entities). The setup is documented in the README under "Weaving Doctrine entities".
What was verified
The exact scenario from this thread, run through Doctrine ORM 3.7.5 / DBAL 4.5 against a real in-memory SQLite database with weaving active:
two woven entities Sensor and SensorValue with a bidirectional OneToMany (inverse, cascade: persist) / ManyToOne (owning, non-nullable join column);
every public method of both entities advised with execution(public ...->*(*)), i.e. the "trace everything" pointcut from the 2018 report, constructors included;
one column of Sensor mapped inside a trait it uses, the Gedmo case the 3.x listener special-cased;
entities discovered through WovenEntityClassLocator, native lazy objects enabled.
Results:
Check
Result
Both classes implement Go\Aop\Proxy (actually woven)
present in CREATE TABLE sensor, hydrated, accessor advised
Persist with cascade, flush
two INSERTs into sensor_value with the right sensor_id
DQL SELECT s, v FROM Sensor s JOIN s.values v
FROM sensor s0_ INNER JOIN sensor_value s1_ ON s0_.id = s1_.sensor_id, consistent aliases, both sides hydrated
find(SensorValue)->getSensor()
native lazy ghost of class Sensor (not a subclass), uninitialized until first access, initialized by one SELECT ... FROM sensor t0 WHERE t0.id = ?
find(Sensor)->getValues()->count()
lazy collection, one SELECT ... FROM sensor_value t0 WHERE t0.sensor_id = ?
Advices during hydration
none (Doctrine writes raw property values)
Advices for user code
every call, including __construct and addValue on cascade
Any generated SQL mentioning aop
none
All of this is now a functional test, DoctrineBridgeTest::testDoctrineMapsAssociationsBetweenWovenEntities in #773, with the fixture script tests/Fixtures/project/bin/doctrine-associations.php. It asserts the exact SQL, so a regression of the alias problem would fail it.
Practical consequences
The !within(Appli\**\*Entity*) exclusion suggested in 2018 is no longer necessary. Entities can be woven like any other class.
Two things remain required and are documented: discover entities through WovenEntityClassLocator, and enable native lazy objects if a mapped property is intercepted with access(...) (the proxy re-declares it with property hooks, which ORM supports only with native lazy objects). Method interception on entities needs nothing beyond the locator.
Doctrine's legacy generated proxies (symfony/var-exporter lazy ghosts) were not tested; they are not a dev dependency and native lazy objects are the documented mode.
Unrelated findings made on the way
Noted here only because they surfaced during this check; they are pointcut matching, not Doctrine:
execution(public Ns\Sensor*->*(*)) matches SensorValue but notSensor itself: NamePointcut compiles * to [^\\]+? (one or more characters), which contradicts the "any part of a name" wording in docs/pointcuts.md. Behaviour dates back to 2013.
execution(public Ns\Sensor|SensorValue->*(*)) means Ns\Sensoror a global classSensorValue: the | alternation spans the whole qualified name, while the docs promise alternatives "within one name part". Either the docs or the regex should change.
Activity
TheCelavi commentedon Jul 7, 2017
Note - I am using annotations to map entities.
TheCelavi commentedon Jul 7, 2017
Tried to use different mapping method - just for testing purposes, no love. Which is reasonable, Entity__AopProxied is not valid superclass....
Here is the example of wrong SQL:
An exception occurred while executing 'SELECT t1.id AS id_2, t1.feed_item_id AS feed_item_id_3, t1.url AS url_4, t1.platform AS platform_5, t1.views AS views_6, t1.likes AS likes_7, t1.dislikes AS dislikes_8, t1.comments AS comments_9, t1.shares AS shares_10, t1.created_at AS created_at_11, t1.updated_at AS updated_at_12, t1.created_by AS created_by_13, t1.updated_by AS updated_by_14, t1.offer_id AS offer_id_15 FROM report_item t1 WHERE t0.offer_id = ?'
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.offer_id' in 'where clause'").
Note how generated alias for report_item is t1, but for where statement is t0 ? This is simple, lazy loaded relation.
TheCelavi commentedon Jul 8, 2017
Ok, this does not allow me to sleep at all. By playing around, going trough code, I think that there is a workaround in for this situation, however, Doctrine mapping must be modified (tried with hardcoding just to test theory, works).
When
Entity__AopProxiedclass is generated, its mapping data must exists and must be appropriately set. It should contain allEntitymapping data, with exception, it must be set as MappedSuperclass (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses). In that matterEntity__AopProxiedwould become a mapping superclass, while generatedEntitywould have all required metadata as concrete class with mapping info from superclass.It is not just simple "replace annotation" process, Doctrine supports mapping via XML, YML, annotations and PHP...
So, in order to do so successfully, we have to find out a certain point of time, or a method, that we can modify, intercept, whatever, so we can do this modification.
Maybe @Ocramius, @beberlei (or someone else) could give us some pointers or advices how to do this properly...?
Ocramius commentedon Jul 8, 2017
TheCelavi commentedon Jul 8, 2017
@Ocramius not that I have that choice, either way, it seams as easy to solve/add support for doctrine entities.
Only thing required is to modify ClassMetadata for __AopProxied entity. For what I have seen, it should be an easy pick with Doctrine's loadclassmetadata event - just to set isMappedSupperclass to true.
I will try that approach today, I think that it will work.
TheCelavi commentedon Jul 8, 2017
@lisachenko Ok, got it, here is a bastebin of listener that could handle this mapping issue with Doctrine entities which are weaved: https://pastebin.com/LwdJ0hXn
It gives a basis support, it works with my use case scenario, but in general, it is quite possible, Doctrine seams very flexible when it comes to metadata. I would create a PR for this project, an optional listener for Doctrine that can be used, if required.
For symfony bundle, I would add a compiler pass which would determine if there is a doctrine ORM included, and if that is true, it would auto register listener.
Feedback?
Added workaround for goaop#327
lisachenko commentedon Jul 10, 2017
@TheCelavi this issue is very cumbersome, yes. There is no place for double magic (goaop + doctrine), so yes, unicorns are expected )
Just to check one more idea, could we somehow use
Proxies\__CG__namespace to make doctrine trust our generated entities? /cc @OcramiusImplement workaround for Doctrine entities with metadata listener #327
lisachenko commentedon Jul 12, 2017
Closed via 6e2a0fe
TheCelavi commentedon Jul 18, 2017
If we are dealing with complex object graph with bidirectional relations and several inheritance, metadata hierarchy gets really, really complex and things tends to go bad...
It seams that Doctrine entities could not be supported by manipulating metadata... I will have to investigate further more in regards to this topic....
This issue ought to be open.
MatthieuSarter commentedon Jan 31, 2018
Hi,
FYI, there are still some issues with the workaround. I was experiencing the same t0/t1 issue in the requests, I added the workaround, it worked fine with most of the cases, but I still got an error with some entity relationships.
EntityA is representing sensors and EntityB is representing past sensor values. EntityB has a ManyToOne relation to EntityA, which works fine. But if I add a OneToMany relationship from EntityA to EntityB I get this error :
I'm using Go AOP to add execution traces around all the methods in the Appli namespace, using this annotation in my aspect:
@Around("execution(public|protected Appli\**->*(*))")If I disable this annotation, everything works fine with the OneToMany relationship between EntityA and EntityB.
lisachenko commentedon Jan 31, 2018
Hi, @MatthieuSarter, if you don't need AOP logic for entities, then you could easily exclude all of them from weaving process by adding logical restriction into the pointcut:
Idea is to exclude all class entities by their namespace or
Entitysuffix in the name.MatthieuSarter commentedon Jan 31, 2018
Thanks @lisachenko !
It works fine this way :) And I can live with the fact of not having execution traces on the entities methods :)
[-]Doctrine entities can not be weaved [/-][+][Known-issue] Doctrine entities could not be woven [/+]lisachenko commentedon Oct 22, 2026
Re-check against 4.0 (October 2026)
This is the oldest open issue, so it was re-verified against the current
masterinstead of being closed on assumption. Summary: the bug described here no longer exists in 4.0. The reproduction below and the regression test in #773 cover every failure mode named in this thread.Root cause, for the record
Everything reported here traces back to one thing in the 3.x extend-based engine: the proxy was a subclass of a renamed copy of the entity,
Entity__AopProxied, and the mapping annotations/attributes stayed on that renamed parent. Doctrine therefore mapped the parent class:__aop_proxiedsuffix;Entity;FROM report_item t1 WHERE t0.offer_id = ?in the second comment), because the association metadata belonged to one class and the hydrated object to another.The mapped-superclass listener from commit 6e2a0fe (the 3.x
Go\Bridge\Doctrine\MetadataLoadInterceptor, based on the pastebin in this thread) worked around it by flaggingEntity__AopProxiedas a mapped superclass, clearing its table and repository class, and stripping field mappings imported from traits (GedmoTimestampable/Blameable) so they would not be mapped twice. It hit a hard limit that could not be worked around: Doctrine refuses an inverseOneToMany/ManyToManyon a mapped superclass, which is theMappingExceptionreported by @MatthieuSarter in 2018. Complex object graphs with bidirectional relations and inheritance went bad for the same reason. #334 (woven entities missing fromdoctrine:schema:update) was folded into this issue in 2019.Why it is gone in 4.0
The trait-based engine does not rename anything. The woven class keeps its own name, its parent, its interfaces and all of its attributes; the original method bodies move into
EntityOriginalTrait, which Doctrine never maps because it is a trait. There is no second class carrying entity metadata, so there is nothing for Doctrine to get wrong, and no metadata surgery is needed. The bridge was rewritten accordingly in #671:MetadataLoadInterceptorno longer touches metadata (it only reports an intercepted mapped property when native lazy objects are off), andWovenEntityClassLocatorlets the mapping driver discover entities through the autoloader, which is what fixes #334 (Doctrine's default locatorrequire_onces the source files, bypassing the weaver and missing already-loaded entities). The setup is documented in the README under "Weaving Doctrine entities".What was verified
The exact scenario from this thread, run through Doctrine ORM 3.7.5 / DBAL 4.5 against a real in-memory SQLite database with weaving active:
SensorandSensorValuewith a bidirectionalOneToMany(inverse,cascade: persist) /ManyToOne(owning, non-nullable join column);execution(public ...->*(*)), i.e. the "trace everything" pointcut from the 2018 report, constructors included;Sensormapped inside a trait it uses, the Gedmo case the 3.x listener special-cased;WovenEntityClassLocator, native lazy objects enabled.Results:
Go\Aop\Proxy(actually woven)sensor,sensor_value, no suffixSensor.values -> SensorValue,SensorValue.sensor -> SensorCREATE TABLE sensor, hydrated, accessor advisedINSERTs intosensor_valuewith the rightsensor_idSELECT s, v FROM Sensor s JOIN s.values vFROM sensor s0_ INNER JOIN sensor_value s1_ ON s0_.id = s1_.sensor_id, consistent aliases, both sides hydratedfind(SensorValue)->getSensor()Sensor(not a subclass), uninitialized until first access, initialized by oneSELECT ... FROM sensor t0 WHERE t0.id = ?find(Sensor)->getValues()->count()SELECT ... FROM sensor_value t0 WHERE t0.sensor_id = ?__constructandaddValueon cascadeaopAll of this is now a functional test,
DoctrineBridgeTest::testDoctrineMapsAssociationsBetweenWovenEntitiesin #773, with the fixture scripttests/Fixtures/project/bin/doctrine-associations.php. It asserts the exact SQL, so a regression of the alias problem would fail it.Practical consequences
!within(Appli\**\*Entity*)exclusion suggested in 2018 is no longer necessary. Entities can be woven like any other class.WovenEntityClassLocator, and enable native lazy objects if a mapped property is intercepted withaccess(...)(the proxy re-declares it with property hooks, which ORM supports only with native lazy objects). Method interception on entities needs nothing beyond the locator.symfony/var-exporterlazy ghosts) were not tested; they are not a dev dependency and native lazy objects are the documented mode.Unrelated findings made on the way
Noted here only because they surfaced during this check; they are pointcut matching, not Doctrine:
execution(public Ns\Sensor*->*(*))matchesSensorValuebut notSensoritself:NamePointcutcompiles*to[^\\]+?(one or more characters), which contradicts the "any part of a name" wording indocs/pointcuts.md. Behaviour dates back to 2013.execution(public Ns\Sensor|SensorValue->*(*))meansNs\Sensoror a global classSensorValue: the|alternation spans the whole qualified name, while the docs promise alternatives "within one name part". Either the docs or the regex should change.Closing this once #773 is merged.
lisachenko commentedon Oct 22, 2026
Finally, I’ve closed this never-ending issue.