Failed Conditions
Push — develop ( 7b23d3...7f775d )
by Guilherme
63:14
created

testGetAllClassNamesIsIdempotentEvenWithDifferentDriverInstances()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Importance

Changes 0
Metric Value
dl 10
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\ORM\Mapping;
6
7
use Doctrine\Common\Annotations\AnnotationException;
8
use Doctrine\ORM\Annotation as ORM;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use Doctrine\ORM\Mapping\ClassMetadataFactory;
11
use Doctrine\ORM\Mapping\MappingException;
12
use Doctrine\Tests\Models\CMS\CmsUser;
13
use Doctrine\Tests\Models\DDC1872\DDC1872ExampleEntityWithoutOverride;
14
use Doctrine\Tests\Models\DDC1872\DDC1872ExampleEntityWithOverride;
15
use Doctrine\Tests\Models\DirectoryTree\Directory;
16
use Doctrine\Tests\Models\DirectoryTree\File;
17
use Doctrine\Tests\Models\ECommerce\ECommerceCart;
18
19
class AnnotationDriverTest extends AbstractMappingDriverTest
20
{
21
    /**
22
     * @group DDC-268
23
     */
24
    public function testLoadMetadataForNonEntityThrowsException()
25
    {
26
        $cm = new ClassMetadata('stdClass', $this->metadataBuildingContext);
27
28
        $mappingDriver = $this->loadDriver();
29
30
        $this->expectException(MappingException::class);
31
32
        $mappingDriver->loadMetadataForClass('stdClass', $cm, $this->metadataBuildingContext);
33
    }
34
35
    /**
36
     * @expectedException \Doctrine\ORM\Cache\CacheException
37
     * @expectedExceptionMessage Entity association field "Doctrine\Tests\ORM\Mapping\AnnotationSLC#foo" not configured as part of the second-level cache.
38
     */
39 View Code Duplication
    public function testFailingSecondLevelCacheAssociation()
40
    {
41
        $mappingDriver = $this->loadDriver();
42
43
        $class = new ClassMetadata(AnnotationSLC::class, $this->metadataBuildingContext);
44
        $mappingDriver->loadMetadataForClass(AnnotationSLC::class, $class, $this->metadataBuildingContext);
45
    }
46
47
    /**
48
     * @group DDC-268
49
     */
50
    public function testColumnWithMissingTypeDefaultsToString()
51
    {
52
        $cm = new ClassMetadata(ColumnWithoutType::class, $this->metadataBuildingContext);
53
54
        $mappingDriver = $this->loadDriver();
55
56
        $mappingDriver->loadMetadataForClass(ColumnWithoutType::class, $cm, $this->metadataBuildingContext);
57
58
        self::assertNotNull($cm->getProperty('id'));
59
60
        $idProperty = $cm->getProperty('id');
61
62
        self::assertEquals('string', $idProperty->getTypeName());
63
    }
64
65
    /**
66
     * @group DDC-318
67
     */
68 View Code Duplication
    public function testGetAllClassNamesIsIdempotent()
69
    {
70
        $annotationDriver = $this->loadDriverForCMSModels();
71
        $original = $annotationDriver->getAllClassNames();
72
73
        $annotationDriver = $this->loadDriverForCMSModels();
74
        $afterTestReset = $annotationDriver->getAllClassNames();
75
76
        self::assertEquals($original, $afterTestReset);
77
    }
78
79
    /**
80
     * @group DDC-318
81
     */
82 View Code Duplication
    public function testGetAllClassNamesIsIdempotentEvenWithDifferentDriverInstances()
83
    {
84
        $annotationDriver = $this->loadDriverForCMSModels();
85
        $original = $annotationDriver->getAllClassNames();
86
87
        $annotationDriver = $this->loadDriverForCMSModels();
88
        $afterTestReset = $annotationDriver->getAllClassNames();
89
90
        self::assertEquals($original, $afterTestReset);
91
    }
92
93
    /**
94
     * @group DDC-318
95
     */
96
    public function testGetAllClassNamesReturnsAlreadyLoadedClassesIfAppropriate()
97
    {
98
        $this->ensureIsLoaded(CmsUser::class);
99
100
        $annotationDriver = $this->loadDriverForCMSModels();
101
        $classes = $annotationDriver->getAllClassNames();
102
103
        self::assertContains(CmsUser::class, $classes);
104
    }
105
106
    /**
107
     * @group DDC-318
108
     */
109
    public function testGetClassNamesReturnsOnlyTheAppropriateClasses()
110
    {
111
        $this->ensureIsLoaded(ECommerceCart::class);
112
113
        $annotationDriver = $this->loadDriverForCMSModels();
114
        $classes = $annotationDriver->getAllClassNames();
115
116
        self::assertNotContains(ECommerceCart::class, $classes);
117
    }
118
119
    protected function loadDriverForCMSModels()
120
    {
121
        $annotationDriver = $this->loadDriver();
122
        $annotationDriver->addPaths([__DIR__ . '/../../Models/CMS/']);
123
        return $annotationDriver;
124
    }
125
126
    protected function loadDriver()
127
    {
128
        return $this->createAnnotationDriver();
129
    }
130
131
    protected function ensureIsLoaded($entityClassName)
132
    {
133
        new $entityClassName;
134
    }
135
136
    /**
137
     * @group DDC-671
138
     *
139
     * Entities for this test are in AbstractMappingDriverTest
140
     */
141
    public function testJoinTablesWithMappedSuperclassForAnnotationDriver()
142
    {
143
        $annotationDriver = $this->loadDriver();
144
        $annotationDriver->addPaths([__DIR__ . '/../../Models/DirectoryTree/']);
145
146
        $em = $this->getTestEntityManager();
147
        $em->getConfiguration()->setMetadataDriverImpl($annotationDriver);
148
        $factory = new ClassMetadataFactory();
149
        $factory->setEntityManager($em);
150
151
        $classPage = $factory->getMetadataFor(File::class);
152
        self::assertArrayHasKey('parentDirectory', $classPage->getDeclaredPropertiesIterator());
153
        self::assertEquals(File::class, $classPage->getProperty('parentDirectory')->getSourceEntity());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\ORM\Mapping\Property as the method getSourceEntity() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\AssociationMetadata, Doctrine\ORM\Mapping\ManyToManyAssociationMetadata, Doctrine\ORM\Mapping\ManyToOneAssociationMetadata, Doctrine\ORM\Mapping\OneToManyAssociationMetadata, Doctrine\ORM\Mapping\OneToOneAssociationMetadata, Doctrine\ORM\Mapping\ToManyAssociationMetadata, Doctrine\ORM\Mapping\ToOneAssociationMetadata.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
154
155
        $classDirectory = $factory->getMetadataFor(Directory::class);
156
        self::assertArrayHasKey('parentDirectory', $classDirectory->getDeclaredPropertiesIterator());
157
        self::assertEquals(Directory::class, $classDirectory->getProperty('parentDirectory')->getSourceEntity());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\ORM\Mapping\Property as the method getSourceEntity() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\AssociationMetadata, Doctrine\ORM\Mapping\ManyToManyAssociationMetadata, Doctrine\ORM\Mapping\ManyToOneAssociationMetadata, Doctrine\ORM\Mapping\OneToManyAssociationMetadata, Doctrine\ORM\Mapping\OneToOneAssociationMetadata, Doctrine\ORM\Mapping\ToManyAssociationMetadata, Doctrine\ORM\Mapping\ToOneAssociationMetadata.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
158
    }
159
160
    /**
161
     * @group DDC-945
162
     */
163
    public function testInvalidMappedSuperClassWithManyToManyAssociation()
164
    {
165
        $annotationDriver = $this->loadDriver();
166
167
        $em = $this->getTestEntityManager();
168
        $em->getConfiguration()->setMetadataDriverImpl($annotationDriver);
169
        $factory = new ClassMetadataFactory();
170
        $factory->setEntityManager($em);
171
172
        $this->expectException(MappingException::class);
173
        $this->expectExceptionMessage(
174
            "It is illegal to put an inverse side one-to-many or many-to-many association on " .
175
            "mapped superclass 'Doctrine\Tests\ORM\Mapping\InvalidMappedSuperClass#users'"
176
        );
177
178
        $usingInvalidMsc = $factory->getMetadataFor(UsingInvalidMappedSuperClass::class);
0 ignored issues
show
Unused Code introduced by
$usingInvalidMsc is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
179
    }
180
181
    /**
182
     * @group DDC-1034
183
     */
184
    public function testInheritanceSkipsParentLifecycleCallbacks()
185
    {
186
        $annotationDriver = $this->loadDriver();
187
188
        $em = $this->getTestEntityManager();
189
        $em->getConfiguration()->setMetadataDriverImpl($annotationDriver);
190
        $factory = new ClassMetadataFactory();
191
        $factory->setEntityManager($em);
192
193
        $cm = $factory->getMetadataFor(AnnotationChild::class);
194
        self::assertEquals(["postLoad" => ["postLoad"], "preUpdate" => ["preUpdate"]], $cm->lifecycleCallbacks);
195
196
        $cm = $factory->getMetadataFor(AnnotationParent::class);
197
        self::assertEquals(["postLoad" => ["postLoad"], "preUpdate" => ["preUpdate"]], $cm->lifecycleCallbacks);
198
    }
199
200
    /**
201
     * @group DDC-1156
202
     */
203
    public function testMappedSuperclassInMiddleOfInheritanceHierarchy()
204
    {
205
        $annotationDriver = $this->loadDriver();
206
207
        $em = $this->getTestEntityManager();
208
        $em->getConfiguration()->setMetadataDriverImpl($annotationDriver);
209
210
        $factory = new ClassMetadataFactory();
211
        $factory->setEntityManager($em);
212
213
        self::assertInstanceOf(ClassMetadata::class, $factory->getMetadataFor(ChildEntity::class));
214
    }
215
216
    public function testInvalidFetchOptionThrowsException()
217
    {
218
        $annotationDriver = $this->loadDriver();
219
220
        $em = $this->getTestEntityManager();
221
        $em->getConfiguration()->setMetadataDriverImpl($annotationDriver);
222
        $factory = new ClassMetadataFactory();
223
        $factory->setEntityManager($em);
224
225
        $this->expectException(AnnotationException::class);
226
        $this->expectExceptionMessage('[Enum Error] Attribute "fetch" of @Doctrine\ORM\Annotation\OneToMany declared on property Doctrine\Tests\ORM\Mapping\InvalidFetchOption::$collection accept only [LAZY, EAGER, EXTRA_LAZY], but got eager.');
227
228
        $factory->getMetadataFor(InvalidFetchOption::class);
229
    }
230
231
    public function testAttributeOverridesMappingWithTrait()
232
    {
233
        $factory = $this->createClassMetadataFactory();
234
235
        $metadataWithoutOverride = $factory->getMetadataFor(DDC1872ExampleEntityWithoutOverride::class);
236
        $metadataWithOverride = $factory->getMetadataFor(DDC1872ExampleEntityWithOverride::class);
237
238
        self::assertNotNull($metadataWithoutOverride->getProperty('foo'));
239
        self::assertNotNull($metadataWithOverride->getProperty('foo'));
240
241
        $fooPropertyWithoutOverride = $metadataWithoutOverride->getProperty('foo');
242
        $fooPropertyWithOverride    = $metadataWithOverride->getProperty('foo');
243
244
        self::assertEquals('trait_foo', $fooPropertyWithoutOverride->getColumnName());
245
        self::assertEquals('foo_overridden', $fooPropertyWithOverride->getColumnName());
246
247
        $barPropertyWithoutOverride = $metadataWithoutOverride->getProperty('bar');
248
        $barPropertyWithOverride    = $metadataWithOverride->getProperty('bar');
249
250
        $barPropertyWithoutOverrideFirstJoinColumn = $barPropertyWithoutOverride->getJoinColumns()[0];
251
        $barPropertyWithOverrideFirstJoinColumn    = $barPropertyWithOverride->getJoinColumns()[0];
252
253
        self::assertEquals('example_trait_bar_id', $barPropertyWithoutOverrideFirstJoinColumn->getColumnName());
254
        self::assertEquals('example_entity_overridden_bar_id', $barPropertyWithOverrideFirstJoinColumn->getColumnName());
255
    }
256
}
257
258
/**
259
 * @ORM\MappedSuperclass
260
 */
261
class InvalidMappedSuperClass
262
{
263
    /**
264
     * @ORM\ManyToMany(targetEntity="Doctrine\Tests\Models\CMS\CmsUser", mappedBy="invalid")
265
     */
266
    private $users;
0 ignored issues
show
Unused Code introduced by
The property $users is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
267
}
268
269
/**
270
 * @ORM\Entity
271
 */
272
class UsingInvalidMappedSuperClass extends InvalidMappedSuperClass
273
{
274
    /**
275
     * @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue
276
     */
277
    private $id;
0 ignored issues
show
Unused Code introduced by
The property $id is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
278
}
279
280
/**
281
 * @ORM\Entity
282
 */
283
class ColumnWithoutType
284
{
285
    /** @ORM\Id @ORM\Column */
286
    public $id;
287
}
288
289
/**
290
 * @ORM\Entity
291
 * @ORM\InheritanceType("JOINED")
292
 * @ORM\DiscriminatorMap({"parent" = "AnnotationParent", "child" = "AnnotationChild"})
293
 * @ORM\HasLifecycleCallbacks
294
 */
295
class AnnotationParent
296
{
297
    /**
298
     * @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue
299
     */
300
    private $id;
0 ignored issues
show
Unused Code introduced by
The property $id is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
301
302
    /**
303
     * @ORM\PostLoad
304
     */
305
    public function postLoad()
306
    {
307
308
    }
309
310
    /**
311
     * @ORM\PreUpdate
312
     */
313
    public function preUpdate()
314
    {
315
316
    }
317
}
318
319
/**
320
 * @ORM\Entity
321
 * @ORM\HasLifecycleCallbacks
322
 */
323
class AnnotationChild extends AnnotationParent
324
{
325
326
}
327
328
/**
329
 * @ORM\Entity
330
 * @ORM\InheritanceType("SINGLE_TABLE")
331
 * @ORM\DiscriminatorMap({"s"="SuperEntity", "c"="ChildEntity"})
332
 */
333
class SuperEntity
334
{
335
    /** @ORM\Id @ORM\Column(type="string") */
336
    private $id;
0 ignored issues
show
Unused Code introduced by
The property $id is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
337
}
338
339
/**
340
 * @ORM\MappedSuperclass
341
 */
342
class MiddleMappedSuperclass extends SuperEntity
343
{
344
    /** @ORM\Column(type="string") */
345
    private $name;
0 ignored issues
show
Unused Code introduced by
The property $name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
346
}
347
348
/**
349
 * @ORM\Entity
350
 */
351
class ChildEntity extends MiddleMappedSuperclass
352
{
353
    /**
354
     * @ORM\Column(type="string")
355
     */
356
    private $text;
0 ignored issues
show
Unused Code introduced by
The property $text is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
357
}
358
359
/**
360
 * @ORM\Entity
361
 */
362
class InvalidFetchOption
363
{
364
    /**
365
     * @ORM\OneToMany(targetEntity="Doctrine\Tests\Models\CMS\CmsUser", fetch="eager")
366
     */
367
    private $collection;
0 ignored issues
show
Unused Code introduced by
The property $collection is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
368
}
369
370
/**
371
 * @ORM\Entity
372
 * @ORM\Cache
373
 */
374
class AnnotationSLC
375
{
376
    /**
377
     * @ORM\Id
378
     * @ORM\ManyToOne(targetEntity="AnnotationSLCFoo")
379
     */
380
    public $foo;
381
}
382
/**
383
 * @ORM\Entity
384
 */
385
class AnnotationSLCFoo
386
{
387
    /**
388
     * @ORM\Column(type="string")
389
     */
390
    public $id;
391
}
392