Failed Conditions
Pull Request — develop (#6935)
by Michael
65:23
created

ClassMetadataFactoryTest   C

Complexity

Total Complexity 16

Size/Duplication

Total Lines 437
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 24

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 24
dl 0
loc 437
rs 5.238
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
B testGetMetadataForSingleClass() 0 33 1
A testGetMetadataFor_ThrowsExceptionOnUnknownCustomGeneratorClass() 0 22 1
A testGetMetadataFor_ThrowsExceptionOnMissingCustomGeneratorDefinition() 0 16 1
A testHasGetMetadata_NamespaceSeparatorIsNotNormalized() 0 17 1
A testIsTransient() 0 18 1
B testAddDefaultDiscriminatorMap() 0 38 1
A testGetAllMetadataWorksWithBadConnection() 0 22 1
A createEntityManager() 0 17 2
A createTestFactory() 0 8 1
A createValidClassMetadata() 0 58 1
A testQuoteMetadata() 0 69 1
B testFallbackLoadingCausesEventTriggeringThatCanModifyFetchedMetadata() 0 29 1
A testAcceptsEntityManagerInterfaceInstances() 0 12 1
A testRejectsEmbeddableWithoutValidClassName() 0 21 1
A testInheritsIdGeneratorMappingFromEmbeddable() 0 11 1
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);
0 ignored issues
show
Bug introduced by
The method setPrefersSequences() does not exist on Doctrine\DBAL\Platforms\AbstractPlatform. Did you maybe mean prefersSequences()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
46
        $mockPlatform->setPrefersIdentityColumns(false);
0 ignored issues
show
Bug introduced by
The method setPrefersIdentityColumns() does not exist on Doctrine\DBAL\Platforms\AbstractPlatform. Did you maybe mean prefersIdentityColumns()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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());
0 ignored issues
show
Bug introduced by
The method getValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. Did you maybe mean getValue()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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(
0 ignored issues
show
Bug introduced by
The method setValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. Did you maybe mean setValue()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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());
0 ignored issues
show
Unused Code introduced by
$actual 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...
93
    }
94
95
    public function testGetMetadataFor_ThrowsExceptionOnMissingCustomGeneratorDefinition()
96
    {
97
        $cm1 = $this->createValidClassMetadata();
98
99
        $cm1->getProperty('id')->setValueGenerator(
0 ignored issues
show
Bug introduced by
The method setValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. Did you maybe mean setValue()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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());
0 ignored issues
show
Unused Code introduced by
$actual 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...
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();
0 ignored issues
show
Unused Code introduced by
$cmf 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...
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
    public function testAddDefaultDiscriminatorMap()
153
    {
154
        $cmf = new ClassMetadataFactory();
155
        $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']);
156
        $em = $this->createEntityManager($driver);
157
        $cmf->setEntityManager($em);
158
159
        $rootMetadata = $cmf->getMetadataFor(RootClass::class);
160
        $childMetadata = $cmf->getMetadataFor(ChildClass::class);
161
        $anotherChildMetadata = $cmf->getMetadataFor(AnotherChildClass::class);
162
        $rootDiscriminatorMap = $rootMetadata->discriminatorMap;
163
        $childDiscriminatorMap = $childMetadata->discriminatorMap;
164
        $anotherChildDiscriminatorMap = $anotherChildMetadata->discriminatorMap;
165
166
        $rootClass = RootClass::class;
167
        $childClass = ChildClass::class;
168
        $anotherChildClass = AnotherChildClass::class;
169
170
        $rootClassKey = array_search($rootClass, $rootDiscriminatorMap);
171
        $childClassKey = array_search($childClass, $rootDiscriminatorMap);
172
        $anotherChildClassKey = array_search($anotherChildClass, $rootDiscriminatorMap);
173
174
        self::assertEquals('rootclass', $rootClassKey);
175
        self::assertEquals('childclass', $childClassKey);
176
        self::assertEquals('anotherchildclass', $anotherChildClassKey);
177
178
        self::assertEquals($childDiscriminatorMap, $rootDiscriminatorMap);
179
        self::assertEquals($anotherChildDiscriminatorMap, $rootDiscriminatorMap);
180
181
        // ClassMetadataFactory::addDefaultDiscriminatorMap shouldn't be called again, because the
182
        // discriminator map is already cached
183
        $cmf = $this->getMockBuilder(ClassMetadataFactory::class)->setMethods(['addDefaultDiscriminatorMap'])->getMock();
184
        $cmf->setEntityManager($em);
185
        $cmf->expects($this->never())
186
            ->method('addDefaultDiscriminatorMap');
187
188
        $rootMetadata = $cmf->getMetadataFor(RootClass::class);
0 ignored issues
show
Unused Code introduced by
$rootMetadata 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...
189
    }
190
191
    public function testGetAllMetadataWorksWithBadConnection()
192
    {
193
        // DDC-3551
194
        $conn = $this->createMock(Connection::class);
195
        $mockDriver    = new MetadataDriverMock();
196
        $conn->expects($this->any())
197
            ->method('getEventManager')
198
            ->willReturn(new EventManager());
199
        $em = $this->createEntityManager($mockDriver, $conn);
200
201
        $conn->expects($this->any())
202
            ->method('getDatabasePlatform')
203
            ->will($this->throwException(new \Exception('Exception thrown in test when calling getDatabasePlatform')));
204
205
        $cmf = new ClassMetadataFactory();
206
        $cmf->setEntityManager($em);
207
208
        // getting all the metadata should work, even if get DatabasePlatform blows up
209
        $metadata = $cmf->getAllMetadata();
210
        // this will just be an empty array - there was no error
211
        self::assertEquals([], $metadata);
212
    }
213
214
    protected function createEntityManager($metadataDriver, $conn = null)
215
    {
216
        $driverMock = new DriverMock();
217
        $config = new Configuration();
218
219
        $config->setProxyDir(__DIR__ . '/../../Proxies');
220
        $config->setProxyNamespace('Doctrine\Tests\Proxies');
221
222
        if (!$conn) {
223
            $conn = new ConnectionMock([], $driverMock, $config, new EventManager());
224
        }
225
        $eventManager = $conn->getEventManager();
226
227
        $config->setMetadataDriverImpl($metadataDriver);
228
229
        return EntityManagerMock::create($conn, $config, $eventManager);
230
    }
231
232
    /**
233
     * @return ClassMetadataFactoryTestSubject
234
     */
235
    protected function createTestFactory()
236
    {
237
        $mockDriver = new MetadataDriverMock();
238
        $entityManager = $this->createEntityManager($mockDriver);
239
        $cmf = new ClassMetadataFactoryTestSubject();
240
        $cmf->setEntityManager($entityManager);
241
        return $cmf;
242
    }
243
244
    /**
245
     * @param string $class
0 ignored issues
show
Bug introduced by
There is no parameter named $class. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
246
     * @return ClassMetadata
247
     */
248
    protected function createValidClassMetadata()
249
    {
250
        // Self-made metadata
251
        $metadataBuildingContext = new Mapping\ClassMetadataBuildingContext(
252
            $this->createMock(ClassMetadataFactory::class),
0 ignored issues
show
Documentation introduced by
$this->createMock(\Doctr...MetadataFactory::class) is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<Doctrine\ORM\Mapp...ctClassMetadataFactory>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
253
            new RuntimeReflectionService()
254
        );
255
256
        $cm1 = new ClassMetadata(TestEntity1::class, $metadataBuildingContext);
257
258
        $tableMetadata = new Mapping\TableMetadata();
259
        $tableMetadata->setName('group');
260
261
        $cm1->setTable($tableMetadata);
262
263
        // Add a mapped field
264
        $fieldMetadata = new Mapping\FieldMetadata('id');
265
266
        $fieldMetadata->setType(Type::getType('integer'));
267
        $fieldMetadata->setPrimaryKey(true);
268
        $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata(Mapping\GeneratorType::AUTO));
269
270
        $cm1->addProperty($fieldMetadata);
271
272
        // Add a mapped field
273
        $fieldMetadata = new Mapping\FieldMetadata('name');
274
275
        $fieldMetadata->setType(Type::getType('string'));
276
277
        $cm1->addProperty($fieldMetadata);
278
279
        // and a mapped association
280
        $association = new Mapping\OneToOneAssociationMetadata('other');
281
282
        $association->setTargetEntity(TestEntity1::class);
283
        $association->setMappedBy('this');
284
285
        $cm1->addProperty($association);
286
287
        // and an association on the owning side
288
        $joinColumns = [];
289
290
        $joinColumn = new Mapping\JoinColumnMetadata();
291
292
        $joinColumn->setColumnName("other_id");
293
        $joinColumn->setReferencedColumnName("id");
294
295
        $joinColumns[] = $joinColumn;
296
297
        $association = new Mapping\OneToOneAssociationMetadata('association');
298
299
        $association->setJoinColumns($joinColumns);
300
        $association->setTargetEntity(TestEntity1::class);
301
302
        $cm1->addProperty($association);
303
304
        return $cm1;
305
    }
306
307
    /**
308
     * @group DDC-1845
309
     */
310
    public function testQuoteMetadata()
311
    {
312
        $cmf    = new ClassMetadataFactory();
313
        $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/Quote/']);
314
        $em     = $this->createEntityManager($driver);
315
        $cmf->setEntityManager($em);
316
317
        $userMetadata       = $cmf->getMetadataFor(Quote\User::class);
318
        $phoneMetadata      = $cmf->getMetadataFor(Quote\Phone::class);
319
        $groupMetadata      = $cmf->getMetadataFor(Quote\Group::class);
320
        $addressMetadata    = $cmf->getMetadataFor(Quote\Address::class);
321
322
        // Phone Class Metadata
323
        self::assertNotNull($phoneMetadata->getProperty('number'));
324
        self::assertEquals('phone-number', $phoneMetadata->getProperty('number')->getColumnName());
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 getColumnName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

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...
325
326
        $user                = $phoneMetadata->getProperty('user');
327
        $userJoinColumns     = $user->getJoinColumns();
328
        $phoneUserJoinColumn = reset($userJoinColumns);
329
330
        self::assertEquals('user-id', $phoneUserJoinColumn->getColumnName());
331
        self::assertEquals('user-id', $phoneUserJoinColumn->getReferencedColumnName());
332
333
        // Address Class Metadata
334
        self::assertNotNull($addressMetadata->getProperty('id'));
335
        self::assertNotNull($addressMetadata->getProperty('zip'));
336
        self::assertEquals('address-id', $addressMetadata->getProperty('id')->getColumnName());
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 getColumnName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

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...
337
        self::assertEquals('address-zip', $addressMetadata->getProperty('zip')->getColumnName());
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 getColumnName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

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...
338
339
        // User Class Metadata
340
        self::assertNotNull($userMetadata->getProperty('id'));
341
        self::assertNotNull($userMetadata->getProperty('name'));
342
        self::assertEquals('user-id', $userMetadata->getProperty('id')->getColumnName());
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 getColumnName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

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...
343
        self::assertEquals('user-name', $userMetadata->getProperty('name')->getColumnName());
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 getColumnName() does only exist in the following implementations of said interface: Doctrine\ORM\Mapping\FieldMetadata, Doctrine\ORM\Mapping\VersionFieldMetadata.

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...
344
345
        $group               = $groupMetadata->getProperty('parent');
346
        $groupJoinColumns    = $group->getJoinColumns();
347
        $groupUserJoinColumn = reset($groupJoinColumns);
348
349
        self::assertEquals('parent-id', $groupUserJoinColumn->getColumnName());
350
        self::assertEquals('group-id', $groupUserJoinColumn->getReferencedColumnName());
351
352
        $user                  = $addressMetadata->getProperty('user');
353
        $userJoinColumns       = $user->getJoinColumns();
354
        $addressUserJoinColumn = reset($userJoinColumns);
355
356
        self::assertEquals('user-id', $addressUserJoinColumn->getColumnName());
357
        self::assertEquals('user-id', $addressUserJoinColumn->getReferencedColumnName());
358
359
        $address               = $userMetadata->getProperty('address');
360
        $addressJoinColumns    = $address->getJoinColumns();
361
        $userAddressJoinColumn = reset($addressJoinColumns);
362
363
        self::assertEquals('address-id', $userAddressJoinColumn->getColumnName());
364
        self::assertEquals('address-id', $userAddressJoinColumn->getReferencedColumnName());
365
366
        $groups                       = $userMetadata->getProperty('groups');
367
        $groupsJoinTable              = $groups->getJoinTable();
368
        $userGroupsJoinColumns        = $groupsJoinTable->getJoinColumns();
369
        $userGroupsJoinColumn         = reset($userGroupsJoinColumns);
370
        $userGroupsInverseJoinColumns = $groupsJoinTable->getInverseJoinColumns();
371
        $userGroupsInverseJoinColumn  = reset($userGroupsInverseJoinColumns);
372
373
        self::assertEquals('quote-users-groups', $groupsJoinTable->getName());
374
        self::assertEquals('user-id', $userGroupsJoinColumn->getColumnName());
375
        self::assertEquals('user-id', $userGroupsJoinColumn->getReferencedColumnName());
376
        self::assertEquals('group-id', $userGroupsInverseJoinColumn->getColumnName());
377
        self::assertEquals('group-id', $userGroupsInverseJoinColumn->getReferencedColumnName());
378
    }
379
380
    /**
381
     * @group DDC-3385
382
     * @group 1181
383
     * @group 385
384
     */
385
    public function testFallbackLoadingCausesEventTriggeringThatCanModifyFetchedMetadata()
386
    {
387
        $test          = $this;
388
389
        /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
390
        $metadata      = $this->createMock(ClassMetadata::class);
391
        $cmf           = new ClassMetadataFactory();
392
        $mockDriver    = new MetadataDriverMock();
393
        $em            = $this->createEntityManager($mockDriver);
394
        $listener      = $this->getMockBuilder(\stdClass::class)->setMethods(['onClassMetadataNotFound'])->getMock();
395
        $eventManager  = $em->getEventManager();
396
397
        $cmf->setEntityManager($em);
398
399
        $listener
400
            ->expects($this->any())
401
            ->method('onClassMetadataNotFound')
402
            ->will($this->returnCallback(function (OnClassMetadataNotFoundEventArgs $args) use ($metadata, $em, $test) {
403
                $test->assertNull($args->getFoundMetadata());
404
                $test->assertSame('Foo', $args->getClassName());
405
                $test->assertSame($em, $args->getObjectManager());
406
407
                $args->setFoundMetadata($metadata);
408
            }));
409
410
        $eventManager->addEventListener([Events::onClassMetadataNotFound], $listener);
411
412
        self::assertSame($metadata, $cmf->getMetadataFor('Foo'));
413
    }
414
415
    /**
416
     * @group DDC-3427
417
     */
418
    public function testAcceptsEntityManagerInterfaceInstances()
419
    {
420
        $classMetadataFactory = new ClassMetadataFactory();
421
422
        /* @var EntityManagerInterface EntityManager */
423
        $entityManager        = $this->createMock(EntityManagerInterface::class);
424
425
        $classMetadataFactory->setEntityManager($entityManager);
0 ignored issues
show
Documentation introduced by
$entityManager is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<Doctrine\ORM\EntityManagerInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
426
427
        // not really the cleanest way to check it, but we won't add a getter to the CMF just for the sake of testing.
428
        self::assertAttributeSame($entityManager, 'em', $classMetadataFactory);
429
    }
430
431
    /**
432
     * @group embedded
433
     * @group DDC-3305
434
     */
435
    public function testRejectsEmbeddableWithoutValidClassName()
436
    {
437
        $metadata = $this->createValidClassMetadata();
438
439
        $metadata->mapEmbedded(
440
            [
441
            'fieldName'    => 'embedded',
442
            'class'        => '',
443
            'columnPrefix' => false,
444
            ]
445
        );
446
447
        $cmf = $this->createTestFactory();
448
449
        $cmf->setMetadataForClass($metadata->getClassName(), $metadata);
450
451
        $this->expectException(MappingException::class);
452
        $this->expectExceptionMessage('The embed mapping \'embedded\' misses the \'class\' attribute.');
453
454
        $cmf->getMetadataFor($metadata->getClassName());
455
    }
456
457
    /**
458
     * @group embedded
459
     * @group DDC-4006
460
     */
461
    public function testInheritsIdGeneratorMappingFromEmbeddable()
462
    {
463
        $cmf = new ClassMetadataFactory();
464
        $driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/DDC4006/']);
465
        $em = $this->createEntityManager($driver);
466
        $cmf->setEntityManager($em);
467
468
        $userMetadata = $cmf->getMetadataFor(DDC4006User::class);
469
470
        self::assertTrue($userMetadata->isIdGeneratorIdentity());
0 ignored issues
show
Bug introduced by
The method isIdGeneratorIdentity() does not seem to exist on object<Doctrine\ORM\Mapping\ClassMetadata>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
471
    }
472
}
473
474
/* Test subject class with overridden factory method for mocking purposes */
475
class ClassMetadataFactoryTestSubject extends ClassMetadataFactory
476
{
477
    private $mockMetadata = [];
478
    private $requestedClasses = [];
479
480
    protected function newClassMetadataInstance(
481
        string $className,
482
        ?Mapping\ClassMetadata $parent,
0 ignored issues
show
Unused Code introduced by
The parameter $parent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
483
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
484
    ) : ClassMetadata
485
    {
486
        $this->requestedClasses[] = $className;
487
488
        if ( ! isset($this->mockMetadata[$className])) {
489
            throw new \InvalidArgumentException("No mock metadata found for class $className.");
490
        }
491
492
        return $this->mockMetadata[$className];
493
    }
494
495
    public function setMetadataForClass($className, $metadata)
496
    {
497
        $this->mockMetadata[$className] = $metadata;
498
    }
499
500
    public function getRequestedClasses()
501
    {
502
        return $this->requestedClasses;
503
    }
504
}
505
506
class TestEntity1
507
{
508
    private $id;
509
    private $name;
510
    private $other;
511
    private $association;
512
    private $embedded;
513
}
514
515
class CustomIdGenerator implements Generator
516
{
517
    /**
518
     * {@inheritdoc}
519
     */
520
    public function generate(EntityManagerInterface $em, $entity): \Generator
521
    {
522
    }
523
524
    /**
525
     * {@inheritdoc}
526
     */
527
    public function isPostInsertGenerator()
528
    {
529
        return false;
530
    }
531
}
532