66
77use Laminas \Code \Generator \ParameterGenerator ;
88use Laminas \Code \Generator \PropertyGenerator ;
9+ use LogicException ;
910use ProxyManager \Generator \MethodGenerator ;
1011use ProxyManager \Generator \Util \IdentifierSuffixer ;
1112use ProxyManager \Generator \ValueGenerator ;
1213use ProxyManager \ProxyGenerator \Util \Properties ;
14+ use ReflectionIntersectionType ;
15+ use ReflectionNamedType ;
1316use ReflectionProperty ;
17+ use ReflectionType ;
18+ use ReflectionUnionType ;
1419
20+ use function assert ;
1521use function array_map ;
22+ use function array_unique ;
23+ use function explode ;
24+ use function get_class ;
1625use function implode ;
1726use function sprintf ;
1827use function str_replace ;
@@ -63,42 +72,55 @@ public function __construct(
6372%s
6473
6574$result = $this->%s->__invoke($this, $methodName, $parameters, $this->%s, $properties);
66- $this->%s = false;
75+ %s $this->%s = false;
6776
6877return $result;
6978PHP;
7079
71- $ referenceableProperties = $ properties ->withoutNonReferenceableProperties ();
80+ $ referenceableProperties = $ properties ->withoutNonReferenceableProperties ();
81+ $ nonReferenceableProperties = $ properties ->onlyNonReferenceableProperties ();
7282
7383 $ this ->setBody (sprintf (
7484 $ bodyTemplate ,
7585 $ initialization ,
7686 $ initializer ,
7787 $ initialization ,
7888 $ this ->propertiesInitializationCode ($ referenceableProperties ),
79- $ this ->propertiesReferenceArrayCode ($ referenceableProperties ),
89+ $ this ->propertiesReferenceArrayCode ($ referenceableProperties, $ nonReferenceableProperties ),
8090 $ initializer ,
8191 $ initializer ,
92+ $ this ->propertiesNonReferenceableCode ($ nonReferenceableProperties ),
8293 $ initialization
8394 ));
8495 }
8596
8697 private function propertiesInitializationCode (Properties $ properties ): string
8798 {
99+ $ scopedPropertyGroups = [];
100+ $ nonScopedProperties = [];
101+
102+ foreach ($ properties ->getInstanceProperties () as $ property ) {
103+ if ($ property ->isPrivate () || (\PHP_VERSION_ID >= 80100 && $ property ->isReadOnly ())) {
104+ $ scopedPropertyGroups [$ property ->getDeclaringClass ()->getName ()][$ property ->getName ()] = $ property ;
105+ } else {
106+ $ nonScopedProperties [] = $ property ;
107+ }
108+ }
109+
88110 $ assignments = [];
89111
90- foreach ($ properties -> getAccessibleProperties () as $ property ) {
112+ foreach ($ nonScopedProperties as $ property ) {
91113 $ assignments [] = '$this-> '
92114 . $ property ->getName ()
93115 . ' = ' . $ this ->getExportedPropertyDefaultValue ($ property )
94116 . '; ' ;
95117 }
96118
97- foreach ($ properties -> getGroupedPrivateProperties () as $ className => $ privateProperties ) {
119+ foreach ($ scopedPropertyGroups as $ className => $ scopedProperties ) {
98120 $ cacheKey = 'cache ' . str_replace ('\\' , '_ ' , $ className );
99121 $ assignments [] = 'static $ ' . $ cacheKey . "; \n\n"
100122 . '$ ' . $ cacheKey . ' ?? $ ' . $ cacheKey . " = \\Closure::bind(static function ( \$instance) { \n"
101- . $ this ->getPropertyDefaultsAssignments ($ privateProperties ) . "\n"
123+ . $ this ->getPropertyDefaultsAssignments ($ scopedProperties ) . "\n"
102124 . '}, null, ' . var_export ($ className , true ) . "); \n\n"
103125 . '$ ' . $ cacheKey . "( \$this); \n\n" ;
104126 }
@@ -123,17 +145,29 @@ function (ReflectionProperty $property): string {
123145 );
124146 }
125147
126- private function propertiesReferenceArrayCode (Properties $ properties ): string
148+ private function propertiesReferenceArrayCode (Properties $ properties, Properties $ nonReferenceableProperties ): string
127149 {
128- $ assignments = [];
150+ $ assignments = [];
151+ $ nonReferenceablePropertiesDefinition = '' ;
129152
130153 foreach ($ properties ->getAccessibleProperties () as $ propertyInternalName => $ property ) {
131154 $ assignments [] = ' '
132155 . var_export ($ propertyInternalName , true ) . ' => & $this-> ' . $ property ->getName ()
133156 . ', ' ;
134157 }
135158
136- $ code = "\$properties = [ \n" . implode ("\n" , $ assignments ) . "\n]; \n\n" ;
159+ foreach ($ nonReferenceableProperties ->getInstanceProperties () as $ propertyInternalName => $ property ) {
160+ $ propertyAlias = $ property ->getName () . ($ property ->isPrivate () ? '_on_ ' . str_replace ('\\' , '_ ' , $ property ->getDeclaringClass ()->getName ()) : '' );
161+ $ propertyType = $ property ->getType ();
162+ assert ($ propertyType !== null );
163+
164+ $ nonReferenceablePropertiesDefinition .= sprintf (" public %s $%s; \n" , self ::getReferenceableType ($ propertyType ), $ propertyAlias );
165+
166+ $ assignments [] = sprintf (' %s => & $nonReferenceableProperties->%s, ' , var_export ($ propertyInternalName , true ), $ propertyAlias );
167+ }
168+
169+ $ code = $ nonReferenceableProperties ->empty () ? '' : sprintf ("\$nonReferenceableProperties = new class() { \n%s}; \n" , $ nonReferenceablePropertiesDefinition );
170+ $ code .= "\$properties = [ \n" . implode ("\n" , $ assignments ) . "\n]; \n\n" ;
137171
138172 // must use assignments, as direct reference during array definition causes a fatal error (not sure why)
139173 foreach ($ properties ->getGroupedPrivateProperties () as $ className => $ classPrivateProperties ) {
@@ -173,4 +207,63 @@ private function getExportedPropertyDefaultValue(ReflectionProperty $property):
173207
174208 return (new ValueGenerator ($ defaults [$ name ] ?? null ))->generate ();
175209 }
210+
211+ private function propertiesNonReferenceableCode (Properties $ properties ): string
212+ {
213+ if ($ properties ->empty ()) {
214+ return '' ;
215+ }
216+
217+ $ code = [];
218+ $ scopedPropertyGroups = [];
219+
220+ foreach ($ properties ->getInstanceProperties () as $ propertyInternalName => $ property ) {
221+ if (! $ property ->isPrivate () && (\PHP_VERSION_ID < 80100 || ! $ property ->isReadOnly ())) {
222+ $ propertyAlias = $ property ->getName () . ($ property ->isPrivate () ? '_on_ ' . str_replace ('\\' , '_ ' , $ property ->getDeclaringClass ()->getName ()) : '' );
223+ $ code [] = sprintf ('isset($nonReferenceableProperties->%s) && $this->%s = $nonReferenceableProperties->%1$s; ' , $ propertyAlias , $ property ->getName ());
224+ } else {
225+ $ scopedPropertyGroups [$ property ->getDeclaringClass ()->getName ()][$ propertyInternalName ] = $ property ;
226+ }
227+ }
228+
229+ foreach ($ scopedPropertyGroups as $ className => $ scopedProperties ) {
230+ $ cacheKey = 'cacheAssign ' . str_replace ('\\' , '_ ' , $ className );
231+
232+ $ code [] = 'static $ ' . $ cacheKey . "; \n" ;
233+ $ code [] = '$ ' . $ cacheKey . ' ?? $ ' . $ cacheKey . ' = \Closure::bind(function ($instance, $nonReferenceableProperties) { ' ;
234+
235+ foreach ($ scopedProperties as $ property ) {
236+ $ propertyAlias = $ property ->getName () . ($ property ->isPrivate () ? '_on_ ' . str_replace ('\\' , '_ ' , $ property ->getDeclaringClass ()->getName ()) : '' );
237+ $ code [] = sprintf (' isset($nonReferenceableProperties->%s) && $this->%s = $nonReferenceableProperties->%1$s; ' , $ propertyAlias , $ property ->getName ());
238+ }
239+
240+ $ code [] = '}, $this, ' . var_export ($ className , true ) . "); \n" ;
241+ $ code [] = '$ ' . $ cacheKey . '($this, $nonReferenceableProperties); ' ;
242+ }
243+
244+ return implode ("\n" , $ code ) . "\n" ;
245+ }
246+
247+ private static function getReferenceableType (ReflectionType $ type ): string
248+ {
249+ if ($ type instanceof ReflectionNamedType) {
250+ return '? ' . ($ type ->isBuiltin () ? '' : '\\' ) . $ type ->getName ();
251+ }
252+
253+ if ($ type instanceof ReflectionIntersectionType) {
254+ return self ::getReferenceableType ($ type ->getTypes ()[0 ]);
255+ }
256+
257+ if (! $ type instanceof ReflectionUnionType) {
258+ throw new LogicException ('Unexpected ' . get_class ($ type ));
259+ }
260+
261+ $ union = 'null ' ;
262+
263+ foreach ($ type ->getTypes () as $ subType ) {
264+ $ union .= '| ' . ($ subType ->isBuiltin () ? '' : '\\' ) . $ subType ->getName ();
265+ }
266+
267+ return $ union ;
268+ }
176269}
0 commit comments