Failed Conditions
Push — master ( 7c9ab7...fa4d3b )
by Marco
13:03
created

Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php (7 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\ORM\Proxy;
6
7
use Doctrine\ORM\EntityNotFoundException;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\Mapping\ClassMetadataBuildingContext;
10
use Doctrine\ORM\Mapping\ClassMetadataFactory;
11
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
12
use Doctrine\ORM\Proxy\Factory\StaticProxyFactory;
13
use Doctrine\ORM\Reflection\RuntimeReflectionService;
14
use Doctrine\Tests\Mocks\ConnectionMock;
15
use Doctrine\Tests\Mocks\DriverMock;
16
use Doctrine\Tests\Mocks\EntityManagerMock;
17
use Doctrine\Tests\Mocks\UnitOfWorkMock;
18
use Doctrine\Tests\Models\Company\CompanyEmployee;
19
use Doctrine\Tests\Models\ECommerce\ECommerceFeature;
20
use Doctrine\Tests\Models\FriendObject\ComparableObject;
21
use Doctrine\Tests\Models\ProxySpecifics\FuncGetArgs;
22
use Doctrine\Tests\OrmTestCase;
23
use PHPUnit_Framework_MockObject_MockObject;
24
use ProxyManager\Proxy\GhostObjectInterface;
25
use stdClass;
26
use function json_encode;
27
28
/**
29
 * Test the proxy generator. Its work is generating on-the-fly subclasses of a given model, which implement the Proxy pattern.
30
 */
31
class ProxyFactoryTest extends OrmTestCase
32
{
33
    /** @var ConnectionMock */
34
    private $connectionMock;
35
36
    /** @var UnitOfWorkMock */
37
    private $uowMock;
38
39
    /** @var EntityManagerMock */
40
    private $emMock;
41
42
    /** @var StaticProxyFactory */
43
    private $proxyFactory;
44
45
    /** @var ClassMetadataBuildingContext */
46
    private $metadataBuildingContext;
47
48
    /**
49
     * {@inheritDoc}
50
     */
51
    protected function setUp() : void
52
    {
53
        parent::setUp();
54
55
        $this->metadataBuildingContext = new ClassMetadataBuildingContext(
56
            $this->createMock(ClassMetadataFactory::class),
57
            new RuntimeReflectionService()
58
        );
59
        $this->connectionMock          = new ConnectionMock([], new DriverMock());
60
        $this->emMock                  = EntityManagerMock::create($this->connectionMock);
61
        $this->uowMock                 = new UnitOfWorkMock($this->emMock);
62
63
        $this->emMock->setUnitOfWork($this->uowMock);
64
65
        $this->proxyFactory = new StaticProxyFactory(
66
            $this->emMock,
67
            $this->emMock->getConfiguration()->buildGhostObjectFactory()
68
        );
69
    }
70
71
    public function testReferenceProxyDelegatesLoadingToThePersister() : void
72
    {
73
        $identifier    = ['id' => 42];
74
        $classMetaData = $this->emMock->getClassMetadata(ECommerceFeature::class);
75
76
        $persister = $this
77
            ->getMockBuilder(BasicEntityPersister::class)
78
            ->setConstructorArgs([$this->emMock, $classMetaData])
79
            ->setMethods(['loadById'])
80
            ->getMock();
81
82
        $persister
83
            ->expects($this->atLeastOnce())
84
            ->method('loadById')
85
            ->with(
86
                $identifier,
87
                self::logicalAnd(
88
                    self::isInstanceOf(GhostObjectInterface::class),
89
                    self::isInstanceOf(ECommerceFeature::class)
90
                )
91
            )
92
            ->will($this->returnValue(new stdClass()));
93
94
        $this->uowMock->setEntityPersister(ECommerceFeature::class, $persister);
95
96
        /** @var GhostObjectInterface|ECommerceFeature $proxy */
97
        $proxy = $this->proxyFactory->getProxy($classMetaData, $identifier);
0 ignored issues
show
$classMetaData of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
        $proxy = $this->proxyFactory->getProxy(/** @scrutinizer ignore-type */ $classMetaData, $identifier);
Loading history...
98
99
        $proxy->getDescription();
100
    }
101
102
    public function testSkipMappedSuperClassesOnGeneration() : void
103
    {
104
        $cm                     = new ClassMetadata(stdClass::class, $this->metadataBuildingContext);
105
        $cm->isMappedSuperclass = true;
106
107
        self::assertSame(
108
            0,
109
            $this->proxyFactory->generateProxyClasses([$cm]),
110
            'No proxies generated.'
111
        );
112
    }
113
114
    /**
115
     * @group 6625
116
     * @group embedded
117
     */
118
    public function testSkipEmbeddableClassesOnGeneration() : void
119
    {
120
        $cm                  = new ClassMetadata(stdClass::class, $this->metadataBuildingContext);
121
        $cm->isEmbeddedClass = true;
122
123
        self::assertSame(
124
            0,
125
            $this->proxyFactory->generateProxyClasses([$cm]),
126
            'No proxies generated.'
127
        );
128
    }
129
130
    /**
131
     * @group DDC-1771
132
     */
133
    public function testSkipAbstractClassesOnGeneration() : void
134
    {
135
        $cm = new ClassMetadata(AbstractClass::class, $this->metadataBuildingContext);
136
137
        self::assertNotNull($cm->getReflectionClass());
138
139
        $num = $this->proxyFactory->generateProxyClasses([$cm]);
140
141
        self::assertEquals(0, $num, 'No proxies generated.');
142
    }
143
144
    /**
145
     * @group DDC-2432
146
     */
147
    public function testFailedProxyLoadingDoesNotMarkTheProxyAsInitialized() : void
148
    {
149
        $classMetaData = $this->emMock->getClassMetadata(ECommerceFeature::class);
150
151
        $persister = $this
152
            ->getMockBuilder(BasicEntityPersister::class)
153
            ->setConstructorArgs([$this->emMock, $classMetaData])
154
            ->setMethods(['load'])
155
            ->getMock();
156
157
        $persister
158
            ->expects($this->atLeastOnce())
159
            ->method('load')
160
            ->will($this->returnValue(null));
161
162
        $this->uowMock->setEntityPersister(ECommerceFeature::class, $persister);
163
164
        /** @var GhostObjectInterface|ECommerceFeature $proxy */
165
        $proxy = $this->proxyFactory->getProxy($classMetaData, ['id' => 42]);
0 ignored issues
show
$classMetaData of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
        $proxy = $this->proxyFactory->getProxy(/** @scrutinizer ignore-type */ $classMetaData, ['id' => 42]);
Loading history...
166
167
        try {
168
            $proxy->getDescription();
169
            $this->fail('An exception was expected to be raised');
170
        } catch (EntityNotFoundException $exception) {
171
        }
172
173
        self::assertFalse($proxy->isProxyInitialized());
174
    }
175
176
    /**
177
     * @group DDC-2432
178
     */
179
    public function testFailedProxyCloningDoesNotMarkTheProxyAsInitialized() : void
180
    {
181
        $classMetaData = $this->emMock->getClassMetadata(ECommerceFeature::class);
182
183
        $persister = $this
184
            ->getMockBuilder(BasicEntityPersister::class)
185
            ->setConstructorArgs([$this->emMock, $classMetaData])
186
            ->setMethods(['load'])
187
            ->getMock();
188
189
        $persister
190
            ->expects($this->atLeastOnce())
191
            ->method('load')
192
            ->will($this->returnValue(null));
193
194
        $this->uowMock->setEntityPersister(ECommerceFeature::class, $persister);
195
196
        /** @var GhostObjectInterface|ECommerceFeature $proxy */
197
        $proxy = $this->proxyFactory->getProxy($classMetaData, ['id' => 42]);
0 ignored issues
show
$classMetaData of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

197
        $proxy = $this->proxyFactory->getProxy(/** @scrutinizer ignore-type */ $classMetaData, ['id' => 42]);
Loading history...
198
199
        try {
200
            $cloned = clone $proxy;
201
            $this->fail('An exception was expected to be raised');
202
        } catch (EntityNotFoundException $exception) {
203
        }
204
205
        self::assertFalse($proxy->isProxyInitialized());
206
    }
207
208
    public function testProxyClonesParentFields() : void
209
    {
210
        $identifier    = ['id' => 42];
211
        $classMetaData = $this->emMock->getClassMetadata(CompanyEmployee::class);
212
213
        $persister = $this
214
            ->getMockBuilder(BasicEntityPersister::class)
215
            ->setConstructorArgs([$this->emMock, $classMetaData])
216
            ->setMethods(['loadById'])
217
            ->getMock();
218
219
        $persister
220
            ->expects(self::atLeastOnce())
221
            ->method('loadById')
222
            ->with(
223
                $identifier,
224
                self::logicalAnd(
225
                    self::isInstanceOf(GhostObjectInterface::class),
226
                    self::isInstanceOf(CompanyEmployee::class)
227
                )
228
            )
229
            ->willReturnCallback(static function (array $id, CompanyEmployee $companyEmployee) {
230
                $companyEmployee->setSalary(1000); // A property on the CompanyEmployee
231
                $companyEmployee->setName('Bob'); // A property on the parent class, CompanyPerson
232
233
                return $companyEmployee;
234
            });
235
236
        $this->uowMock->setEntityPersister(CompanyEmployee::class, $persister);
237
238
        /** @var GhostObjectInterface|CompanyEmployee $proxy */
239
        $proxy = $this->proxyFactory->getProxy($classMetaData, $identifier);
0 ignored issues
show
$classMetaData of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

239
        $proxy = $this->proxyFactory->getProxy(/** @scrutinizer ignore-type */ $classMetaData, $identifier);
Loading history...
240
241
        $cloned = clone $proxy;
242
243
        self::assertSame(42, $cloned->getId(), 'Expected the Id to be cloned');
244
        self::assertSame(1000, $cloned->getSalary(), 'Expect properties on the CompanyEmployee class to be cloned');
245
        self::assertSame('Bob', $cloned->getName(), 'Expect properties on the CompanyPerson class to be cloned');
246
    }
247
248
    public function testFriendObjectsDoNotLazyLoadIfNotAccessingLazyState() : void
249
    {
250
        /** @var BasicEntityPersister|PHPUnit_Framework_MockObject_MockObject $persister */
251
        $persister = $this->createMock(BasicEntityPersister::class);
252
        $persister->expects(self::never())->method('loadById');
253
254
        $this->uowMock->setEntityPersister(ComparableObject::class, $persister);
255
256
        /** @var ComparableObject|GhostObjectInterface $comparable */
257
        $comparable = $this->proxyFactory->getProxy(
258
            $this->emMock->getClassMetadata(ComparableObject::class),
0 ignored issues
show
$this->emMock->getClassM...omparableObject::class) of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

258
            /** @scrutinizer ignore-type */ $this->emMock->getClassMetadata(ComparableObject::class),
Loading history...
259
            ['id' => 123]
260
        );
261
262
        self::assertInstanceOf(ComparableObject::class, $comparable);
263
        self::assertInstanceOf(GhostObjectInterface::class, $comparable);
264
        self::assertFalse($comparable->isProxyInitialized());
265
266
        // due to implementation details, identity check is not reading lazy state:
267
        self::assertTrue($comparable->equalTo($comparable));
268
269
        self::assertFalse($comparable->isProxyInitialized());
270
    }
271
272
    public function testFriendObjectsLazyLoadWhenAccessingLazyState() : void
273
    {
274
        /** @var BasicEntityPersister|PHPUnit_Framework_MockObject_MockObject $persister */
275
        $persister = $this
276
            ->getMockBuilder(BasicEntityPersister::class)
277
            ->setConstructorArgs([$this->emMock, $this->emMock->getClassMetadata(ComparableObject::class)])
278
            ->setMethods(['loadById'])
279
            ->getMock();
280
281
        $persister
282
            ->expects(self::exactly(2))
283
            ->method('loadById')
284
            ->with(
285
                self::logicalOr(['id' => 123], ['id' => 456]),
286
                self::logicalAnd(
287
                    self::isInstanceOf(GhostObjectInterface::class),
288
                    self::isInstanceOf(ComparableObject::class)
289
                )
290
            )
291
            ->willReturnCallback(static function (array $id, ComparableObject $comparableObject) {
292
                $comparableObject->setComparedFieldValue(json_encode($id));
293
294
                return $comparableObject;
295
            });
296
297
        $this->uowMock->setEntityPersister(ComparableObject::class, $persister);
298
299
        $metadata = $this->emMock->getClassMetadata(ComparableObject::class);
300
301
        /** @var ComparableObject|GhostObjectInterface $comparable1 */
302
        $comparable1 = $this->proxyFactory->getProxy($metadata, ['id' => 123]);
0 ignored issues
show
$metadata of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

302
        $comparable1 = $this->proxyFactory->getProxy(/** @scrutinizer ignore-type */ $metadata, ['id' => 123]);
Loading history...
303
        /** @var ComparableObject|GhostObjectInterface $comparable2 */
304
        $comparable2 = $this->proxyFactory->getProxy($metadata, ['id' => 456]);
305
306
        self::assertInstanceOf(ComparableObject::class, $comparable1);
307
        self::assertInstanceOf(ComparableObject::class, $comparable2);
308
        self::assertInstanceOf(GhostObjectInterface::class, $comparable1);
309
        self::assertInstanceOf(GhostObjectInterface::class, $comparable2);
310
        self::assertNotSame($comparable1, $comparable2);
311
        self::assertFalse($comparable1->isProxyInitialized());
312
        self::assertFalse($comparable2->isProxyInitialized());
313
314
        self::assertFalse(
315
            $comparable1->equalTo($comparable2),
316
            'Due to implementation details, identity check is not reading lazy state'
317
        );
318
319
        self::assertTrue($comparable1->isProxyInitialized());
320
        self::assertTrue($comparable2->isProxyInitialized());
321
    }
322
323
    public function testProxyMethodsSupportFuncGetArgsLogic() : void
324
    {
325
        /** @var BasicEntityPersister|PHPUnit_Framework_MockObject_MockObject $persister */
326
        $persister = $this->createMock(BasicEntityPersister::class);
327
        $persister->expects(self::never())->method('loadById');
328
329
        $this->uowMock->setEntityPersister(FuncGetArgs::class, $persister);
330
331
        /** @var FuncGetArgs|GhostObjectInterface $funcGetArgs */
332
        $funcGetArgs = $this->proxyFactory->getProxy(
333
            $this->emMock->getClassMetadata(FuncGetArgs::class),
0 ignored issues
show
$this->emMock->getClassM...ics\FuncGetArgs::class) of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

333
            /** @scrutinizer ignore-type */ $this->emMock->getClassMetadata(FuncGetArgs::class),
Loading history...
334
            ['id' => 123]
335
        );
336
337
        self::assertInstanceOf(GhostObjectInterface::class, $funcGetArgs);
338
        self::assertFalse($funcGetArgs->isProxyInitialized());
339
340
        self::assertSame(
341
            [1, 2, 3, 4],
342
            $funcGetArgs->funcGetArgsCallingMethod(1, 2, 3, 4),
343
            '`func_get_args()` calls are now supported in proxy implementations'
344
        );
345
346
        self::assertFalse($funcGetArgs->isProxyInitialized(), 'No state was accessed anyway');
347
    }
348
}
349
350
abstract class AbstractClass
351
{
352
}
353