88use Laminas \Code \Generator \PropertyGenerator ;
99use ReflectionClass ;
1010
11+ use function array_filter ;
12+ use function array_map ;
13+ use function implode ;
1114use function sprintf ;
1215use function var_export ;
1316
@@ -30,7 +33,8 @@ class PublicScopeSimulator
3033 *
3134 * @param string $operationType operation to execute: one of 'get', 'set', 'isset' or 'unset'
3235 * @param string $nameParameter name of the `name` parameter of the magic method
33- * @param string|null $valueParameter name of the `value` parameter of the magic method
36+ * @param string|null $valueParameter name of the `value` parameter of the magic method, only to be
37+ * used with $operationType 'set'
3438 * @param PropertyGenerator $valueHolder name of the property containing the target object from which
3539 * to read the property. `$this` if none provided
3640 * @param string|null $returnPropertyName name of the property to which we want to assign the result of
@@ -50,7 +54,6 @@ public static function getPublicAccessSimulationCode(
5054 ?ReflectionClass $ originalClass = null
5155 ): string {
5256 $ byRef = self ::getByRefReturnValue ($ operationType );
53- $ value = $ operationType === self ::OPERATION_SET ? ', $ ' . ($ valueParameter ?? 'value ' ) : '' ;
5457 $ target = '$this ' ;
5558
5659 if ($ valueHolder ) {
@@ -61,7 +64,13 @@ public static function getPublicAccessSimulationCode(
6164 ? 'new \\ReflectionClass(get_parent_class($this)) '
6265 : 'new \\ReflectionClass( ' . var_export ($ originalClass ->getName (), true ) . ') ' ;
6366
64- $ returnPropertyName ??= ($ operationType === self ::OPERATION_UNSET ? 'unset ' : $ returnPropertyName );
67+ $ accessorEvaluation = $ returnPropertyName
68+ ? '$ ' . $ returnPropertyName . ' = ' . $ byRef . '$accessor(); '
69+ : '$returnValue = ' . $ byRef . '$accessor(); ' . "\n\n" . 'return $returnValue; ' ;
70+
71+ if ($ operationType === self ::OPERATION_UNSET ) {
72+ $ accessorEvaluation = '$accessor(); ' ;
73+ }
6574
6675 return '$realInstanceReflection = ' . $ originalClassReflection . '; ' . "\n\n"
6776 . 'if (! $realInstanceReflection->hasProperty($ ' . $ nameParameter . ')) { ' . "\n"
@@ -70,19 +79,22 @@ public static function getPublicAccessSimulationCode(
7079 . ' ' . self ::getOperation ($ operationType , $ nameParameter , $ valueParameter ) . "\n"
7180 . '} ' . "\n\n"
7281 . '$targetObject = ' . self ::getTargetObject ($ valueHolder ) . "; \n"
73- . '$accessor = function ' . $ byRef . '() use ($targetObject, $ ' . $ nameParameter . $ value . ') { ' . "\n"
82+ . '$accessor = function ' . $ byRef . '() use ( '
83+ . implode (', ' , array_map (
84+ static fn (string $ parameterName ): string => '$ ' . $ parameterName ,
85+ array_filter (['targetObject ' , $ nameParameter , $ valueParameter ])
86+ ))
87+ . ') { ' . "\n"
7488 . ' ' . self ::getOperation ($ operationType , $ nameParameter , $ valueParameter ) . "\n"
7589 . "}; \n"
76- . self ::getScopeReBind ()
77- . (
78- $ returnPropertyName
79- ? '$ ' . $ returnPropertyName . ' = ' . $ byRef . '$accessor(); '
80- : '$returnValue = ' . $ byRef . '$accessor(); ' . "\n\n" . 'return $returnValue; '
81- );
90+ . self ::generateScopeReBind ()
91+ . $ accessorEvaluation ;
8292 }
8393
8494 /**
8595 * This will generate code that triggers a notice if access is attempted on a non-existing property
96+ *
97+ * @psalm-param $operationType self::OPERATION_*
8698 */
8799 private static function getUndefinedPropertyNotice (string $ operationType , string $ nameParameter ): string
88100 {
@@ -109,6 +121,8 @@ private static function getUndefinedPropertyNotice(string $operationType, string
109121 * Note: if the object is a wrapper, the wrapped instance is accessed directly. If the object
110122 * is a ghost or the proxy has no wrapper, then an instance of the parent class is created via
111123 * on-the-fly unserialization
124+ *
125+ * @psalm-param $operationType self::OPERATION_*
112126 */
113127 private static function getByRefReturnValue (string $ operationType ): string
114128 {
@@ -129,19 +143,35 @@ private static function getTargetObject(?PropertyGenerator $valueHolder = null):
129143
130144 /**
131145 * @throws InvalidArgumentException
146+ *
147+ * @psalm-param $operationType self::OPERATION_*
132148 */
133149 private static function getOperation (string $ operationType , string $ nameParameter , ?string $ valueParameter ): string
134150 {
151+ if ($ valueParameter !== null && $ operationType !== self ::OPERATION_SET ) {
152+ throw new InvalidArgumentException (
153+ 'Parameter $valueParameter should be provided (only) when $operationType === " ' . self ::OPERATION_SET . '" '
154+ . self ::class
155+ . '::OPERATION_SET '
156+ );
157+ }
158+
135159 switch ($ operationType ) {
136160 case self ::OPERATION_GET :
137161 return 'return $targetObject->$ ' . $ nameParameter . '; ' ;
138162
139163 case self ::OPERATION_SET :
140164 if ($ valueParameter === null ) {
141- throw new InvalidArgumentException ('Parameter $valueParameter not provided ' );
165+ throw new InvalidArgumentException (
166+ 'Parameter $valueParameter should be provided (only) when $operationType === " ' . self ::OPERATION_SET . '" '
167+ . self ::class
168+ . '::OPERATION_SET '
169+ );
142170 }
143171
144- return '$targetObject->$ ' . $ nameParameter . ' = $ ' . $ valueParameter . '; return $targetObject->$ ' . $ nameParameter . '; ' ;
172+ return '$targetObject->$ ' . $ nameParameter . ' = $ ' . $ valueParameter . '; '
173+ . "\n\n"
174+ . ' return $targetObject->$ ' . $ nameParameter . '; ' ;
145175
146176 case self ::OPERATION_ISSET :
147177 return 'return isset($targetObject->$ ' . $ nameParameter . '); ' ;
@@ -158,11 +188,13 @@ private static function getOperation(string $operationType, string $nameParamete
158188 /**
159189 * Generates code to bind operations to the parent scope
160190 */
161- private static function getScopeReBind (): string
191+ private static function generateScopeReBind (): string
162192 {
163- return '$backtrace = debug_backtrace(true, 2); ' . "\n"
164- . '$scopeObject = isset($backtrace[1][ \'object \']) '
165- . ' ? $backtrace[1][ \'object \'] : new \ProxyManager\Stub\EmptyClassStub(); ' . "\n"
166- . '$accessor = $accessor->bindTo($scopeObject, get_class($scopeObject)); ' . "\n" ;
193+ return <<<'PHP'
194+ $backtrace = debug_backtrace(true, 2);
195+ $scopeObject = isset($backtrace[1]['object']) ? $backtrace[1]['object'] : new \ProxyManager\Stub\EmptyClassStub();
196+ $accessor = $accessor->bindTo($scopeObject, get_class($scopeObject));
197+
198+ PHP;
167199 }
168200}
0 commit comments