Failed Conditions
Pull Request — develop (#6724)
by Marco
122:37 queued 57:31
created

UnitOfWorkTest::testChangeTrackingNotify()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 49
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 9.2258
c 0
b 0
f 0
cc 1
eloc 34
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\ORM;
6
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\Common\EventManager;
9
use Doctrine\Common\NotifyPropertyChanged;
10
use Doctrine\Common\PropertyChangedListener;
11
use Doctrine\ORM\Annotation as ORM;
12
use Doctrine\ORM\Mapping\ClassMetadata;
13
use Doctrine\ORM\Mapping\ClassMetadataBuildingContext;
14
use Doctrine\ORM\Mapping\ClassMetadataFactory;
15
use Doctrine\ORM\Mapping\GeneratorType;
16
use Doctrine\ORM\ORMInvalidArgumentException;
17
use Doctrine\ORM\PersistentCollection;
18
use Doctrine\ORM\Reflection\RuntimeReflectionService;
19
use Doctrine\ORM\UnitOfWork;
20
use Doctrine\Tests\Mocks\ConnectionMock;
21
use Doctrine\Tests\Mocks\DriverMock;
22
use Doctrine\Tests\Mocks\EntityManagerMock;
23
use Doctrine\Tests\Mocks\EntityPersisterMock;
24
use Doctrine\Tests\Mocks\UnitOfWorkMock;
25
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
26
use Doctrine\Tests\Models\Forum\ForumAvatar;
27
use Doctrine\Tests\Models\Forum\ForumUser;
28
use Doctrine\Tests\Models\GeoNames\City;
29
use Doctrine\Tests\Models\GeoNames\Country;
30
use Doctrine\Tests\Models\UniDirectionalManyToMany\Inverse;
31
use Doctrine\Tests\Models\UniDirectionalManyToMany\Owning;
32
use Doctrine\Tests\OrmTestCase;
33
use ProxyManager\Proxy\GhostObjectInterface;
34
use stdClass;
35
36
/**
37
 * UnitOfWork tests.
38
 */
39
class UnitOfWorkTest extends OrmTestCase
40
{
41
    /**
42
     * SUT
43
     *
44
     * @var UnitOfWorkMock
45
     */
46
    private $unitOfWork;
47
48
    /**
49
     * Provides a sequence mock to the UnitOfWork
50
     *
51
     * @var ConnectionMock
52
     */
53
    private $connectionMock;
54
55
    /**
56
     * The EntityManager mock that provides the mock persisters
57
     *
58
     * @var EntityManagerMock
59
     */
60
    private $emMock;
61
62
    /**
63
     * @var EventManager|\PHPUnit_Framework_MockObject_MockObject
64
     */
65
    private $eventManager;
66
67
    /**
68
     * @var ClassMetadataBuildingContext|\PHPUnit_Framework_MockObject_MockObject
69
     */
70
    private $metadataBuildingContext;
71
72
    protected function setUp()
73
    {
74
        parent::setUp();
75
76
        $this->metadataBuildingContext = new ClassMetadataBuildingContext(
77
            $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...
78
            new RuntimeReflectionService()
79
        );
80
81
        $this->eventManager   = $this->getMockBuilder(EventManager::class)->getMock();
82
        $this->connectionMock = new ConnectionMock([], new DriverMock(), null, $this->eventManager);
0 ignored issues
show
Documentation introduced by
$this->eventManager is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a object<Doctrine\Common\EventManager>|null.

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...
83
        $this->emMock         = EntityManagerMock::create($this->connectionMock, null, $this->eventManager);
0 ignored issues
show
Documentation introduced by
$this->eventManager is of type object<PHPUnit\Framework\MockObject\MockObject>, but the function expects a null|object<Doctrine\Common\EventManager>.

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...
84
        $this->unitOfWork     = $this->emMock->getUnitOfWork();
85
86
        $this->emMock->setUnitOfWork($this->unitOfWork);
87
    }
88
89
    public function testRegisterRemovedOnNewEntityIsIgnored()
90
    {
91
        $user = new ForumUser();
92
        $user->username = 'romanb';
93
        self::assertFalse($this->unitOfWork->isScheduledForDelete($user));
94
        $this->unitOfWork->scheduleForDelete($user);
95
        self::assertFalse($this->unitOfWork->isScheduledForDelete($user));
96
    }
97
98
99
    /* Operational tests */
100
101
    public function testSavingSingleEntityWithIdentityColumnForcesInsert()
102
    {
103
        // Setup fake persister and id generator for identity generation
104
        $userPersister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(ForumUser::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...Forum\ForumUser::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
105
        $this->unitOfWork->setEntityPersister(ForumUser::class, $userPersister);
106
        $userPersister->setMockIdGeneratorType(GeneratorType::IDENTITY);
107
108
        // Test
109
        $user = new ForumUser();
110
        $user->username = 'romanb';
111
        $this->unitOfWork->persist($user);
112
113
        // Check
114
        self::assertCount(0, $userPersister->getInserts());
115
        self::assertCount(0, $userPersister->getUpdates());
116
        self::assertCount(0, $userPersister->getDeletes());
117
        self::assertFalse($this->unitOfWork->isInIdentityMap($user));
118
        // should no longer be scheduled for insert
119
        self::assertTrue($this->unitOfWork->isScheduledForInsert($user));
120
121
        // Now lets check whether a subsequent commit() does anything
122
        $userPersister->reset();
123
124
        // Test
125
        $this->unitOfWork->commit();
126
127
        // Check.
128
        self::assertCount(1, $userPersister->getInserts());
129
        self::assertCount(0, $userPersister->getUpdates());
130
        self::assertCount(0, $userPersister->getDeletes());
131
132
        // should have an id
133
        self::assertInternalType('numeric', $user->id);
134
}
135
136
    /**
137
     * Tests a scenario where a save() operation is cascaded from a ForumUser
138
     * to its associated ForumAvatar, both entities using IDENTITY id generation.
139
     */
140
    public function testCascadedIdentityColumnInsert()
141
    {
142
        // Setup fake persister and id generator for identity generation
143
        //ForumUser
144
        $userPersister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(ForumUser::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...Forum\ForumUser::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
145
        $this->unitOfWork->setEntityPersister(ForumUser::class, $userPersister);
146
        $userPersister->setMockIdGeneratorType(GeneratorType::IDENTITY);
147
        // ForumAvatar
148
        $avatarPersister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(ForumAvatar::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...rum\ForumAvatar::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
149
        $this->unitOfWork->setEntityPersister(ForumAvatar::class, $avatarPersister);
150
        $avatarPersister->setMockIdGeneratorType(GeneratorType::IDENTITY);
151
152
        // Test
153
        $user = new ForumUser();
154
        $user->username = 'romanb';
155
        $avatar = new ForumAvatar();
156
        $user->avatar = $avatar;
157
        $this->unitOfWork->persist($user); // save cascaded to avatar
158
159
        $this->unitOfWork->commit();
160
161
        self::assertInternalType('numeric', $user->id);
162
        self::assertInternalType('numeric', $avatar->id);
163
164
        self::assertCount(1, $userPersister->getInserts());
165
        self::assertCount(0, $userPersister->getUpdates());
166
        self::assertCount(0, $userPersister->getDeletes());
167
168
        self::assertCount(1, $avatarPersister->getInserts());
169
        self::assertCount(0, $avatarPersister->getUpdates());
170
        self::assertCount(0, $avatarPersister->getDeletes());
171
    }
172
173
    public function testChangeTrackingNotify()
174
    {
175
        $persister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(NotifyChangedEntity::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...fyChangedEntity::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
176
        $this->unitOfWork->setEntityPersister(NotifyChangedEntity::class, $persister);
177
        $itemPersister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(NotifyChangedRelatedItem::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...ngedRelatedItem::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
178
        $this->unitOfWork->setEntityPersister(NotifyChangedRelatedItem::class, $itemPersister);
179
180
        $entity = new NotifyChangedEntity;
181
        $entity->setData('thedata');
182
        $this->unitOfWork->persist($entity);
183
184
        $this->unitOfWork->commit();
185
        self::assertCount(1, $persister->getInserts());
186
        $persister->reset();
187
188
        self::assertTrue($this->unitOfWork->isInIdentityMap($entity));
189
190
        $entity->setData('newdata');
191
        $entity->setTransient('newtransientvalue');
192
193
        self::assertTrue($this->unitOfWork->isScheduledForDirtyCheck($entity));
194
195
        self::assertEquals(
196
            [
197
                'data' => ['thedata', 'newdata'],
198
                'transient' => [null, 'newtransientvalue'],
199
            ],
200
            $this->unitOfWork->getEntityChangeSet($entity)
201
        );
202
203
        $item = new NotifyChangedRelatedItem();
204
        $entity->getItems()->add($item);
205
        $item->setOwner($entity);
206
        $this->unitOfWork->persist($item);
207
208
        $this->unitOfWork->commit();
209
        self::assertCount(1, $itemPersister->getInserts());
210
        $persister->reset();
211
        $itemPersister->reset();
212
213
214
        $entity->getItems()->removeElement($item);
215
        $item->setOwner(null);
216
        self::assertTrue($entity->getItems()->isDirty());
0 ignored issues
show
Bug introduced by
The method isDirty() does not seem to exist on object<Doctrine\Common\C...ctions\ArrayCollection>.

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...
217
        $this->unitOfWork->commit();
218
        $updates = $itemPersister->getUpdates();
219
        self::assertCount(1, $updates);
220
        self::assertSame($updates[0], $item);
221
    }
222
223
    public function testGetEntityStateOnVersionedEntityWithAssignedIdentifier()
224
    {
225
        $persister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(VersionedAssignedIdentifierEntity::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...dentifierEntity::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
226
        $this->unitOfWork->setEntityPersister(VersionedAssignedIdentifierEntity::class, $persister);
227
228
        $e = new VersionedAssignedIdentifierEntity();
229
        $e->id = 42;
230
        self::assertEquals(UnitOfWork::STATE_NEW, $this->unitOfWork->getEntityState($e));
231
        self::assertFalse($persister->isExistsCalled());
232
    }
233
234
    public function testGetEntityStateWithAssignedIdentity()
235
    {
236
        $persister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(CmsPhonenumber::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...\CmsPhonenumber::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
237
        $this->unitOfWork->setEntityPersister(CmsPhonenumber::class, $persister);
238
239
        $ph = new CmsPhonenumber();
240
        $ph->phonenumber = '12345';
241
242
        self::assertEquals(UnitOfWork::STATE_NEW, $this->unitOfWork->getEntityState($ph));
243
        self::assertTrue($persister->isExistsCalled());
244
245
        $persister->reset();
246
247
        // if the entity is already managed the exists() check should be skipped
248
        $this->unitOfWork->registerManaged($ph, ['phonenumber' => '12345'], []);
249
        self::assertEquals(UnitOfWork::STATE_MANAGED, $this->unitOfWork->getEntityState($ph));
250
        self::assertFalse($persister->isExistsCalled());
251
        $ph2 = new CmsPhonenumber();
252
        $ph2->phonenumber = '12345';
253
        self::assertEquals(UnitOfWork::STATE_DETACHED, $this->unitOfWork->getEntityState($ph2));
254
        self::assertFalse($persister->isExistsCalled());
255
    }
256
257
    /**
258
     * DDC-2086 [GH-484] Prevented 'Undefined index' notice when updating.
259
     */
260
    public function testNoUndefinedIndexNoticeOnScheduleForUpdateWithoutChanges()
261
    {
262
        // Setup fake persister and id generator
263
        $userPersister = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(ForumUser::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...Forum\ForumUser::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
264
        $userPersister->setMockIdGeneratorType(GeneratorType::IDENTITY);
265
        $this->unitOfWork->setEntityPersister(ForumUser::class, $userPersister);
266
267
        // Create a test user
268
        $user = new ForumUser();
269
        $user->name = 'Jasper';
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in Doctrine\Tests\Models\Forum\ForumUser.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
270
        $this->unitOfWork->persist($user);
271
        $this->unitOfWork->commit();
272
273
        // Schedule user for update without changes
274
        $this->unitOfWork->scheduleForUpdate($user);
275
276
        self::assertNotEmpty($this->unitOfWork->getScheduledEntityUpdates());
277
278
        // This commit should not raise an E_NOTICE
279
        $this->unitOfWork->commit();
280
281
        self::assertEmpty($this->unitOfWork->getScheduledEntityUpdates());
282
    }
283
284
    /**
285
     * @group DDC-1984
286
     */
287
    public function testLockWithoutEntityThrowsException()
288
    {
289
        $this->expectException(\InvalidArgumentException::class);
290
        $this->unitOfWork->lock(null, null, null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object.

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...
291
    }
292
293
    /**
294
     * @group DDC-3490
295
     *
296
     * @dataProvider invalidAssociationValuesDataProvider
297
     *
298
     * @param mixed $invalidValue
299
     */
300
    public function testRejectsPersistenceOfObjectsWithInvalidAssociationValue($invalidValue)
301
    {
302
        $this->unitOfWork->setEntityPersister(
303
            ForumUser::class,
304
            new EntityPersisterMock(
305
                $this->emMock,
306
                $this->emMock->getClassMetadata(ForumUser::class)
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...Forum\ForumUser::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
307
            )
308
        );
309
310
        $user           = new ForumUser();
311
        $user->username = 'John';
312
        $user->avatar   = $invalidValue;
313
314
        $this->expectException(\Doctrine\ORM\ORMInvalidArgumentException::class);
315
316
        $this->unitOfWork->persist($user);
317
    }
318
319
    /**
320
     * @group DDC-3490
321
     *
322
     * @dataProvider invalidAssociationValuesDataProvider
323
     *
324
     * @param mixed $invalidValue
325
     */
326
    public function testRejectsChangeSetComputationForObjectsWithInvalidAssociationValue($invalidValue)
327
    {
328
        $metadata = $this->emMock->getClassMetadata(ForumUser::class);
329
330
        $this->unitOfWork->setEntityPersister(
331
            ForumUser::class,
332
            new EntityPersisterMock($this->emMock, $metadata)
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
333
        );
334
335
        $user = new ForumUser();
336
337
        $this->unitOfWork->persist($user);
338
339
        $user->username = 'John';
340
        $user->avatar   = $invalidValue;
341
342
        $this->expectException(\Doctrine\ORM\ORMInvalidArgumentException::class);
343
344
        $this->unitOfWork->computeChangeSet($metadata, $user);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
345
    }
346
347
    /**
348
     * @group DDC-3619
349
     * @group 1338
350
     */
351
    public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGarbageCollected()
352
    {
353
        $entity     = new ForumUser();
354
        $entity->id = 123;
355
356
        $this->unitOfWork->registerManaged($entity, ['id' => 123], []);
357
        self::assertTrue($this->unitOfWork->isInIdentityMap($entity));
358
359
        $this->unitOfWork->remove($entity);
360
        self::assertFalse($this->unitOfWork->isInIdentityMap($entity));
361
362
        $this->unitOfWork->persist($entity);
363
        self::assertTrue($this->unitOfWork->isInIdentityMap($entity));
364
    }
365
366
    /**
367
     * @group 5849
368
     * @group 5850
369
     */
370
    public function testPersistedEntityAndClearManager()
371
    {
372
        $entity1 = new City(123, 'London');
373
        $entity2 = new Country(456, 'United Kingdom');
374
375
        $this->unitOfWork->persist($entity1);
376
        self::assertTrue($this->unitOfWork->isInIdentityMap($entity1));
377
378
        $this->unitOfWork->persist($entity2);
379
        self::assertTrue($this->unitOfWork->isInIdentityMap($entity2));
380
381
        $this->unitOfWork->clear();
382
383
        self::assertFalse($this->unitOfWork->isInIdentityMap($entity1));
384
        self::assertFalse($this->unitOfWork->isInIdentityMap($entity2));
385
386
        self::assertFalse($this->unitOfWork->isScheduledForInsert($entity1));
387
        self::assertFalse($this->unitOfWork->isScheduledForInsert($entity2));
388
    }
389
390
    /**
391
     * @group #5579
392
     */
393
    public function testEntityChangeSetIsClearedAfterFlush() : void
394
    {
395
        $entity1 = new NotifyChangedEntity;
396
        $entity2 = new NotifyChangedEntity;
397
398
        $entity1->setData('thedata');
399
        $entity2->setData('thedata');
400
401
        $this->unitOfWork->persist($entity1);
402
        $this->unitOfWork->persist($entity2);
403
        $this->unitOfWork->commit();
404
405
        self::assertEmpty($this->unitOfWork->getEntityChangeSet($entity1));
406
        self::assertEmpty($this->unitOfWork->getEntityChangeSet($entity2));
407
    }
408
409
    /**
410
     * Data Provider
411
     *
412
     * @return mixed[][]
413
     */
414
    public function invalidAssociationValuesDataProvider()
415
    {
416
        return [
417
            ['foo'],
418
            [['foo']],
419
            [''],
420
            [[]],
421
            [new stdClass()],
422
            [new ArrayCollection()],
423
        ];
424
    }
425
426
    /**
427
     * @dataProvider entitiesWithValidIdentifiersProvider
428
     *
429
     * @param object $entity
430
     * @param string $idHash
431
     *
432
     * @return void
433
     */
434
    public function testAddToIdentityMapValidIdentifiers($entity, $idHash)
435
    {
436
        $this->unitOfWork->persist($entity);
437
        $this->unitOfWork->addToIdentityMap($entity);
438
439
        self::assertSame($entity, $this->unitOfWork->getByIdHash($idHash, get_class($entity)));
440
    }
441
442
    public function entitiesWithValidIdentifiersProvider()
443
    {
444
        $emptyString = new EntityWithStringIdentifier();
445
446
        $emptyString->id = '';
447
448
        $nonEmptyString = new EntityWithStringIdentifier();
449
450
        $nonEmptyString->id = uniqid('id', true);
451
452
        $emptyStrings = new EntityWithCompositeStringIdentifier();
453
454
        $emptyStrings->id1 = '';
455
        $emptyStrings->id2 = '';
456
457
        $nonEmptyStrings = new EntityWithCompositeStringIdentifier();
458
459
        $nonEmptyStrings->id1 = uniqid('id1', true);
460
        $nonEmptyStrings->id2 = uniqid('id2', true);
461
462
        $booleanTrue = new EntityWithBooleanIdentifier();
463
464
        $booleanTrue->id = true;
465
466
        $booleanFalse = new EntityWithBooleanIdentifier();
467
468
        $booleanFalse->id = false;
469
470
        return [
471
            'empty string, single field'     => [$emptyString, ''],
472
            'non-empty string, single field' => [$nonEmptyString, $nonEmptyString->id],
473
            'empty strings, two fields'      => [$emptyStrings, ' '],
474
            'non-empty strings, two fields'  => [$nonEmptyStrings, $nonEmptyStrings->id1 . ' ' . $nonEmptyStrings->id2],
475
            'boolean true'                   => [$booleanTrue, '1'],
476
            'boolean false'                  => [$booleanFalse, ''],
477
        ];
478
    }
479
480
    public function testRegisteringAManagedInstanceRequiresANonEmptyIdentifier()
481
    {
482
        $this->expectException(ORMInvalidArgumentException::class);
483
484
        $this->unitOfWork->registerManaged(new EntityWithBooleanIdentifier(), [], []);
485
    }
486
487
    /**
488
     * @dataProvider entitiesWithInvalidIdentifiersProvider
489
     *
490
     * @param object $entity
491
     * @param array  $identifier
492
     *
493
     * @return void
494
     */
495
    public function testAddToIdentityMapInvalidIdentifiers($entity, array $identifier)
496
    {
497
        $this->expectException(ORMInvalidArgumentException::class);
498
499
        $this->unitOfWork->registerManaged($entity, $identifier, []);
500
    }
501
502
503
    public function entitiesWithInvalidIdentifiersProvider()
504
    {
505
        $firstNullString  = new EntityWithCompositeStringIdentifier();
506
507
        $firstNullString->id2 = uniqid('id2', true);
508
509
        $secondNullString = new EntityWithCompositeStringIdentifier();
510
511
        $secondNullString->id1 = uniqid('id1', true);
512
513
        return [
514
            'null string, single field'      => [new EntityWithStringIdentifier(), ['id' => null]],
515
            'null strings, two fields'       => [new EntityWithCompositeStringIdentifier(), ['id1' => null, 'id2' => null]],
516
            'first null string, two fields'  => [$firstNullString, ['id1' => null, 'id2' => $firstNullString->id2]],
517
            'second null string, two fields' => [$secondNullString, ['id1' => $secondNullString->id1, 'id2' => null]],
518
        ];
519
    }
520
521
    /**
522
     * Unlike next test, this one demonstrates that the problem does
523
     * not necessarily reproduce if all the pieces are being flushed together.
524
     *
525
     * @group DDC-2922
526
     * @group #1521
527
     */
528
    public function testNewAssociatedEntityPersistenceOfNewEntitiesThroughCascadedAssociationsFirst()
529
    {
530
        $persister1 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(CascadePersistedEntity::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...PersistedEntity::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
531
        $persister2 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(EntityWithCascadingAssociation::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...dingAssociation::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
532
        $persister3 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(EntityWithNonCascadingAssociation::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...dingAssociation::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
533
534
        $this->unitOfWork->setEntityPersister(CascadePersistedEntity::class, $persister1);
535
        $this->unitOfWork->setEntityPersister(EntityWithCascadingAssociation::class, $persister2);
536
        $this->unitOfWork->setEntityPersister(EntityWithNonCascadingAssociation::class, $persister3);
537
538
        $cascadePersisted = new CascadePersistedEntity();
539
        $cascading        = new EntityWithCascadingAssociation();
540
        $nonCascading     = new EntityWithNonCascadingAssociation();
541
542
        // First we persist and flush a EntityWithCascadingAssociation with
543
        // the cascading association not set. Having the "cascading path" involve
544
        // a non-new object is important to show that the ORM should be considering
545
        // cascades across entity changesets in subsequent flushes.
546
        $cascading->cascaded = $cascadePersisted;
547
        $nonCascading->cascaded = $cascadePersisted;
0 ignored issues
show
Bug introduced by
The property cascaded does not seem to exist. Did you mean nonCascaded?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
548
549
        $this->unitOfWork->persist($cascading);
550
        $this->unitOfWork->persist($nonCascading);
551
        $this->unitOfWork->commit();
552
553
        self::assertCount(1, $persister1->getInserts());
554
        self::assertCount(1, $persister2->getInserts());
555
        self::assertCount(1, $persister3->getInserts());
556
    }
557
558
    /**
559
     * This test exhibits the bug describe in the ticket, where an object that
560
     * ought to be reachable causes errors.
561
     *
562
     * @group DDC-2922
563
     * @group #1521
564
     */
565
    public function testNewAssociatedEntityPersistenceOfNewEntitiesThroughNonCascadedAssociationsFirst()
566
    {
567
        $persister1 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(CascadePersistedEntity::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...PersistedEntity::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
568
        $persister2 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(EntityWithCascadingAssociation::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...dingAssociation::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
569
        $persister3 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(EntityWithNonCascadingAssociation::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...dingAssociation::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
570
571
        $this->unitOfWork->setEntityPersister(CascadePersistedEntity::class, $persister1);
572
        $this->unitOfWork->setEntityPersister(EntityWithCascadingAssociation::class, $persister2);
573
        $this->unitOfWork->setEntityPersister(EntityWithNonCascadingAssociation::class, $persister3);
574
575
        $cascadePersisted = new CascadePersistedEntity();
576
        $cascading        = new EntityWithCascadingAssociation();
577
        $nonCascading     = new EntityWithNonCascadingAssociation();
578
579
        // First we persist and flush a EntityWithCascadingAssociation with
580
        // the cascading association not set. Having the "cascading path" involve
581
        // a non-new object is important to show that the ORM should be considering
582
        // cascades across entity changesets in subsequent flushes.
583
        $cascading->cascaded = null;
584
585
        $this->unitOfWork->persist($cascading);
586
        $this->unitOfWork->commit();
587
588
        self::assertCount(0, $persister1->getInserts());
589
        self::assertCount(1, $persister2->getInserts());
590
        self::assertCount(0, $persister3->getInserts());
591
592
        // Note that we have NOT directly persisted the CascadePersistedEntity,
593
        // and EntityWithNonCascadingAssociation does NOT have a configured
594
        // cascade-persist.
595
        $nonCascading->nonCascaded = $cascadePersisted;
596
597
        // However, EntityWithCascadingAssociation *does* have a cascade-persist
598
        // association, which ought to allow us to save the CascadePersistedEntity
599
        // anyway through that connection.
600
        $cascading->cascaded = $cascadePersisted;
601
602
        $this->unitOfWork->persist($nonCascading);
603
        $this->unitOfWork->commit();
604
605
        self::assertCount(1, $persister1->getInserts());
606
        self::assertCount(1, $persister2->getInserts());
607
        self::assertCount(1, $persister3->getInserts());
608
    }
609
610
    /**
611
     * This test exhibits the bug describe in the ticket, where an object that
612
     * ought to be reachable causes errors.
613
     *
614
     * @group DDC-2922
615
     * @group #1521
616
     */
617
    public function testPreviousDetectedIllegalNewNonCascadedEntitiesAreCleanedUpOnSubsequentCommits()
618
    {
619
        $persister1 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(CascadePersistedEntity::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...PersistedEntity::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
620
        $persister2 = new EntityPersisterMock($this->emMock, $this->emMock->getClassMetadata(EntityWithNonCascadingAssociation::class));
0 ignored issues
show
Documentation introduced by
$this->emMock->getClassM...dingAssociation::class) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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...
621
622
        $this->unitOfWork->setEntityPersister(CascadePersistedEntity::class, $persister1);
623
        $this->unitOfWork->setEntityPersister(EntityWithNonCascadingAssociation::class, $persister2);
624
625
        $cascadePersisted = new CascadePersistedEntity();
626
        $nonCascading = new EntityWithNonCascadingAssociation();
627
628
        // We explicitly cause the ORM to detect a non-persisted new entity in the association graph:
629
        $nonCascading->nonCascaded = $cascadePersisted;
630
631
        $this->unitOfWork->persist($nonCascading);
632
633
        try {
634
            $this->unitOfWork->commit();
635
636
            self::fail('An exception was supposed to be raised');
637
        } catch (ORMInvalidArgumentException $ignored) {
638
            self::assertEmpty($persister1->getInserts());
639
            self::assertEmpty($persister2->getInserts());
640
        }
641
642
        $this->unitOfWork->clear();
643
        $this->unitOfWork->persist(new CascadePersistedEntity());
644
        $this->unitOfWork->commit();
645
646
        // Persistence operations should just recover normally:
647
        self::assertCount(1, $persister1->getInserts());
648
        self::assertCount(0, $persister2->getInserts());
649
    }
650
651
    /**
652
     * @group DDC-3120
653
     */
654
    public function testCanInstantiateInternalPhpClassSubclass()
655
    {
656
        $classMetadata = new ClassMetadata(MyArrayObjectEntity::class, $this->metadataBuildingContext);
657
658
        self::assertInstanceOf(MyArrayObjectEntity::class, $this->unitOfWork->newInstance($classMetadata));
659
    }
660
661
    /**
662
     * @group DDC-3120
663
     */
664
    public function testCanInstantiateInternalPhpClassSubclassFromUnserializedMetadata()
665
    {
666
        /* @var $classMetadata ClassMetadata */
667
        $classMetadata = unserialize(
668
            serialize(
669
                new ClassMetadata(MyArrayObjectEntity::class, $this->metadataBuildingContext)
670
            )
671
        );
672
673
        $classMetadata->wakeupReflection(new RuntimeReflectionService());
674
675
        self::assertInstanceOf(MyArrayObjectEntity::class, $this->unitOfWork->newInstance($classMetadata));
676
    }
677
678
    /**
679
     * @group #6722
680
     * @group #6724
681
     */
682
    public function testCollectionChangesOnNonInitializedProxiesAreDetected()
683
    {
684
        $proxyFactory    = $this->emMock->getProxyFactory();
0 ignored issues
show
Unused Code introduced by
$proxyFactory 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...
685
        $owningMetadata  = $this->emMock->getClassMetadata(Owning::class);
0 ignored issues
show
Unused Code introduced by
$owningMetadata 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...
686
        $inverseMetadata = $this->emMock->getClassMetadata(Inverse::class);
0 ignored issues
show
Unused Code introduced by
$inverseMetadata 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...
687
        $owning          = $this->emMock->getReference(Owning::class, ['id' => 'abc']);
688
        $inverse         = $this->emMock->getReference(Inverse::class, ['id' => 'def']);
689
690
        /** @var $owning Owning|GhostObjectInterface */
691
        self::assertInstanceOf(GhostObjectInterface::class, $owning);
692
        self::assertInstanceOf(GhostObjectInterface::class, $inverse);
693
        self::assertFalse($owning->isProxyInitialized());
0 ignored issues
show
Bug introduced by
The method isProxyInitialized does only exist in ProxyManager\Proxy\GhostObjectInterface, but not in Doctrine\Tests\Models\Un...tionalManyToMany\Owning.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
694
        self::assertFalse($inverse->isProxyInitialized());
695
696
        /** @var $collection PersistentCollection */
697
        $collection = $owning->inverse;
698
699
        self::assertInstanceOf(PersistentCollection::class, $collection);
700
        self::assertFalse($collection->isInitialized());
701
702
        $collection->add($inverse);
703
704
        $this->unitOfWork->computeChangeSets();
705
706
        $collectionInserts = $this->unitOfWork->getScheduledCollectionUpdates();
707
708
        self::assertFalse($owning->isProxyInitialized());
709
        self::assertFalse($inverse->isProxyInitialized());
710
711
        self::assertCount(1, $collectionInserts);
712
        self::assertSame($owning->inverse, \reset($collectionInserts));
713
    }
714
}
715
716
/**
717
 * @ORM\Entity
718
 */
719
class NotifyChangedEntity implements NotifyPropertyChanged
720
{
721
    private $listeners = [];
722
    /**
723
     * @ORM\Id
724
     * @ORM\Column(type="integer")
725
     * @ORM\GeneratedValue
726
     */
727
    private $id;
728
    /**
729
     * @ORM\Column(type="string")
730
     */
731
    private $data;
732
733
    private $transient; // not persisted
734
735
    /** @ORM\OneToMany(targetEntity="NotifyChangedRelatedItem", mappedBy="owner") */
736
    private $items;
737
738
    public function  __construct() {
739
        $this->items = new ArrayCollection;
740
    }
741
742
    public function getId() {
743
        return $this->id;
744
    }
745
746
    public function getItems() {
747
        return $this->items;
748
    }
749
750
    public function setTransient($value) {
751
        if ($value != $this->transient) {
752
            $this->onPropertyChanged('transient', $this->transient, $value);
753
            $this->transient = $value;
754
        }
755
    }
756
757
    public function getData() {
758
        return $this->data;
759
    }
760
761
    public function setData($data) {
762
        if ($data != $this->data) {
763
            $this->onPropertyChanged('data', $this->data, $data);
764
            $this->data = $data;
765
        }
766
    }
767
768
    public function addPropertyChangedListener(PropertyChangedListener $listener)
769
    {
770
        $this->listeners[] = $listener;
771
    }
772
773
    protected function onPropertyChanged($propName, $oldValue, $newValue) {
774
        if ($this->listeners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->listeners of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
775
            foreach ($this->listeners as $listener) {
776
                $listener->propertyChanged($this, $propName, $oldValue, $newValue);
777
            }
778
        }
779
    }
780
}
781
782
/** @ORM\Entity */
783
class NotifyChangedRelatedItem
784
{
785
    /**
786
     * @ORM\Id
787
     * @ORM\Column(type="integer")
788
     * @ORM\GeneratedValue
789
     */
790
    private $id;
791
792
    /** @ORM\ManyToOne(targetEntity="NotifyChangedEntity", inversedBy="items") */
793
    private $owner;
794
795
    public function getId() {
796
        return $this->id;
797
    }
798
799
    public function getOwner() {
800
        return $this->owner;
801
    }
802
803
    public function setOwner($owner) {
804
        $this->owner = $owner;
805
    }
806
}
807
808
/** @ORM\Entity */
809
class VersionedAssignedIdentifierEntity
810
{
811
    /**
812
     * @ORM\Id @ORM\Column(type="integer")
813
     */
814
    public $id;
815
    /**
816
     * @ORM\Version @ORM\Column(type="integer")
817
     */
818
    public $version;
819
}
820
821
/** @ORM\Entity */
822
class EntityWithStringIdentifier
823
{
824
    /**
825
     * @ORM\Id @ORM\Column(type="string")
826
     *
827
     * @var string|null
828
     */
829
    public $id;
830
}
831
832
/** @ORM\Entity */
833
class EntityWithBooleanIdentifier
834
{
835
    /**
836
     * @ORM\Id @ORM\Column(type="boolean")
837
     *
838
     * @var bool|null
839
     */
840
    public $id;
841
}
842
843
/** @ORM\Entity */
844
class EntityWithCompositeStringIdentifier
845
{
846
    /**
847
     * @ORM\Id @ORM\Column(type="string")
848
     *
849
     * @var string|null
850
     */
851
    public $id1;
852
853
    /**
854
     * @ORM\Id @ORM\Column(type="string")
855
     *
856
     * @var string|null
857
     */
858
    public $id2;
859
}
860
861
/** @ORM\Entity */
862
class EntityWithRandomlyGeneratedField
863
{
864
    /** @ORM\Id @ORM\Column(type="string") */
865
    public $id;
866
867
    /**
868
     * @ORM\Column(type="integer")
869
     */
870
    public $generatedField;
871
872
    public function __construct()
873
    {
874
        $this->id             = uniqid('id', true);
875
        $this->generatedField = random_int(0, 100000);
876
    }
877
}
878
879
/** @ORM\Entity */
880
class CascadePersistedEntity
881
{
882
    /** @ORM\Id @ORM\Column(type="string") @ORM\GeneratedValue(strategy="NONE") */
883
    private $id;
884
885
    public function __construct()
886
    {
887
        $this->id = uniqid(self::class, true);
888
    }
889
}
890
891
/** @ORM\Entity */
892
class EntityWithCascadingAssociation
893
{
894
    /** @ORM\Id @ORM\Column(type="string") @ORM\GeneratedValue(strategy="NONE") */
895
    private $id;
896
897
    /** @ORM\ManyToOne(targetEntity=CascadePersistedEntity::class, cascade={"persist"}) */
898
    public $cascaded;
899
900
    public function __construct()
901
    {
902
        $this->id = uniqid(self::class, true);
903
    }
904
}
905
906
/** @ORM\Entity */
907
class EntityWithNonCascadingAssociation
908
{
909
    /** @ORM\Id @ORM\Column(type="string") @ORM\GeneratedValue(strategy="NONE") */
910
    private $id;
911
912
    /** @ORM\ManyToOne(targetEntity=CascadePersistedEntity::class) */
913
    public $nonCascaded;
914
915
    public function __construct()
916
    {
917
        $this->id = uniqid(self::class, true);
918
    }
919
}
920
921
class MyArrayObjectEntity extends \ArrayObject
922
{
923
}
924