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,14 +33,17 @@ 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
3741 * the operation. Return directly if none provided
3842 * @param string|null $interfaceName name of the proxified interface if any
3943 *
4044 * @throws InvalidArgumentException
45+ *
46+ * @psalm-param $operationType self::OPERATION_*
4147 */
4248 public static function getPublicAccessSimulationCode (
4349 string $ operationType ,
@@ -48,7 +54,6 @@ public static function getPublicAccessSimulationCode(
4854 ?ReflectionClass $ originalClass = null
4955 ): string {
5056 $ byRef = self ::getByRefReturnValue ($ operationType );
51- $ value = $ operationType === self ::OPERATION_SET ? ', $value ' : '' ;
5257 $ target = '$this ' ;
5358
5459 if ($ valueHolder ) {
@@ -59,27 +64,37 @@ public static function getPublicAccessSimulationCode(
5964 ? 'new \\ReflectionClass(get_parent_class($this)) '
6065 : 'new \\ReflectionClass( ' . var_export ($ originalClass ->getName (), true ) . ') ' ;
6166
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+ }
74+
6275 return '$realInstanceReflection = ' . $ originalClassReflection . '; ' . "\n\n"
6376 . 'if (! $realInstanceReflection->hasProperty($ ' . $ nameParameter . ')) { ' . "\n"
6477 . ' $targetObject = ' . $ target . '; ' . "\n\n"
6578 . self ::getUndefinedPropertyNotice ($ operationType , $ nameParameter )
6679 . ' ' . self ::getOperation ($ operationType , $ nameParameter , $ valueParameter ) . "\n"
67- . " return; \n"
6880 . '} ' . "\n\n"
6981 . '$targetObject = ' . self ::getTargetObject ($ valueHolder ) . "; \n"
70- . '$accessor = function ' . $ byRef . '() use ($targetObject, $name ' . $ 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"
7188 . ' ' . self ::getOperation ($ operationType , $ nameParameter , $ valueParameter ) . "\n"
7289 . "}; \n"
73- . self ::getScopeReBind ()
74- . (
75- $ returnPropertyName
76- ? '$ ' . $ returnPropertyName . ' = ' . $ byRef . '$accessor(); '
77- : '$returnValue = ' . $ byRef . '$accessor(); ' . "\n\n" . 'return $returnValue; '
78- );
90+ . self ::generateScopeReBind ()
91+ . $ accessorEvaluation ;
7992 }
8093
8194 /**
8295 * This will generate code that triggers a notice if access is attempted on a non-existing property
96+ *
97+ * @psalm-param $operationType self::OPERATION_*
8398 */
8499 private static function getUndefinedPropertyNotice (string $ operationType , string $ nameParameter ): string
85100 {
@@ -106,6 +121,8 @@ private static function getUndefinedPropertyNotice(string $operationType, string
106121 * Note: if the object is a wrapper, the wrapped instance is accessed directly. If the object
107122 * is a ghost or the proxy has no wrapper, then an instance of the parent class is created via
108123 * on-the-fly unserialization
124+ *
125+ * @psalm-param $operationType self::OPERATION_*
109126 */
110127 private static function getByRefReturnValue (string $ operationType ): string
111128 {
@@ -126,25 +143,43 @@ private static function getTargetObject(?PropertyGenerator $valueHolder = null):
126143
127144 /**
128145 * @throws InvalidArgumentException
146+ *
147+ * @psalm-param $operationType self::OPERATION_*
129148 */
130149 private static function getOperation (string $ operationType , string $ nameParameter , ?string $ valueParameter ): string
131150 {
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+
132159 switch ($ operationType ) {
133160 case self ::OPERATION_GET :
134161 return 'return $targetObject->$ ' . $ nameParameter . '; ' ;
135162
136163 case self ::OPERATION_SET :
137164 if ($ valueParameter === null ) {
138- 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+ );
139170 }
140171
141- return 'return $targetObject->$ ' . $ nameParameter . ' = $ ' . $ valueParameter . '; ' ;
172+ return '$targetObject->$ ' . $ nameParameter . ' = $ ' . $ valueParameter . '; '
173+ . "\n\n"
174+ . ' return $targetObject->$ ' . $ nameParameter . '; ' ;
142175
143176 case self ::OPERATION_ISSET :
144177 return 'return isset($targetObject->$ ' . $ nameParameter . '); ' ;
145178
146179 case self ::OPERATION_UNSET :
147- return 'unset($targetObject->$ ' . $ nameParameter . '); ' ;
180+ return 'unset($targetObject->$ ' . $ nameParameter . '); '
181+ . "\n\n"
182+ . ' return; ' ;
148183 }
149184
150185 throw new InvalidArgumentException (sprintf ('Invalid operation "%s" provided ' , $ operationType ));
@@ -153,11 +188,13 @@ private static function getOperation(string $operationType, string $nameParamete
153188 /**
154189 * Generates code to bind operations to the parent scope
155190 */
156- private static function getScopeReBind (): string
191+ private static function generateScopeReBind (): string
157192 {
158- return '$backtrace = debug_backtrace(true, 2); ' . "\n"
159- . '$scopeObject = isset($backtrace[1][ \'object \']) '
160- . ' ? $backtrace[1][ \'object \'] : new \ProxyManager\Stub\EmptyClassStub(); ' . "\n"
161- . '$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;
162199 }
163200}
0 commit comments