@@ -58,6 +58,37 @@ public static function generateLazyGhost(\ReflectionClass $class): string
58
58
throw new LogicException (sprintf ('Cannot generate lazy ghost: class "%s" extends "%s" which is internal. ' , $ class ->name , $ parent ->name ));
59
59
}
60
60
}
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
+
61
92
$ propertyScopes = self ::exportPropertyScopes ($ class ->name );
62
93
63
94
return <<<EOPHP
@@ -66,7 +97,7 @@ public static function generateLazyGhost(\ReflectionClass $class): string
66
97
use \Symfony\Component\VarExporter\LazyGhostTrait;
67
98
68
99
private const LAZY_OBJECT_PROPERTY_SCOPES = {$ propertyScopes };
69
- }
100
+ { $ hooks } }
70
101
71
102
// Help opcache.preload discover always-needed symbols
72
103
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
@@ -95,14 +126,74 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
95
126
throw new LogicException (sprintf ('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly. ' , $ class ->name ));
96
127
}
97
128
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
+
98
139
$ methodReflectors = [$ class ?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED ) ?? []];
99
140
foreach ($ interfaces as $ interface ) {
100
141
if (!$ interface ->isInterface ()) {
101
142
throw new LogicException (sprintf ('Cannot generate lazy proxy: "%s" is not an interface. ' , $ interface ->name ));
102
143
}
103
144
$ 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" ;
104
196
}
105
- $ methodReflectors = array_merge (...$ methodReflectors );
106
197
107
198
$ extendsInternalClass = false ;
108
199
if ($ parent = $ class ) {
@@ -112,6 +203,7 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
112
203
}
113
204
$ methodsHaveToBeProxied = $ extendsInternalClass ;
114
205
$ methods = [];
206
+ $ methodReflectors = array_merge (...$ methodReflectors );
115
207
116
208
foreach ($ methodReflectors as $ method ) {
117
209
if ('__get ' !== strtolower ($ method ->name ) || 'mixed ' === ($ type = self ::exportType ($ method ) ?? 'mixed ' )) {
@@ -228,7 +320,7 @@ public function __unserialize(\$data): void
228
320
{$ lazyProxyTraitStatement }
229
321
230
322
private const LAZY_OBJECT_PROPERTY_SCOPES = {$ propertyScopes };
231
- {$ body }}
323
+ {$ hooks }{ $ body }}
232
324
233
325
// Help opcache.preload discover always-needed symbols
234
326
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
@@ -238,7 +330,7 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
238
330
EOPHP ;
239
331
}
240
332
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
242
334
{
243
335
$ byRefIndex = 0 ;
244
336
$ args = '' ;
@@ -268,8 +360,15 @@ public static function exportSignature(\ReflectionFunctionAbstract $function, bo
268
360
$ args = implode (', ' , $ args );
269
361
}
270
362
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
+
271
370
$ signature = 'function ' .($ function ->returnsReference () ? '& ' : '' )
272
- .($ function ->isClosure () ? '' : $ function ->name ).'( ' .implode ( ' , ' , $ parameters) .') ' ;
371
+ .($ function ->isClosure () ? '' : $ function ->name ).'( ' .$ parameters .') ' ;
273
372
274
373
if ($ function instanceof \ReflectionMethod) {
275
374
$ signature = ($ function ->isPublic () ? 'public ' : ($ function ->isProtected () ? 'protected ' : 'private ' ))
0 commit comments