|
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 ProxyManagerTest\Functional; |
|
22
|
|
|
|
|
23
|
|
|
use PHPUnit_Framework_TestCase; |
|
24
|
|
|
use ProxyManager\Factory\AccessInterceptorValueHolderFactory; |
|
25
|
|
|
use ProxyManager\Generator\ClassGenerator; |
|
26
|
|
|
use ProxyManager\Generator\Util\UniqueIdentifierGenerator; |
|
27
|
|
|
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; |
|
28
|
|
|
use ProxyManager\Proxy\AccessInterceptorValueHolderInterface; |
|
29
|
|
|
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator; |
|
30
|
|
|
use ProxyManagerTestAsset\BaseClass; |
|
31
|
|
|
use ProxyManagerTestAsset\BaseInterface; |
|
32
|
|
|
use ProxyManagerTestAsset\ClassWithCounterConstructor; |
|
33
|
|
|
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod; |
|
34
|
|
|
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction; |
|
35
|
|
|
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction; |
|
36
|
|
|
use ProxyManagerTestAsset\ClassWithPublicArrayProperty; |
|
37
|
|
|
use ProxyManagerTestAsset\ClassWithPublicProperties; |
|
38
|
|
|
use ProxyManagerTestAsset\ClassWithSelfHint; |
|
39
|
|
|
use ProxyManagerTestAsset\OtherObjectAccessClass; |
|
40
|
|
|
use ReflectionClass; |
|
41
|
|
|
use stdClass; |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects |
|
45
|
|
|
* |
|
46
|
|
|
* @author Marco Pivetta <[email protected]> |
|
47
|
|
|
* @license MIT |
|
48
|
|
|
* |
|
49
|
|
|
* @group Functional |
|
50
|
|
|
* @coversNothing |
|
51
|
|
|
*/ |
|
52
|
|
|
class AccessInterceptorValueHolderFunctionalTest extends PHPUnit_Framework_TestCase |
|
53
|
|
|
{ |
|
54
|
|
|
/** |
|
55
|
|
|
* @dataProvider getProxyMethods |
|
56
|
|
|
* |
|
57
|
|
|
* @param string $className |
|
58
|
|
|
* @param object $instance |
|
59
|
|
|
* @param string $method |
|
60
|
|
|
* @param mixed[] $params |
|
61
|
|
|
* @param mixed $expectedValue |
|
62
|
|
|
*/ |
|
63
|
|
|
public function testMethodCalls(string $className, $instance, string $method, $params, $expectedValue) |
|
64
|
|
|
{ |
|
65
|
|
|
$proxyName = $this->generateProxy($className); |
|
66
|
|
|
|
|
67
|
|
|
/* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */ |
|
68
|
|
|
$proxy = $proxyName::staticProxyConstructor($instance); |
|
69
|
|
|
|
|
70
|
|
|
$this->assertSame($instance, $proxy->getWrappedValueHolderValue()); |
|
71
|
|
|
$this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params)); |
|
72
|
|
|
|
|
73
|
|
|
/* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */ |
|
74
|
|
|
$listener = $this->getMock(stdClass::class, ['__invoke']); |
|
75
|
|
|
$listener |
|
76
|
|
|
->expects($this->once()) |
|
77
|
|
|
->method('__invoke') |
|
78
|
|
|
->with($proxy, $instance, $method, $params, false); |
|
79
|
|
|
|
|
80
|
|
|
$proxy->setMethodPrefixInterceptor( |
|
81
|
|
|
$method, |
|
82
|
|
|
function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) { |
|
83
|
|
|
$listener($proxy, $instance, $method, $params, $returnEarly); |
|
84
|
|
|
} |
|
85
|
|
|
); |
|
86
|
|
|
|
|
87
|
|
|
$this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params)); |
|
88
|
|
|
|
|
89
|
|
|
$random = uniqid('', true); |
|
90
|
|
|
|
|
91
|
|
|
$proxy->setMethodPrefixInterceptor( |
|
92
|
|
|
$method, |
|
93
|
|
|
function ($proxy, $instance, string $method, $params, & $returnEarly) use ($random) : string { |
|
94
|
|
|
$returnEarly = true; |
|
95
|
|
|
|
|
96
|
|
|
return $random; |
|
97
|
|
|
} |
|
98
|
|
|
); |
|
99
|
|
|
|
|
100
|
|
|
$this->assertSame($random, call_user_func_array([$proxy, $method], $params)); |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* @dataProvider getProxyMethods |
|
105
|
|
|
* |
|
106
|
|
|
* @param string $className |
|
107
|
|
|
* @param object $instance |
|
108
|
|
|
* @param string $method |
|
109
|
|
|
* @param mixed[] $params |
|
110
|
|
|
* @param mixed $expectedValue |
|
111
|
|
|
*/ |
|
112
|
|
|
public function testMethodCallsWithSuffixListener( |
|
113
|
|
|
string $className, |
|
114
|
|
|
$instance, |
|
115
|
|
|
string $method, |
|
116
|
|
|
$params, |
|
117
|
|
|
$expectedValue |
|
118
|
|
|
) { |
|
119
|
|
|
$proxyName = $this->generateProxy($className); |
|
120
|
|
|
|
|
121
|
|
|
/* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */ |
|
122
|
|
|
$proxy = $proxyName::staticProxyConstructor($instance); |
|
123
|
|
|
/* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */ |
|
124
|
|
|
$listener = $this->getMock(stdClass::class, ['__invoke']); |
|
125
|
|
|
$listener |
|
126
|
|
|
->expects($this->once()) |
|
127
|
|
|
->method('__invoke') |
|
128
|
|
|
->with($proxy, $instance, $method, $params, $expectedValue, false); |
|
129
|
|
|
|
|
130
|
|
|
$proxy->setMethodSuffixInterceptor( |
|
131
|
|
|
$method, |
|
132
|
|
|
function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($listener) { |
|
133
|
|
|
$listener($proxy, $instance, $method, $params, $returnValue, $returnEarly); |
|
134
|
|
|
} |
|
135
|
|
|
); |
|
136
|
|
|
|
|
137
|
|
|
$this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params)); |
|
138
|
|
|
|
|
139
|
|
|
$random = uniqid(); |
|
140
|
|
|
|
|
141
|
|
|
$proxy->setMethodSuffixInterceptor( |
|
142
|
|
|
$method, |
|
143
|
|
|
function ($proxy, $instance, string $method, $params, $returnValue, & $returnEarly) use ($random) : string { |
|
144
|
|
|
$returnEarly = true; |
|
145
|
|
|
|
|
146
|
|
|
return $random; |
|
147
|
|
|
} |
|
148
|
|
|
); |
|
149
|
|
|
|
|
150
|
|
|
$this->assertSame($random, call_user_func_array([$proxy, $method], $params)); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* @dataProvider getProxyMethods |
|
155
|
|
|
* |
|
156
|
|
|
* @param string $className |
|
157
|
|
|
* @param object $instance |
|
158
|
|
|
* @param string $method |
|
159
|
|
|
* @param mixed[] $params |
|
160
|
|
|
* @param mixed $expectedValue |
|
161
|
|
|
*/ |
|
162
|
|
|
public function testMethodCallsAfterUnSerialization( |
|
163
|
|
|
string $className, |
|
164
|
|
|
$instance, |
|
165
|
|
|
string $method, |
|
166
|
|
|
$params, |
|
167
|
|
|
$expectedValue |
|
168
|
|
|
) { |
|
169
|
|
|
$proxyName = $this->generateProxy($className); |
|
170
|
|
|
/* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */ |
|
171
|
|
|
$proxy = unserialize(serialize($proxyName::staticProxyConstructor($instance))); |
|
172
|
|
|
|
|
173
|
|
|
$this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params)); |
|
174
|
|
|
$this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** |
|
178
|
|
|
* @dataProvider getProxyMethods |
|
179
|
|
|
* |
|
180
|
|
|
* @param string $className |
|
181
|
|
|
* @param object $instance |
|
182
|
|
|
* @param string $method |
|
183
|
|
|
* @param mixed[] $params |
|
184
|
|
|
* @param mixed $expectedValue |
|
185
|
|
|
*/ |
|
186
|
|
|
public function testMethodCallsAfterCloning(string $className, $instance, string $method, $params, $expectedValue) |
|
187
|
|
|
{ |
|
188
|
|
|
$proxyName = $this->generateProxy($className); |
|
189
|
|
|
|
|
190
|
|
|
/* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */ |
|
191
|
|
|
$proxy = $proxyName::staticProxyConstructor($instance); |
|
192
|
|
|
$cloned = clone $proxy; |
|
193
|
|
|
|
|
194
|
|
|
$this->assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue()); |
|
195
|
|
|
$this->assertSame($expectedValue, call_user_func_array([$cloned, $method], $params)); |
|
196
|
|
|
$this->assertEquals($instance, $cloned->getWrappedValueHolderValue()); |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
/** |
|
200
|
|
|
* @dataProvider getPropertyAccessProxies |
|
201
|
|
|
* |
|
202
|
|
|
* @param object $instance |
|
203
|
|
|
* @param AccessInterceptorValueHolderInterface $proxy |
|
204
|
|
|
* @param string $publicProperty |
|
205
|
|
|
* @param mixed $propertyValue |
|
206
|
|
|
*/ |
|
207
|
|
|
public function testPropertyReadAccess( |
|
208
|
|
|
$instance, |
|
209
|
|
|
AccessInterceptorValueHolderInterface $proxy, |
|
210
|
|
|
string $publicProperty, |
|
211
|
|
|
$propertyValue |
|
212
|
|
|
) { |
|
213
|
|
|
$this->assertSame($propertyValue, $proxy->$publicProperty); |
|
214
|
|
|
$this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* @dataProvider getPropertyAccessProxies |
|
219
|
|
|
* |
|
220
|
|
|
* @param object $instance |
|
221
|
|
|
* @param AccessInterceptorValueHolderInterface $proxy |
|
222
|
|
|
* @param string $publicProperty |
|
223
|
|
|
*/ |
|
224
|
|
|
public function testPropertyWriteAccess( |
|
225
|
|
|
$instance, |
|
|
|
|
|
|
226
|
|
|
AccessInterceptorValueHolderInterface $proxy, |
|
227
|
|
|
string $publicProperty |
|
228
|
|
|
) { |
|
229
|
|
|
$newValue = uniqid(); |
|
230
|
|
|
$proxy->$publicProperty = $newValue; |
|
231
|
|
|
|
|
232
|
|
|
$this->assertSame($newValue, $proxy->$publicProperty); |
|
233
|
|
|
$this->assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty); |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* @dataProvider getPropertyAccessProxies |
|
238
|
|
|
* |
|
239
|
|
|
* @param object $instance |
|
240
|
|
|
* @param AccessInterceptorValueHolderInterface $proxy |
|
241
|
|
|
* @param string $publicProperty |
|
242
|
|
|
*/ |
|
243
|
|
|
public function testPropertyExistence( |
|
244
|
|
|
$instance, |
|
245
|
|
|
AccessInterceptorValueHolderInterface $proxy, |
|
246
|
|
|
string $publicProperty |
|
247
|
|
|
) { |
|
248
|
|
|
$this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty)); |
|
249
|
|
|
$this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); |
|
250
|
|
|
|
|
251
|
|
|
$proxy->getWrappedValueHolderValue()->$publicProperty = null; |
|
252
|
|
|
$this->assertFalse(isset($proxy->$publicProperty)); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/** |
|
256
|
|
|
* @dataProvider getPropertyAccessProxies |
|
257
|
|
|
* |
|
258
|
|
|
* @param object $instance |
|
259
|
|
|
* @param AccessInterceptorValueHolderInterface $proxy |
|
260
|
|
|
* @param string $publicProperty |
|
261
|
|
|
*/ |
|
262
|
|
|
public function testPropertyUnset( |
|
263
|
|
|
$instance, |
|
264
|
|
|
AccessInterceptorValueHolderInterface $proxy, |
|
265
|
|
|
string $publicProperty |
|
266
|
|
|
) { |
|
267
|
|
|
$instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance; |
|
268
|
|
|
unset($proxy->$publicProperty); |
|
269
|
|
|
|
|
270
|
|
|
$this->assertFalse(isset($instance->$publicProperty)); |
|
271
|
|
|
$this->assertFalse(isset($proxy->$publicProperty)); |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
/** |
|
275
|
|
|
* Verifies that accessing a public property containing an array behaves like in a normal context |
|
276
|
|
|
*/ |
|
277
|
|
|
public function testCanWriteToArrayKeysInPublicProperty() |
|
278
|
|
|
{ |
|
279
|
|
|
$instance = new ClassWithPublicArrayProperty(); |
|
280
|
|
|
$className = get_class($instance); |
|
281
|
|
|
$proxyName = $this->generateProxy($className); |
|
282
|
|
|
/* @var $proxy ClassWithPublicArrayProperty */ |
|
283
|
|
|
$proxy = $proxyName::staticProxyConstructor($instance); |
|
284
|
|
|
|
|
285
|
|
|
$proxy->arrayProperty['foo'] = 'bar'; |
|
286
|
|
|
|
|
287
|
|
|
$this->assertSame('bar', $proxy->arrayProperty['foo']); |
|
288
|
|
|
|
|
289
|
|
|
$proxy->arrayProperty = ['tab' => 'taz']; |
|
290
|
|
|
|
|
291
|
|
|
$this->assertSame(['tab' => 'taz'], $proxy->arrayProperty); |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
/** |
|
295
|
|
|
* Verifies that public properties retrieved via `__get` don't get modified in the object state |
|
296
|
|
|
*/ |
|
297
|
|
|
public function testWillNotModifyRetrievedPublicProperties() |
|
298
|
|
|
{ |
|
299
|
|
|
$instance = new ClassWithPublicProperties(); |
|
300
|
|
|
$className = get_class($instance); |
|
301
|
|
|
$proxyName = $this->generateProxy($className); |
|
302
|
|
|
/* @var $proxy ClassWithPublicProperties */ |
|
303
|
|
|
$proxy = $proxyName::staticProxyConstructor($instance); |
|
304
|
|
|
$variable = $proxy->property0; |
|
305
|
|
|
|
|
306
|
|
|
$this->assertSame('property0', $variable); |
|
307
|
|
|
|
|
308
|
|
|
$variable = 'foo'; |
|
309
|
|
|
|
|
310
|
|
|
$this->assertSame('property0', $proxy->property0); |
|
311
|
|
|
$this->assertSame('foo', $variable); |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
/** |
|
315
|
|
|
* Verifies that public properties references retrieved via `__get` modify in the object state |
|
316
|
|
|
*/ |
|
317
|
|
|
public function testWillModifyByRefRetrievedPublicProperties() |
|
318
|
|
|
{ |
|
319
|
|
|
$instance = new ClassWithPublicProperties(); |
|
320
|
|
|
$className = get_class($instance); |
|
321
|
|
|
$proxyName = $this->generateProxy($className); |
|
322
|
|
|
/* @var $proxy ClassWithPublicProperties */ |
|
323
|
|
|
$proxy = $proxyName::staticProxyConstructor($instance); |
|
324
|
|
|
$variable = & $proxy->property0; |
|
325
|
|
|
|
|
326
|
|
|
$this->assertSame('property0', $variable); |
|
327
|
|
|
|
|
328
|
|
|
$variable = 'foo'; |
|
329
|
|
|
|
|
330
|
|
|
$this->assertSame('foo', $proxy->property0); |
|
331
|
|
|
$this->assertSame('foo', $variable); |
|
332
|
|
|
} |
|
333
|
|
|
|
|
334
|
|
|
/** |
|
335
|
|
|
* @group 115 |
|
336
|
|
|
* @group 175 |
|
337
|
|
|
*/ |
|
338
|
|
|
public function testWillBehaveLikeObjectWithNormalConstructor() |
|
339
|
|
|
{ |
|
340
|
|
|
$instance = new ClassWithCounterConstructor(10); |
|
341
|
|
|
|
|
342
|
|
|
$this->assertSame(10, $instance->amount, 'Verifying that test asset works as expected'); |
|
343
|
|
|
$this->assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected'); |
|
344
|
|
|
$instance->__construct(3); |
|
345
|
|
|
$this->assertSame(13, $instance->amount, 'Verifying that test asset works as expected'); |
|
346
|
|
|
$this->assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected'); |
|
347
|
|
|
|
|
348
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
349
|
|
|
|
|
350
|
|
|
/* @var $proxy ClassWithCounterConstructor */ |
|
351
|
|
|
$proxy = new $proxyName(15); |
|
352
|
|
|
|
|
353
|
|
|
$this->assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected'); |
|
354
|
|
|
$this->assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected'); |
|
355
|
|
|
$proxy->__construct(5); |
|
356
|
|
|
$this->assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected'); |
|
357
|
|
|
$this->assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected'); |
|
358
|
|
|
} |
|
359
|
|
|
|
|
360
|
|
|
public function testWillForwardVariadicArguments() |
|
361
|
|
|
{ |
|
362
|
|
|
$factory = new AccessInterceptorValueHolderFactory(); |
|
363
|
|
|
$targetObject = new ClassWithMethodWithVariadicFunction(); |
|
364
|
|
|
|
|
365
|
|
|
/* @var $object ClassWithMethodWithVariadicFunction */ |
|
366
|
|
|
$object = $factory->createProxy( |
|
367
|
|
|
$targetObject, |
|
368
|
|
|
[ |
|
369
|
|
|
function () : string { |
|
370
|
|
|
return 'Foo Baz'; |
|
371
|
|
|
}, |
|
372
|
|
|
] |
|
373
|
|
|
); |
|
374
|
|
|
|
|
375
|
|
|
$this->assertNull($object->bar); |
|
376
|
|
|
$this->assertNull($object->baz); |
|
377
|
|
|
|
|
378
|
|
|
$object->foo('Ocramius', 'Malukenho', 'Danizord'); |
|
379
|
|
|
$this->assertSame('Ocramius', $object->bar); |
|
380
|
|
|
$this->assertSame(['Malukenho', 'Danizord'], $object->baz); |
|
381
|
|
|
} |
|
382
|
|
|
|
|
383
|
|
|
/** |
|
384
|
|
|
* @group 265 |
|
385
|
|
|
*/ |
|
386
|
|
|
public function testWillForwardVariadicByRefArguments() |
|
387
|
|
|
{ |
|
388
|
|
|
$factory = new AccessInterceptorValueHolderFactory(); |
|
389
|
|
|
$targetObject = new ClassWithMethodWithByRefVariadicFunction(); |
|
390
|
|
|
|
|
391
|
|
|
/* @var $object ClassWithMethodWithByRefVariadicFunction */ |
|
392
|
|
|
$object = $factory->createProxy( |
|
393
|
|
|
$targetObject, |
|
394
|
|
|
[ |
|
395
|
|
|
function () : string { |
|
396
|
|
|
return 'Foo Baz'; |
|
397
|
|
|
}, |
|
398
|
|
|
] |
|
399
|
|
|
); |
|
400
|
|
|
|
|
401
|
|
|
$arguments = ['Ocramius', 'Malukenho', 'Danizord']; |
|
402
|
|
|
|
|
403
|
|
|
self::assertSame( |
|
404
|
|
|
['Ocramius', 'changed', 'Danizord'], |
|
405
|
|
|
(new ClassWithMethodWithByRefVariadicFunction())->tuz(...$arguments), |
|
406
|
|
|
'Verifying that the implementation of the test asset is correct before proceeding' |
|
407
|
|
|
); |
|
408
|
|
|
self::assertSame(['Ocramius', 'changed', 'Danizord'], $object->tuz(...$arguments)); |
|
409
|
|
|
self::assertSame(['Ocramius', 'changed', 'Danizord'], $arguments, 'By-ref arguments were changed'); |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
/** |
|
413
|
|
|
* This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs. |
|
414
|
|
|
* If you manage to make this test pass, then please do send a patch |
|
415
|
|
|
* |
|
416
|
|
|
* @group 265 |
|
417
|
|
|
*/ |
|
418
|
|
|
public function testWillNotForwardDynamicArguments() |
|
419
|
|
|
{ |
|
420
|
|
|
$proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class); |
|
421
|
|
|
|
|
422
|
|
|
/* @var $object ClassWithDynamicArgumentsMethod */ |
|
423
|
|
|
$object = $proxyName::staticProxyConstructor(new ClassWithDynamicArgumentsMethod()); |
|
424
|
|
|
|
|
425
|
|
|
self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b')); |
|
426
|
|
|
|
|
427
|
|
|
$this->setExpectedException(\PHPUnit_Framework_ExpectationFailedException::class); |
|
428
|
|
|
|
|
429
|
|
|
self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b')); |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
/** |
|
433
|
|
|
* Generates a proxy for the given class name, and retrieves its class name |
|
434
|
|
|
* |
|
435
|
|
|
* @param string $parentClassName |
|
436
|
|
|
* |
|
437
|
|
|
* @return string |
|
438
|
|
|
*/ |
|
439
|
|
|
private function generateProxy(string $parentClassName) : string |
|
440
|
|
|
{ |
|
441
|
|
|
$generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); |
|
442
|
|
|
$generator = new AccessInterceptorValueHolderGenerator(); |
|
443
|
|
|
$generatedClass = new ClassGenerator($generatedClassName); |
|
444
|
|
|
$strategy = new EvaluatingGeneratorStrategy(); |
|
445
|
|
|
|
|
446
|
|
|
$generator->generate(new ReflectionClass($parentClassName), $generatedClass); |
|
447
|
|
|
$strategy->generate($generatedClass); |
|
448
|
|
|
|
|
449
|
|
|
return $generatedClassName; |
|
450
|
|
|
} |
|
451
|
|
|
|
|
452
|
|
|
/** |
|
453
|
|
|
* Generates a list of object | invoked method | parameters | expected result |
|
454
|
|
|
* |
|
455
|
|
|
* @return array |
|
456
|
|
|
*/ |
|
457
|
|
|
public function getProxyMethods() : array |
|
458
|
|
|
{ |
|
459
|
|
|
$selfHintParam = new ClassWithSelfHint(); |
|
460
|
|
|
|
|
461
|
|
|
return [ |
|
462
|
|
|
[ |
|
463
|
|
|
BaseClass::class, |
|
464
|
|
|
new BaseClass(), |
|
465
|
|
|
'publicMethod', |
|
466
|
|
|
[], |
|
467
|
|
|
'publicMethodDefault' |
|
468
|
|
|
], |
|
469
|
|
|
[ |
|
470
|
|
|
BaseClass::class, |
|
471
|
|
|
new BaseClass(), |
|
472
|
|
|
'publicTypeHintedMethod', |
|
473
|
|
|
['param' => new stdClass()], |
|
474
|
|
|
'publicTypeHintedMethodDefault' |
|
475
|
|
|
], |
|
476
|
|
|
[ |
|
477
|
|
|
BaseClass::class, |
|
478
|
|
|
new BaseClass(), |
|
479
|
|
|
'publicByReferenceMethod', |
|
480
|
|
|
[], |
|
481
|
|
|
'publicByReferenceMethodDefault' |
|
482
|
|
|
], |
|
483
|
|
|
[ |
|
484
|
|
|
BaseInterface::class, |
|
485
|
|
|
new BaseClass(), |
|
486
|
|
|
'publicMethod', |
|
487
|
|
|
[], |
|
488
|
|
|
'publicMethodDefault' |
|
489
|
|
|
], |
|
490
|
|
|
[ |
|
491
|
|
|
ClassWithSelfHint::class, |
|
492
|
|
|
new ClassWithSelfHint(), |
|
493
|
|
|
'selfHintMethod', |
|
494
|
|
|
['parameter' => $selfHintParam], |
|
495
|
|
|
$selfHintParam |
|
496
|
|
|
], |
|
497
|
|
|
]; |
|
498
|
|
|
} |
|
499
|
|
|
|
|
500
|
|
|
/** |
|
501
|
|
|
* Generates proxies and instances with a public property to feed to the property accessor methods |
|
502
|
|
|
* |
|
503
|
|
|
* @return array |
|
504
|
|
|
*/ |
|
505
|
|
|
public function getPropertyAccessProxies() : array |
|
506
|
|
|
{ |
|
507
|
|
|
$instance1 = new BaseClass(); |
|
508
|
|
|
$proxyName1 = $this->generateProxy(get_class($instance1)); |
|
509
|
|
|
$instance2 = new BaseClass(); |
|
510
|
|
|
$proxyName2 = $this->generateProxy(get_class($instance2)); |
|
511
|
|
|
|
|
512
|
|
|
return [ |
|
513
|
|
|
[ |
|
514
|
|
|
$instance1, |
|
515
|
|
|
$proxyName1::staticProxyConstructor($instance1), |
|
516
|
|
|
'publicProperty', |
|
517
|
|
|
'publicPropertyDefault', |
|
518
|
|
|
], |
|
519
|
|
|
[ |
|
520
|
|
|
$instance2, |
|
521
|
|
|
unserialize(serialize($proxyName2::staticProxyConstructor($instance2))), |
|
522
|
|
|
'publicProperty', |
|
523
|
|
|
'publicPropertyDefault', |
|
524
|
|
|
], |
|
525
|
|
|
]; |
|
526
|
|
|
} |
|
527
|
|
|
|
|
528
|
|
|
/** |
|
529
|
|
|
* @group 276 |
|
530
|
|
|
* |
|
531
|
|
|
* @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
|
532
|
|
|
* |
|
533
|
|
|
* @param object $callerObject |
|
534
|
|
|
* @param object $realInstance |
|
535
|
|
|
* @param string $method |
|
536
|
|
|
* @param string $expectedValue |
|
537
|
|
|
* @param string $propertyName |
|
538
|
|
|
*/ |
|
539
|
|
|
public function testWillInterceptAccessToPropertiesViaFriendClassAccess( |
|
540
|
|
|
$callerObject, |
|
541
|
|
|
$realInstance, |
|
542
|
|
|
string $method, |
|
543
|
|
|
string $expectedValue, |
|
544
|
|
|
string $propertyName |
|
545
|
|
|
) { |
|
546
|
|
|
$proxyName = $this->generateProxy(get_class($realInstance)); |
|
547
|
|
|
/* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */ |
|
548
|
|
|
$proxy = $proxyName::staticProxyConstructor($realInstance); |
|
549
|
|
|
|
|
550
|
|
|
/* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */ |
|
551
|
|
|
$listener = $this->getMock(\stdClass::class, ['__invoke']); |
|
552
|
|
|
|
|
553
|
|
|
$listener |
|
554
|
|
|
->expects(self::once()) |
|
555
|
|
|
->method('__invoke') |
|
556
|
|
|
->with($proxy, $realInstance, '__get', ['name' => $propertyName]); |
|
557
|
|
|
|
|
558
|
|
|
$proxy->setMethodPrefixInterceptor( |
|
|
|
|
|
|
559
|
|
|
'__get', |
|
560
|
|
|
function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) { |
|
561
|
|
|
$listener($proxy, $instance, $method, $params, $returnEarly); |
|
562
|
|
|
} |
|
563
|
|
|
); |
|
564
|
|
|
|
|
565
|
|
|
/* @var $accessor callable */ |
|
566
|
|
|
$accessor = [$callerObject, $method]; |
|
567
|
|
|
|
|
568
|
|
|
self::assertSame($expectedValue, $accessor($proxy)); |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
/** |
|
572
|
|
|
* @group 276 |
|
573
|
|
|
* |
|
574
|
|
|
* @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
|
575
|
|
|
* |
|
576
|
|
|
* @param object $callerObject |
|
577
|
|
|
* @param object $realInstance |
|
578
|
|
|
* @param string $method |
|
579
|
|
|
* @param string $expectedValue |
|
580
|
|
|
* @param string $propertyName |
|
581
|
|
|
*/ |
|
582
|
|
|
public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfDeSerialized( |
|
583
|
|
|
$callerObject, |
|
584
|
|
|
$realInstance, |
|
585
|
|
|
string $method, |
|
586
|
|
|
string $expectedValue, |
|
587
|
|
|
string $propertyName |
|
588
|
|
|
) { |
|
589
|
|
|
$proxyName = $this->generateProxy(get_class($realInstance)); |
|
590
|
|
|
/* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */ |
|
591
|
|
|
$proxy = unserialize(serialize($proxyName::staticProxyConstructor($realInstance))); |
|
592
|
|
|
|
|
593
|
|
|
/* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */ |
|
594
|
|
|
$listener = $this->getMock(\stdClass::class, ['__invoke']); |
|
595
|
|
|
|
|
596
|
|
|
$listener |
|
597
|
|
|
->expects(self::once()) |
|
598
|
|
|
->method('__invoke') |
|
599
|
|
|
->with($proxy, $realInstance, '__get', ['name' => $propertyName]); |
|
600
|
|
|
|
|
601
|
|
|
$proxy->setMethodPrefixInterceptor( |
|
|
|
|
|
|
602
|
|
|
'__get', |
|
603
|
|
|
function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) { |
|
604
|
|
|
$listener($proxy, $instance, $method, $params, $returnEarly); |
|
605
|
|
|
} |
|
606
|
|
|
); |
|
607
|
|
|
|
|
608
|
|
|
/* @var $accessor callable */ |
|
609
|
|
|
$accessor = [$callerObject, $method]; |
|
610
|
|
|
|
|
611
|
|
|
self::assertSame($expectedValue, $accessor($proxy)); |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
|
|
|
|
615
|
|
|
/** |
|
616
|
|
|
* @group 276 |
|
617
|
|
|
* |
|
618
|
|
|
* @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
|
619
|
|
|
* |
|
620
|
|
|
* @param object $callerObject |
|
621
|
|
|
* @param object $realInstance |
|
622
|
|
|
* @param string $method |
|
623
|
|
|
* @param string $expectedValue |
|
624
|
|
|
* @param string $propertyName |
|
625
|
|
|
*/ |
|
626
|
|
|
public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned( |
|
627
|
|
|
$callerObject, |
|
628
|
|
|
$realInstance, |
|
629
|
|
|
string $method, |
|
630
|
|
|
string $expectedValue, |
|
631
|
|
|
string $propertyName |
|
632
|
|
|
) { |
|
633
|
|
|
$proxyName = $this->generateProxy(get_class($realInstance)); |
|
634
|
|
|
/* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */ |
|
635
|
|
|
$proxy = clone $proxyName::staticProxyConstructor($realInstance); |
|
636
|
|
|
|
|
637
|
|
|
/* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */ |
|
638
|
|
|
$listener = $this->getMock(\stdClass::class, ['__invoke']); |
|
639
|
|
|
|
|
640
|
|
|
$listener |
|
641
|
|
|
->expects(self::once()) |
|
642
|
|
|
->method('__invoke') |
|
643
|
|
|
->with($proxy, $realInstance, '__get', ['name' => $propertyName]); |
|
644
|
|
|
|
|
645
|
|
|
$proxy->setMethodPrefixInterceptor( |
|
|
|
|
|
|
646
|
|
|
'__get', |
|
647
|
|
|
function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) { |
|
648
|
|
|
$listener($proxy, $instance, $method, $params, $returnEarly); |
|
649
|
|
|
} |
|
650
|
|
|
); |
|
651
|
|
|
|
|
652
|
|
|
/* @var $accessor callable */ |
|
653
|
|
|
$accessor = [$callerObject, $method]; |
|
654
|
|
|
|
|
655
|
|
|
self::assertSame($expectedValue, $accessor($proxy)); |
|
656
|
|
|
} |
|
657
|
|
|
|
|
658
|
|
|
public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator |
|
659
|
|
|
{ |
|
660
|
|
|
$proxyClass = $this->generateProxy(OtherObjectAccessClass::class); |
|
661
|
|
|
|
|
662
|
|
|
foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) { |
|
663
|
|
|
$property->setAccessible(true); |
|
664
|
|
|
|
|
665
|
|
|
$propertyName = $property->getName(); |
|
666
|
|
|
$realInstance = new OtherObjectAccessClass(); |
|
667
|
|
|
$expectedValue = uniqid('', true); |
|
668
|
|
|
|
|
669
|
|
|
$property->setValue($realInstance, $expectedValue); |
|
670
|
|
|
|
|
671
|
|
|
// callee is an actual object |
|
672
|
|
|
yield OtherObjectAccessClass::class . '#$' . $propertyName => [ |
|
673
|
|
|
new OtherObjectAccessClass(), |
|
674
|
|
|
$realInstance, |
|
675
|
|
|
'get' . ucfirst($propertyName), |
|
676
|
|
|
$expectedValue, |
|
677
|
|
|
$propertyName, |
|
678
|
|
|
]; |
|
679
|
|
|
|
|
680
|
|
|
$realInstance = new OtherObjectAccessClass(); |
|
681
|
|
|
$expectedValue = uniqid('', true); |
|
682
|
|
|
|
|
683
|
|
|
$property->setValue($realInstance, $expectedValue); |
|
684
|
|
|
|
|
685
|
|
|
// callee is a proxy (not to be lazy-loaded!) |
|
686
|
|
|
yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [ |
|
687
|
|
|
$proxyClass::staticProxyConstructor(new OtherObjectAccessClass()), |
|
688
|
|
|
$realInstance, |
|
689
|
|
|
'get' . ucfirst($propertyName), |
|
690
|
|
|
$expectedValue, |
|
691
|
|
|
$propertyName, |
|
692
|
|
|
]; |
|
693
|
|
|
} |
|
694
|
|
|
} |
|
695
|
|
|
} |
|
696
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.