1717use App \Audit \ConcreteFormatters \EntityDeletionAuditLogFormatter ;
1818use App \Audit \ConcreteFormatters \EntityUpdateAuditLogFormatter ;
1919use App \Audit \Interfaces \IAuditStrategy ;
20+ use Doctrine \ORM \PersistentCollection ;
21+ use Illuminate \Support \Facades \Log ;
2022
2123class AuditLogFormatterFactory implements IAuditLogFormatterFactory
2224{
@@ -34,14 +36,50 @@ public function make(AuditContext $ctx, $subject, $eventType): ?IAuditLogFormatt
3436 $ formatter = null ;
3537 switch ($ eventType ) {
3638 case IAuditStrategy::EVENT_COLLECTION_UPDATE :
37- $ child_entity = null ;
38- if (count ($ subject ) > 0 ) {
39- $ child_entity = $ subject [0 ];
40- }
41- if (is_null ($ child_entity ) && isset ($ subject ->getSnapshot ()[0 ]) && count ($ subject ->getSnapshot ()) > 0 ) {
42- $ child_entity = $ subject ->getSnapshot ()[0 ];
39+ Log::debug ("AuditLogFormatterFactory::make IAuditStrategy::EVENT_COLLECTION_UPDATE " ,
40+ ["subject " => get_class ($ subject )]);
41+
42+ $ child_entity_formatter = null ;
43+
44+ if ($ subject instanceof PersistentCollection) {
45+ // KEY: do NOT initialize the collection.
46+ $ targetEntity = null ;
47+
48+ // Doctrine variants:
49+ if (method_exists ($ subject , 'getTypeClass ' )) {
50+ $ targetEntity = $ subject ->getTypeClass ();
51+ Log::debug ("AuditLogFormatterFactory::make getTypeClass {$ targetEntity }" );
52+ } elseif (method_exists ($ subject , 'getMapping ' )) {
53+ $ mapping = $ subject ->getMapping ();
54+ $ targetEntity = $ mapping ['targetEntity ' ] ?? null ;
55+ Log::debug ("AuditLogFormatterFactory::make getMapping {$ targetEntity }" );
56+ } else {
57+ // last-resort: read private association metadata (still no hydration)
58+ $ ref = new \ReflectionObject ($ subject );
59+ foreach (['association ' , 'mapping ' , 'associationMapping ' ] as $ propName ) {
60+ if ($ ref ->hasProperty ($ propName )) {
61+ $ prop = $ ref ->getProperty ($ propName );
62+ $ prop ->setAccessible (true );
63+ $ mapping = $ prop ->getValue ($ subject );
64+ $ targetEntity = $ mapping ['targetEntity ' ] ?? null ;
65+ if ($ targetEntity ) break ;
66+ }
67+ }
68+ }
69+
70+ if ($ targetEntity ) {
71+ $ child_entity_formatter = ChildEntityFormatterFactory::build ($ targetEntity );
72+ }
73+
74+ } elseif (is_array ($ subject )) {
75+ $ child_entity = $ subject [0 ] ?? null ;
76+ $ child_entity_formatter = $ child_entity ? ChildEntityFormatterFactory::build ($ child_entity ) : null ;
77+ } elseif (is_object ($ subject ) && method_exists ($ subject , 'getSnapshot ' )) {
78+ $ snap = $ subject ->getSnapshot (); // only once
79+ $ child_entity = $ snap [0 ] ?? null ;
80+ $ child_entity_formatter = $ child_entity ? ChildEntityFormatterFactory::build ($ child_entity ) : null ;
4381 }
44- $ child_entity_formatter = $ child_entity != null ? ChildEntityFormatterFactory:: build ( $ child_entity ) : null ;
82+
4583 $ formatter = new EntityCollectionUpdateAuditLogFormatter ($ child_entity_formatter );
4684 break ;
4785 case IAuditStrategy::EVENT_ENTITY_CREATION :
@@ -65,6 +103,7 @@ public function make(AuditContext $ctx, $subject, $eventType): ?IAuditLogFormatt
65103 }
66104 break ;
67105 }
106+ if ($ formatter === null ) return null ;
68107 $ formatter ->setContext ($ ctx );
69108 return $ formatter ;
70109 }
@@ -73,7 +112,7 @@ private function getFormatterByContext(object $subject, string $event_type, Audi
73112 {
74113 $ class = get_class ($ subject );
75114 $ entity_config = $ this ->config ['entities ' ][$ class ] ?? null ;
76-
115+
77116 if (!$ entity_config ) {
78117 return null ;
79118 }
0 commit comments