Completed
Push — master ( a0071b...e33605 )
by Michael
12s
created

Tests/ORM/Mapping/ClassMetadataFactoryTest.php (3 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\ORM\Mapping;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Configuration;
11
use Doctrine\ORM\EntityManagerInterface;
12
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
13
use Doctrine\ORM\Events;
14
use Doctrine\ORM\Sequencing\Generator;
15
use Doctrine\ORM\Mapping;
16
use Doctrine\ORM\Mapping\ClassMetadata;
17
use Doctrine\ORM\Mapping\ClassMetadataFactory;
18
use Doctrine\ORM\Mapping\Driver\MappingDriver;
19
use Doctrine\ORM\Mapping\MappingException;
20
use Doctrine\ORM\ORMException;
21
use Doctrine\ORM\Reflection\RuntimeReflectionService;
22
use Doctrine\Tests\Mocks\ConnectionMock;
23
use Doctrine\Tests\Mocks\DriverMock;
24
use Doctrine\Tests\Mocks\EntityManagerMock;
25
use Doctrine\Tests\Mocks\MetadataDriverMock;
26
use Doctrine\Tests\Models\CMS\CmsArticle;
27
use Doctrine\Tests\Models\CMS\CmsUser;
28
use Doctrine\Tests\Models\DDC4006\DDC4006User;
29
use Doctrine\Tests\Models\JoinedInheritanceType\AnotherChildClass;
30
use Doctrine\Tests\Models\JoinedInheritanceType\ChildClass;
31
use Doctrine\Tests\Models\JoinedInheritanceType\RootClass;
32
use Doctrine\Tests\Models\Quote;
33
use Doctrine\Tests\OrmTestCase;
34
use DoctrineGlobal_Article;
35
36
class ClassMetadataFactoryTest extends OrmTestCase
37
{
38
    public function testGetMetadataForSingleClass()
39
    {
40
        $mockDriver = new MetadataDriverMock();
41
        $entityManager = $this->createEntityManager($mockDriver);
42
43
        $conn = $entityManager->getConnection();
44
        $mockPlatform = $conn->getDatabasePlatform();
45
        $mockPlatform->setPrefersSequences(true);
46
        $mockPlatform->setPrefersIdentityColumns(false);
47
48
        $cm1 = $this->createValidClassMetadata();
49
50
        // SUT
51
        $cmf = new ClassMetadataFactory();
52
        $cmf->setEntityManager($entityManager);
53
        $cmf->setMetadataFor($cm1->getClassName(), $cm1);
54
55
        // Prechecks
56
        self::assertCount(0, $cm1->getAncestorsIterator());
57
        self::assertEquals(Mapping\InheritanceType::NONE, $cm1->inheritanceType);
58
        self::assertEquals(Mapping\GeneratorType::AUTO, $cm1->getProperty('id')->getValueGenerator()->getType());
59
        self::assertTrue($cm1->hasField('name'));
60
        self::assertCount(4, $cm1->getDeclaredPropertiesIterator()); // 2 fields + 2 associations
61
        self::assertEquals('group', $cm1->table->getName());
62
63
        // Go
64
        $cmMap1 = $cmf->getMetadataFor($cm1->getClassName());
65
66
        self::assertSame($cm1, $cmMap1);
67
        self::assertEquals('group', $cmMap1->table->getName());
68
        self::assertCount(0, $cmMap1->getAncestorsIterator());
69
        self::assertTrue($cmMap1->hasField('name'));
70
    }
71
72
    public function testGetMetadataFor_ThrowsExceptionOnUnknownCustomGeneratorClass()
73
    {
74
        $cm1 = $this->createValidClassMetadata();
75
76
        $cm1->getProperty('id')->setValueGenerator(
77
            new Mapping\ValueGeneratorMetadata(
78
                Mapping\GeneratorType::CUSTOM,
79
                [
80
                    'class' => 'NotExistingGenerator',
81
                    'arguments' => [],
82
                ]
83
            )
84
        );
85
86
        $cmf = $this->createTestFactory();
87
88
        $cmf->setMetadataForClass($cm1->getClassName(), $cm1);
89
90
        $this->expectException(ORMException::class);
91
92
        $actual = $cmf->getMetadataFor($cm1->getClassName());
93
    }
94
95
    public function testGetMetadataFor_ThrowsExceptionOnMissingCustomGeneratorDefinition()
96
    {
97
        $cm1 = $this->createValidClassMetadata();
98
99
        $cm1->getProperty('id')->setValueGenerator(
100
            new Mapping\ValueGeneratorMetadata(Mapping\GeneratorType::CUSTOM)
101
        );
102
103
        $cmf = $this->createTestFactory();
104
105
        $cmf->setMetadataForClass($cm1->getClassName(), $cm1);
106
107
        $this->expectException(ORMException::class);
108
109
        $actual = $cmf->getMetadataFor($cm1->getClassName());
110
    }
111
112
    public function testHasGetMetadata_NamespaceSeparatorIsNotNormalized()
113
    {
114
        require_once __DIR__."/../../Models/Global/GlobalNamespaceModel.php";
115
116
        $metadataDriver = $this->createAnnotationDriver([__DIR__ . '/../../Models/Global/']);
117
118
        $entityManager = $this->createEntityManager($metadataDriver);
119
120
        $mf = $entityManager->getMetadataFactory();
121
122
        self::assertSame(
123
            $mf->getMetadataFor(DoctrineGlobal_Article::class),
124
            $mf->getMetadataFor('\\' . DoctrineGlobal_Article::class)
125
        );
126
        self::assertTrue($mf->hasMetadataFor(DoctrineGlobal_Article::class));
127
        self::assertTrue($mf->hasMetadataFor('\\' . DoctrineGlobal_Article::class));
128
    }
129
130
    /**
131
     * @group DDC-1512
132
     */
133
    public function testIsTransient()
134
    {
135
        $cmf = new ClassMetadataFactory();
136
        $driver = $this->createMock(MappingDriver::class);
137
        $driver->expects($this->at(0))
138
               ->method('isTransient')
139
               ->with($this->equalTo(CmsUser::class))
140
               ->will($this->returnValue(true));
141
        $driver->expects($this->at(1))
142
               ->method('isTransient')
143
               ->with($this->equalTo(CmsArticle::class))
144
               ->will($this->returnValue(false));
145
146
        $em = $this->createEntityManager($driver);
147
148
        self::assertTrue($em->getMetadataFactory()->isTransient(CmsUser::class));
149
        self::assertFalse($em->getMetadataFactory()->isTransient(CmsArticle::class));
150
    }
151
152
    /**
153
     * @group DDC-1512
154
     */
155
    public function testIsTransientEntityNamespace()
156
    {
157
        $cmf = new ClassMetadataFactory();
158
        $driver = $this->createMock(MappingDriver::class);
159
        $driver->expects($this->at(0))
160
               ->method('isTransient')
161
               ->with($this->equalTo(CmsUser::class))
162
               ->will($this->returnValue(true));
163
        $driver->expects($this->at(1))
164
               ->method('isTransient')
165
               ->with($this->equalTo(CmsArticle::class))
166
               ->will($this->returnValue(false));
167
168
        $em = $this->createEntityManager($driver);
169
        $em->getConfiguration()->addEntityNamespace('CMS', 'Doctrine\Tests\Models\CMS');
170
171
        self::assertTrue($em->getMetadataFactory()->isTransient('CMS:CmsUser'));
172
        self::assertFalse($em->getMetadataFactory()->isTransient('CMS:CmsArticle'));
173
    }
174
175
    public function testAddDefaultDiscriminatorMap()
176
    {
177
        $cmf = new ClassMetadataFactory();
178
        $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']);
179
        $em = $this->createEntityManager($driver);
180
        $cmf->setEntityManager($em);
181
182
        $rootMetadata = $cmf->getMetadataFor(RootClass::class);
183
        $childMetadata = $cmf->getMetadataFor(ChildClass::class);
184
        $anotherChildMetadata = $cmf->getMetadataFor(AnotherChildClass::class);
185
        $rootDiscriminatorMap = $rootMetadata->discriminatorMap;
186
        $childDiscriminatorMap = $childMetadata->discriminatorMap;
187
        $anotherChildDiscriminatorMap = $anotherChildMetadata->discriminatorMap;
188
189
        $rootClass = RootClass::class;
190
        $childClass = ChildClass::class;
191
        $anotherChildClass = AnotherChildClass::class;
192
193
        $rootClassKey = array_search($rootClass, $rootDiscriminatorMap);
194
        $childClassKey = array_search($childClass, $rootDiscriminatorMap);
195
        $anotherChildClassKey = array_search($anotherChildClass, $rootDiscriminatorMap);
196
197
        self::assertEquals('rootclass', $rootClassKey);
198
        self::assertEquals('childclass', $childClassKey);
199
        self::assertEquals('anotherchildclass', $anotherChildClassKey);
200
201
        self::assertEquals($childDiscriminatorMap, $rootDiscriminatorMap);
202
        self::assertEquals($anotherChildDiscriminatorMap, $rootDiscriminatorMap);
203
204
        // ClassMetadataFactory::addDefaultDiscriminatorMap shouldn't be called again, because the
205
        // discriminator map is already cached
206
        $cmf = $this->getMockBuilder(ClassMetadataFactory::class)->setMethods(['addDefaultDiscriminatorMap'])->getMock();
207
        $cmf->setEntityManager($em);
208
        $cmf->expects($this->never())
209
            ->method('addDefaultDiscriminatorMap');
210
211
        $rootMetadata = $cmf->getMetadataFor(RootClass::class);
212
    }
213
214
    public function testGetAllMetadataWorksWithBadConnection()
215
    {
216
        // DDC-3551
217
        $conn = $this->createMock(Connection::class);
218
        $mockDriver    = new MetadataDriverMock();
219
        $conn->expects($this->any())
220
            ->method('getEventManager')
221
            ->willReturn(new EventManager());
222
        $em = $this->createEntityManager($mockDriver, $conn);
223
224
        $conn->expects($this->any())
225
            ->method('getDatabasePlatform')
226
            ->will($this->throwException(new \Exception('Exception thrown in test when calling getDatabasePlatform')));
227
228
        $cmf = new ClassMetadataFactory();
229
        $cmf->setEntityManager($em);
230
231
        // getting all the metadata should work, even if get DatabasePlatform blows up
232
        $metadata = $cmf->getAllMetadata();
233
        // this will just be an empty array - there was no error
234
        self::assertEquals([], $metadata);
235
    }
236
237
    protected function createEntityManager($metadataDriver, $conn = null)
238
    {
239
        $driverMock = new DriverMock();
240
        $config = new Configuration();
241
242
        $config->setProxyDir(__DIR__ . '/../../Proxies');
243
        $config->setProxyNamespace('Doctrine\Tests\Proxies');
244
245
        if (!$conn) {
246
            $conn = new ConnectionMock([], $driverMock, $config, new EventManager());
247
        }
248
        $eventManager = $conn->getEventManager();
249
250
        $config->setMetadataDriverImpl($metadataDriver);
251
252
        return EntityManagerMock::create($conn, $config, $eventManager);
253
    }
254
255
    /**
256
     * @return ClassMetadataFactoryTestSubject
257
     */
258
    protected function createTestFactory()
259
    {
260
        $mockDriver = new MetadataDriverMock();
261
        $entityManager = $this->createEntityManager($mockDriver);
262
        $cmf = new ClassMetadataFactoryTestSubject();
263
        $cmf->setEntityManager($entityManager);
264
        return $cmf;
265
    }
266
267
    /**
268
     * @param string $class
269
     * @return ClassMetadata
270
     */
271
    protected function createValidClassMetadata()
272
    {
273
        // Self-made metadata
274
        $metadataBuildingContext = new Mapping\ClassMetadataBuildingContext(
275
            $this->createMock(ClassMetadataFactory::class),
276
            new RuntimeReflectionService()
277
        );
278
279
        $cm1 = new ClassMetadata(TestEntity1::class, $metadataBuildingContext);
280
281
        $tableMetadata = new Mapping\TableMetadata();
282
        $tableMetadata->setName('group');
283
284
        $cm1->setTable($tableMetadata);
285
286
        // Add a mapped field
287
        $fieldMetadata = new Mapping\FieldMetadata('id');
288
289
        $fieldMetadata->setType(Type::getType('integer'));
290
        $fieldMetadata->setPrimaryKey(true);
291
        $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata(Mapping\GeneratorType::AUTO));
292
293
        $cm1->addProperty($fieldMetadata);
294
295
        // Add a mapped field
296
        $fieldMetadata = new Mapping\FieldMetadata('name');
297
298
        $fieldMetadata->setType(Type::getType('string'));
299
300
        $cm1->addProperty($fieldMetadata);
301
302
        // and a mapped association
303
        $association = new Mapping\OneToOneAssociationMetadata('other');
304
305
        $association->setTargetEntity(TestEntity1::class);
306
        $association->setMappedBy('this');
307
308
        $cm1->addProperty($association);
309
310
        // and an association on the owning side
311
        $joinColumns = [];
312
313
        $joinColumn = new Mapping\JoinColumnMetadata();
314
315
        $joinColumn->setColumnName("other_id");
316
        $joinColumn->setReferencedColumnName("id");
317
318
        $joinColumns[] = $joinColumn;
319
320
        $association = new Mapping\OneToOneAssociationMetadata('association');
321
322
        $association->setJoinColumns($joinColumns);
323
        $association->setTargetEntity(TestEntity1::class);
324
325
        $cm1->addProperty($association);
326
327
        return $cm1;
328
    }
329
330
    /**
331
     * @group DDC-1845
332
     */
333
    public function testQuoteMetadata()
334
    {
335
        $cmf    = new ClassMetadataFactory();
336
        $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/Quote/']);
337
        $em     = $this->createEntityManager($driver);
338
        $cmf->setEntityManager($em);
339
340
        $userMetadata       = $cmf->getMetadataFor(Quote\User::class);
341
        $phoneMetadata      = $cmf->getMetadataFor(Quote\Phone::class);
342
        $groupMetadata      = $cmf->getMetadataFor(Quote\Group::class);
343
        $addressMetadata    = $cmf->getMetadataFor(Quote\Address::class);
344
345
        // Phone Class Metadata
346
        self::assertNotNull($phoneMetadata->getProperty('number'));
347
        self::assertEquals('phone-number', $phoneMetadata->getProperty('number')->getColumnName());
348
349
        $user                = $phoneMetadata->getProperty('user');
350
        $userJoinColumns     = $user->getJoinColumns();
351
        $phoneUserJoinColumn = reset($userJoinColumns);
352
353
        self::assertEquals('user-id', $phoneUserJoinColumn->getColumnName());
354
        self::assertEquals('user-id', $phoneUserJoinColumn->getReferencedColumnName());
355
356
        // Address Class Metadata
357
        self::assertNotNull($addressMetadata->getProperty('id'));
358
        self::assertNotNull($addressMetadata->getProperty('zip'));
359
        self::assertEquals('address-id', $addressMetadata->getProperty('id')->getColumnName());
360
        self::assertEquals('address-zip', $addressMetadata->getProperty('zip')->getColumnName());
361
362
        // User Class Metadata
363
        self::assertNotNull($userMetadata->getProperty('id'));
364
        self::assertNotNull($userMetadata->getProperty('name'));
365
        self::assertEquals('user-id', $userMetadata->getProperty('id')->getColumnName());
366
        self::assertEquals('user-name', $userMetadata->getProperty('name')->getColumnName());
367
368
        $group               = $groupMetadata->getProperty('parent');
369
        $groupJoinColumns    = $group->getJoinColumns();
370
        $groupUserJoinColumn = reset($groupJoinColumns);
371
372
        self::assertEquals('parent-id', $groupUserJoinColumn->getColumnName());
373
        self::assertEquals('group-id', $groupUserJoinColumn->getReferencedColumnName());
374
375
        $user                  = $addressMetadata->getProperty('user');
376
        $userJoinColumns       = $user->getJoinColumns();
377
        $addressUserJoinColumn = reset($userJoinColumns);
378
379
        self::assertEquals('user-id', $addressUserJoinColumn->getColumnName());
380
        self::assertEquals('user-id', $addressUserJoinColumn->getReferencedColumnName());
381
382
        $address               = $userMetadata->getProperty('address');
383
        $addressJoinColumns    = $address->getJoinColumns();
384
        $userAddressJoinColumn = reset($addressJoinColumns);
385
386
        self::assertEquals('address-id', $userAddressJoinColumn->getColumnName());
387
        self::assertEquals('address-id', $userAddressJoinColumn->getReferencedColumnName());
388
389
        $groups                       = $userMetadata->getProperty('groups');
390
        $groupsJoinTable              = $groups->getJoinTable();
391
        $userGroupsJoinColumns        = $groupsJoinTable->getJoinColumns();
392
        $userGroupsJoinColumn         = reset($userGroupsJoinColumns);
393
        $userGroupsInverseJoinColumns = $groupsJoinTable->getInverseJoinColumns();
394
        $userGroupsInverseJoinColumn  = reset($userGroupsInverseJoinColumns);
395
396
        self::assertEquals('quote-users-groups', $groupsJoinTable->getName());
397
        self::assertEquals('user-id', $userGroupsJoinColumn->getColumnName());
398
        self::assertEquals('user-id', $userGroupsJoinColumn->getReferencedColumnName());
399
        self::assertEquals('group-id', $userGroupsInverseJoinColumn->getColumnName());
400
        self::assertEquals('group-id', $userGroupsInverseJoinColumn->getReferencedColumnName());
401
    }
402
403
    /**
404
     * @group DDC-3385
405
     * @group 1181
406
     * @group 385
407
     */
408
    public function testFallbackLoadingCausesEventTriggeringThatCanModifyFetchedMetadata()
409
    {
410
        $test          = $this;
411
412
        /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
413
        $metadata      = $this->createMock(ClassMetadata::class);
414
        $cmf           = new ClassMetadataFactory();
415
        $mockDriver    = new MetadataDriverMock();
416
        $em            = $this->createEntityManager($mockDriver);
417
        $listener      = $this->getMockBuilder(\stdClass::class)->setMethods(['onClassMetadataNotFound'])->getMock();
418
        $eventManager  = $em->getEventManager();
419
420
        $cmf->setEntityManager($em);
421
422
        $listener
423
            ->expects($this->any())
424
            ->method('onClassMetadataNotFound')
425
            ->will($this->returnCallback(function (OnClassMetadataNotFoundEventArgs $args) use ($metadata, $em, $test) {
426
                $test->assertNull($args->getFoundMetadata());
427
                $test->assertSame('Foo', $args->getClassName());
428
                $test->assertSame($em, $args->getObjectManager());
429
430
                $args->setFoundMetadata($metadata);
431
            }));
432
433
        $eventManager->addEventListener([Events::onClassMetadataNotFound], $listener);
434
435
        self::assertSame($metadata, $cmf->getMetadataFor('Foo'));
436
    }
437
438
    /**
439
     * @group DDC-3427
440
     */
441
    public function testAcceptsEntityManagerInterfaceInstances()
442
    {
443
        $classMetadataFactory = new ClassMetadataFactory();
444
445
        /* @var EntityManagerInterface EntityManager */
446
        $entityManager        = $this->createMock(EntityManagerInterface::class);
447
448
        $classMetadataFactory->setEntityManager($entityManager);
449
450
        // not really the cleanest way to check it, but we won't add a getter to the CMF just for the sake of testing.
451
        self::assertAttributeSame($entityManager, 'em', $classMetadataFactory);
452
    }
453
454
    /**
455
     * @group embedded
456
     * @group DDC-3305
457
     */
458
    public function testRejectsEmbeddableWithoutValidClassName()
459
    {
460
        $metadata = $this->createValidClassMetadata();
461
462
        $metadata->mapEmbedded(
463
            [
464
            'fieldName'    => 'embedded',
465
            'class'        => '',
466
            'columnPrefix' => false,
467
            ]
468
        );
469
470
        $cmf = $this->createTestFactory();
471
472
        $cmf->setMetadataForClass($metadata->getClassName(), $metadata);
473
474
        $this->expectException(MappingException::class);
475
        $this->expectExceptionMessage('The embed mapping \'embedded\' misses the \'class\' attribute.');
476
477
        $cmf->getMetadataFor($metadata->getClassName());
478
    }
479
480
    /**
481
     * @group embedded
482
     * @group DDC-4006
483
     */
484
    public function testInheritsIdGeneratorMappingFromEmbeddable()
485
    {
486
        $cmf = new ClassMetadataFactory();
487
        $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/DDC4006/']);
488
        $em = $this->createEntityManager($driver);
489
        $cmf->setEntityManager($em);
490
491
        $userMetadata = $cmf->getMetadataFor(DDC4006User::class);
492
493
        self::assertTrue($userMetadata->isIdGeneratorIdentity());
494
    }
495
}
496
497
/* Test subject class with overridden factory method for mocking purposes */
498
class ClassMetadataFactoryTestSubject extends ClassMetadataFactory
499
{
500
    private $mockMetadata = [];
501
    private $requestedClasses = [];
502
503
    protected function newClassMetadataInstance(
504
        string $className,
505
        ?Mapping\ClassMetadata $parent,
506
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
507
    ) : ClassMetadata
508
    {
509
        $this->requestedClasses[] = $className;
510
511
        if ( ! isset($this->mockMetadata[$className])) {
512
            throw new \InvalidArgumentException("No mock metadata found for class $className.");
513
        }
514
515
        return $this->mockMetadata[$className];
516
    }
517
518
    public function setMetadataForClass($className, $metadata)
519
    {
520
        $this->mockMetadata[$className] = $metadata;
521
    }
522
523
    public function getRequestedClasses()
524
    {
525
        return $this->requestedClasses;
526
    }
527
}
528
529
class TestEntity1
530
{
531
    private $id;
0 ignored issues
show
The private property $id is not used, and could be removed.
Loading history...
532
    private $name;
0 ignored issues
show
The private property $name is not used, and could be removed.
Loading history...
533
    private $other;
0 ignored issues
show
The private property $other is not used, and could be removed.
Loading history...
534
    private $association;
535
    private $embedded;
536
}
537
538
class CustomIdGenerator implements Generator
539
{
540
    /**
541
     * {@inheritdoc}
542
     */
543
    public function generate(EntityManagerInterface $em, $entity): \Generator
544
    {
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550
    public function isPostInsertGenerator()
551
    {
552
        return false;
553
    }
554
}
555