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