|
1
|
|
|
<?php |
|
2
|
|
|
/* |
|
3
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
4
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
5
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
6
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
7
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
8
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
9
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
10
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
11
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
12
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
13
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
14
|
|
|
* |
|
15
|
|
|
* This software consists of voluntary contributions made by many individuals |
|
16
|
|
|
* and is licensed under the MIT license. |
|
17
|
|
|
*/ |
|
18
|
|
|
|
|
19
|
|
|
declare(strict_types=1); |
|
20
|
|
|
|
|
21
|
|
|
namespace ProxyManager\ProxyGenerator\Util; |
|
22
|
|
|
|
|
23
|
|
|
use ProxyManager\Generator\Util\ProxiedMethodReturnExpression; |
|
24
|
|
|
use Zend\Code\Generator\PropertyGenerator; |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* Generates code necessary to simulate a fatal error in case of unauthorized |
|
28
|
|
|
* access to class members in magic methods even when in child classes and dealing |
|
29
|
|
|
* with protected members. |
|
30
|
|
|
* |
|
31
|
|
|
* @author Marco Pivetta <[email protected]> |
|
32
|
|
|
* @license MIT |
|
33
|
|
|
*/ |
|
34
|
|
|
class PublicScopeSimulator |
|
35
|
|
|
{ |
|
36
|
|
|
const OPERATION_SET = 'set'; |
|
37
|
|
|
const OPERATION_GET = 'get'; |
|
38
|
|
|
const OPERATION_ISSET = 'isset'; |
|
39
|
|
|
const OPERATION_UNSET = 'unset'; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* Generates code for simulating access to a property from the scope that is accessing a proxy. |
|
43
|
|
|
* This is done by introspecting `debug_backtrace()` and then binding a closure to the scope |
|
44
|
|
|
* of the parent caller. |
|
45
|
|
|
* |
|
46
|
|
|
* @param \ReflectionMethod|null $originalMethod the original method, if it exists |
|
47
|
|
|
* @param string $operationType operation to execute: one of 'get', 'set', 'isset' or 'unset' |
|
48
|
|
|
* @param string $nameParameter name of the `name` parameter of the magic method |
|
49
|
|
|
* @param string|null $valueParameter name of the `value` parameter of the magic method |
|
50
|
|
|
* @param PropertyGenerator $valueHolder name of the property containing the target object from which |
|
51
|
|
|
* to read the property. `$this` if none provided |
|
52
|
|
|
* @param string|null $returnPropertyName name of the property to which we want to assign the result of |
|
53
|
|
|
* the operation. Return directly if none provided |
|
54
|
|
|
* |
|
55
|
|
|
* @return string |
|
56
|
|
|
* |
|
57
|
|
|
* @throws \InvalidArgumentException |
|
58
|
|
|
*/ |
|
59
|
|
|
public static function getPublicAccessSimulationCode( |
|
60
|
|
|
?\ReflectionMethod $originalMethod, |
|
61
|
|
|
string $operationType, |
|
62
|
|
|
string $nameParameter, |
|
63
|
|
|
$valueParameter = null, |
|
64
|
|
|
PropertyGenerator $valueHolder = null, |
|
65
|
|
|
$returnPropertyName = null |
|
66
|
|
|
) : string { |
|
67
|
|
|
$byRef = self::getByRefReturnValue($operationType); |
|
68
|
|
|
$value = static::OPERATION_SET === $operationType ? ', $value' : ''; |
|
69
|
|
|
$target = '$this'; |
|
70
|
|
|
|
|
71
|
|
|
if ($valueHolder) { |
|
72
|
|
|
$target = '$this->' . $valueHolder->getName(); |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
return '$realInstanceReflection = new \\ReflectionClass(get_parent_class($this));' . "\n\n" |
|
76
|
|
|
. 'if (! $realInstanceReflection->hasProperty($' . $nameParameter . ')) {' . "\n" |
|
77
|
|
|
. ' $targetObject = ' . $target . ';' . "\n\n" |
|
78
|
|
|
. self::getUndefinedPropertyNotice($operationType, $nameParameter) |
|
79
|
|
|
. ' ' . self::getOperation($originalMethod, $operationType, $nameParameter, $valueParameter) . "\n" |
|
80
|
|
|
. '}' . "\n\n" |
|
81
|
|
|
. '$targetObject = ' . self::getTargetObject($valueHolder) . ";\n" |
|
82
|
|
|
. '$accessor = function ' . $byRef . '() use ($targetObject, $name' . $value . ') {' . "\n" |
|
83
|
|
|
. ' ' . self::getOperation($originalMethod, $operationType, $nameParameter, $valueParameter) . "\n" |
|
84
|
|
|
. "};\n" |
|
85
|
|
|
. self::getScopeReBind() |
|
86
|
|
|
. ( |
|
87
|
|
|
$returnPropertyName |
|
88
|
|
|
? '$' . $returnPropertyName . ' = ' . $byRef . '$accessor();' |
|
89
|
|
|
: '$returnValue = ' . $byRef . '$accessor();' . "\n\n" . 'return $returnValue;' |
|
90
|
|
|
); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* This will generate code that triggers a notice if access is attempted on a non-existing property |
|
95
|
|
|
* |
|
96
|
|
|
* @param string $operationType |
|
97
|
|
|
* @param string $nameParameter |
|
98
|
|
|
* |
|
99
|
|
|
* @return string |
|
100
|
|
|
*/ |
|
101
|
|
|
private static function getUndefinedPropertyNotice(string $operationType, string $nameParameter) : string |
|
102
|
|
|
{ |
|
103
|
|
|
if (static::OPERATION_GET !== $operationType) { |
|
104
|
|
|
return ''; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
return ' $backtrace = debug_backtrace(false);' . "\n" |
|
108
|
|
|
. ' trigger_error(' . "\n" |
|
109
|
|
|
. ' sprintf(' . "\n" |
|
110
|
|
|
. ' \'Undefined property: %s::$%s in %s on line %s\',' . "\n" |
|
111
|
|
|
. ' get_parent_class($this),' . "\n" |
|
112
|
|
|
. ' $' . $nameParameter . ',' . "\n" |
|
113
|
|
|
. ' $backtrace[0][\'file\'],' . "\n" |
|
114
|
|
|
. ' $backtrace[0][\'line\']' . "\n" |
|
115
|
|
|
. ' ),' . "\n" |
|
116
|
|
|
. ' \E_USER_NOTICE' . "\n" |
|
117
|
|
|
. ' );' . "\n"; |
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* Defines whether the given operation produces a reference. |
|
122
|
|
|
* |
|
123
|
|
|
* Note: if the object is a wrapper, the wrapped instance is accessed directly. If the object |
|
124
|
|
|
* is a ghost or the proxy has no wrapper, then an instance of the parent class is created via |
|
125
|
|
|
* on-the-fly unserialization |
|
126
|
|
|
* |
|
127
|
|
|
* @param string $operationType |
|
128
|
|
|
* |
|
129
|
|
|
* @return string |
|
130
|
|
|
*/ |
|
131
|
|
|
private static function getByRefReturnValue(string $operationType) : string |
|
132
|
|
|
{ |
|
133
|
|
|
return (static::OPERATION_GET === $operationType || static::OPERATION_SET === $operationType) ? '& ' : ''; |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
/** |
|
137
|
|
|
* Retrieves the logic to fetch the object on which access should be attempted |
|
138
|
|
|
* |
|
139
|
|
|
* @param PropertyGenerator|null $valueHolder |
|
140
|
|
|
* |
|
141
|
|
|
* @return string |
|
142
|
|
|
*/ |
|
143
|
|
|
private static function getTargetObject(?PropertyGenerator $valueHolder) : string |
|
144
|
|
|
{ |
|
145
|
|
|
if ($valueHolder) { |
|
146
|
|
|
return '$this->' . $valueHolder->getName(); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
return 'unserialize(sprintf(\'O:%d:"%s":0:{}\', strlen(get_parent_class($this)), get_parent_class($this)))'; |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
private static function getOperation( |
|
153
|
|
|
?\ReflectionMethod $originalMethod, |
|
154
|
|
|
string $operationType, |
|
155
|
|
|
string $nameParameter, |
|
156
|
|
|
?string $valueParameter |
|
157
|
|
|
) : string { |
|
158
|
|
|
switch ($operationType) { |
|
159
|
|
|
case static::OPERATION_GET: |
|
|
|
|
|
|
160
|
|
|
return ProxiedMethodReturnExpression::generate('$targetObject->$' . $nameParameter, $originalMethod); |
|
161
|
|
|
case static::OPERATION_SET: |
|
162
|
|
|
if (! $valueParameter) { |
|
163
|
|
|
throw new \InvalidArgumentException('Parameter $valueParameter not provided'); |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
return ProxiedMethodReturnExpression::generate( |
|
167
|
|
|
'$targetObject->$' . $nameParameter . ' = $' . $valueParameter, |
|
168
|
|
|
$originalMethod |
|
169
|
|
|
); |
|
170
|
|
|
case static::OPERATION_ISSET: |
|
171
|
|
|
return ProxiedMethodReturnExpression::generate( |
|
172
|
|
|
'isset($targetObject->$' . $nameParameter . ')', |
|
173
|
|
|
$originalMethod |
|
174
|
|
|
); |
|
175
|
|
|
case static::OPERATION_UNSET: |
|
176
|
|
|
return 'unset($targetObject->$' . $nameParameter . ');' |
|
177
|
|
|
. ProxiedMethodReturnExpression::generate('true', $originalMethod); |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
throw new \InvalidArgumentException(sprintf('Invalid operation "%s" provided', $operationType)); |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* Generates code to bind operations to the parent scope |
|
185
|
|
|
* |
|
186
|
|
|
* @return string |
|
187
|
|
|
*/ |
|
188
|
|
|
private static function getScopeReBind() : string |
|
189
|
|
|
{ |
|
190
|
|
|
return '$backtrace = debug_backtrace(true);' . "\n" |
|
191
|
|
|
. '$scopeObject = isset($backtrace[1][\'object\'])' |
|
192
|
|
|
. ' ? $backtrace[1][\'object\'] : new \ProxyManager\Stub\EmptyClassStub();' . "\n" |
|
193
|
|
|
. '$accessor = $accessor->bindTo($scopeObject, get_class($scopeObject));' . "\n"; |
|
194
|
|
|
} |
|
195
|
|
|
} |
|
196
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.