@@ -58,6 +58,37 @@ public static function generateLazyGhost(\ReflectionClass $class): string
5858 throw new LogicException (\sprintf ('Cannot generate lazy ghost: class "%s" extends "%s" which is internal. ' , $ class ->name , $ parent ->name ));
5959 }
6060 }
61+
62+ $ hooks = '' ;
63+ $ propertyScopes = Hydrator::$ propertyScopes [$ class ->name ] ??= Hydrator::getPropertyScopes ($ class ->name );
64+ foreach ($ propertyScopes as $ name => $ scope ) {
65+ if (!isset ($ scope [4 ]) || ($ p = $ scope [3 ])->isVirtual ()) {
66+ continue ;
67+ }
68+
69+ $ type = self ::exportType ($ p );
70+ $ hooks .= "\n public {$ type } \${$ name } { \n" ;
71+
72+ foreach ($ p ->getHooks () as $ hook => $ method ) {
73+ if ($ method ->isFinal ()) {
74+ throw new LogicException (sprintf ('Cannot generate lazy ghost: hook "%s::%s()" is final. ' , $ class ->name , $ method ->name ));
75+ }
76+
77+ if ('get ' === $ hook ) {
78+ $ ref = ($ method ->returnsReference () ? '& ' : '' );
79+ $ hooks .= " {$ ref }get { \$this->initializeLazyObject(); return parent:: \${$ name }::get(); } \n" ;
80+ } elseif ('set ' === $ hook ) {
81+ $ parameters = self ::exportParameters ($ method , true );
82+ $ arg = '$ ' .$ method ->getParameters ()[0 ]->name ;
83+ $ hooks .= " set( {$ parameters }) { \$this->initializeLazyObject(); parent:: \${$ name }::set( {$ arg }); } \n" ;
84+ } else {
85+ throw new LogicException (sprintf ('Cannot generate lazy ghost: hook "%s::%s()" is not supported. ' , $ class ->name , $ method ->name ));
86+ }
87+ }
88+
89+ $ hooks .= " } \n" ;
90+ }
91+
6192 $ propertyScopes = self ::exportPropertyScopes ($ class ->name );
6293
6394 return <<<EOPHP
@@ -66,7 +97,7 @@ public static function generateLazyGhost(\ReflectionClass $class): string
6697 use \Symfony\Component\VarExporter\LazyGhostTrait;
6798
6899 private const LAZY_OBJECT_PROPERTY_SCOPES = {$ propertyScopes };
69- }
100+ { $ hooks } }
70101
71102 // Help opcache.preload discover always-needed symbols
72103 class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
@@ -95,14 +126,74 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
95126 throw new LogicException (\sprintf ('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly. ' , $ class ->name ));
96127 }
97128
129+ $ hookedProperties = [];
130+ if (\PHP_VERSION_ID >= 80400 && $ class ) {
131+ $ propertyScopes = Hydrator::$ propertyScopes [$ class ->name ] ??= Hydrator::getPropertyScopes ($ class ->name );
132+ foreach ($ propertyScopes as $ name => $ scope ) {
133+ if (isset ($ scope [4 ]) && !($ p = $ scope [3 ])->isVirtual ()) {
134+ $ hookedProperties [$ name ] = [$ p , $ p ->getHooks ()];
135+ }
136+ }
137+ }
138+
98139 $ methodReflectors = [$ class ?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED ) ?? []];
99140 foreach ($ interfaces as $ interface ) {
100141 if (!$ interface ->isInterface ()) {
101142 throw new LogicException (\sprintf ('Cannot generate lazy proxy: "%s" is not an interface. ' , $ interface ->name ));
102143 }
103144 $ methodReflectors [] = $ interface ->getMethods ();
145+
146+ if (\PHP_VERSION_ID >= 80400 && !$ class ) {
147+ foreach ($ interface ->getProperties () as $ p ) {
148+ $ hookedProperties [$ p ->name ] ??= [$ p , []];
149+ $ hookedProperties [$ p ->name ][1 ] += $ p ->getHooks ();
150+ }
151+ }
152+ }
153+
154+ $ hooks = '' ;
155+ foreach ($ hookedProperties as $ name => [$ p , $ methods ]) {
156+ $ type = self ::exportType ($ p );
157+ $ hooks .= "\n public {$ type } \${$ p ->name } { \n" ;
158+
159+ foreach ($ methods as $ hook => $ method ) {
160+ if ($ method ->isFinal ()) {
161+ throw new LogicException (sprintf ('Cannot generate lazy proxy: hook "%s::%s()" is final. ' , $ class ->name , $ method ->name ));
162+ }
163+
164+ if ('get ' === $ hook ) {
165+ $ ref = ($ method ->returnsReference () ? '& ' : '' );
166+ $ hooks .= <<<EOPHP
167+ {$ ref }get {
168+ if (isset( \$this->lazyObjectState)) {
169+ return ( \$this->lazyObjectState->realInstance ??= ( \$this->lazyObjectState->initializer)())-> {$ p ->name };
170+ }
171+
172+ return parent:: \${$ p ->name }::get();
173+ }
174+
175+ EOPHP ;
176+ } elseif ('set ' === $ hook ) {
177+ $ parameters = self ::exportParameters ($ method , true );
178+ $ arg = '$ ' .$ method ->getParameters ()[0 ]->name ;
179+ $ hooks .= <<<EOPHP
180+ set( {$ parameters }) {
181+ if (isset( \$this->lazyObjectState)) {
182+ \$this->lazyObjectState->realInstance ??= ( \$this->lazyObjectState->initializer)();
183+ \$this->lazyObjectState->realInstance-> {$ p ->name } = {$ arg };
184+ }
185+
186+ parent:: \${$ p ->name }::set( {$ arg });
187+ }
188+
189+ EOPHP ;
190+ } else {
191+ throw new LogicException (sprintf ('Cannot generate lazy proxy: hook "%s::%s()" is not supported. ' , $ class ->name , $ method ->name ));
192+ }
193+ }
194+
195+ $ hooks .= " } \n" ;
104196 }
105- $ methodReflectors = array_merge (...$ methodReflectors );
106197
107198 $ extendsInternalClass = false ;
108199 if ($ parent = $ class ) {
@@ -112,6 +203,7 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
112203 }
113204 $ methodsHaveToBeProxied = $ extendsInternalClass ;
114205 $ methods = [];
206+ $ methodReflectors = array_merge (...$ methodReflectors );
115207
116208 foreach ($ methodReflectors as $ method ) {
117209 if ('__get ' !== strtolower ($ method ->name ) || 'mixed ' === ($ type = self ::exportType ($ method ) ?? 'mixed ' )) {
@@ -228,7 +320,7 @@ public function __unserialize(\$data): void
228320 {$ lazyProxyTraitStatement }
229321
230322 private const LAZY_OBJECT_PROPERTY_SCOPES = {$ propertyScopes };
231- {$ body }}
323+ {$ hooks }{ $ body }}
232324
233325 // Help opcache.preload discover always-needed symbols
234326 class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
@@ -238,7 +330,7 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
238330 EOPHP ;
239331 }
240332
241- public static function exportSignature (\ReflectionFunctionAbstract $ function , bool $ withParameterTypes = true , ?string &$ args = null ): string
333+ public static function exportParameters (\ReflectionFunctionAbstract $ function , bool $ withParameterTypes = true , ?string &$ args = null ): string
242334 {
243335 $ byRefIndex = 0 ;
244336 $ args = '' ;
@@ -268,8 +360,15 @@ public static function exportSignature(\ReflectionFunctionAbstract $function, bo
268360 $ args = implode (', ' , $ args );
269361 }
270362
363+ return implode (', ' , $ parameters );
364+ }
365+
366+ public static function exportSignature (\ReflectionFunctionAbstract $ function , bool $ withParameterTypes = true , ?string &$ args = null ): string
367+ {
368+ $ parameters = self ::exportParameters ($ function , $ withParameterTypes , $ args );
369+
271370 $ signature = 'function ' .($ function ->returnsReference () ? '& ' : '' )
272- .($ function ->isClosure () ? '' : $ function ->name ).'( ' .implode ( ' , ' , $ parameters) .') ' ;
371+ .($ function ->isClosure () ? '' : $ function ->name ).'( ' .$ parameters .') ' ;
273372
274373 if ($ function instanceof \ReflectionMethod) {
275374 $ signature = ($ function ->isPublic () ? 'public ' : ($ function ->isProtected () ? 'protected ' : 'private ' ))
0 commit comments