Completed
Pull Request — master (#5579)
by Huberty
12:21
created

testLockWithoutEntityThrowsException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4286
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\ORM;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\Common\NotifyPropertyChanged;
7
use Doctrine\Common\PropertyChangedListener;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\UnitOfWork;
10
use Doctrine\Tests\Mocks\ConnectionMock;
11
use Doctrine\Tests\Mocks\DriverMock;
12
use Doctrine\Tests\Mocks\EntityManagerMock;
13
use Doctrine\Tests\Mocks\EntityPersisterMock;
14
use Doctrine\Tests\Mocks\UnitOfWorkMock;
15
use Doctrine\Tests\Models\Forum\ForumAvatar;
16
use Doctrine\Tests\Models\Forum\ForumUser;
17
use stdClass;
18
19
/**
20
 * UnitOfWork tests.
21
 */
22
class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
23
{
24
    /**
25
     * SUT
26
     *
27
     * @var UnitOfWorkMock
28
     */
29
    private $_unitOfWork;
30
31
    /**
32
     * Provides a sequence mock to the UnitOfWork
33
     *
34
     * @var ConnectionMock
35
     */
36
    private $_connectionMock;
37
38
    /**
39
     * The EntityManager mock that provides the mock persisters
40
     *
41
     * @var EntityManagerMock
42
     */
43
    private $_emMock;
44
45
    protected function setUp() {
46
        parent::setUp();
47
        $this->_connectionMock = new ConnectionMock(array(), new DriverMock());
48
        $this->_emMock = EntityManagerMock::create($this->_connectionMock);
49
        // SUT
50
        $this->_unitOfWork = new UnitOfWorkMock($this->_emMock);
51
        $this->_emMock->setUnitOfWork($this->_unitOfWork);
52
    }
53
54
    protected function tearDown() {
55
    }
56
57
    public function testRegisterRemovedOnNewEntityIsIgnored()
58
    {
59
        $user = new ForumUser();
60
        $user->username = 'romanb';
61
        $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user));
62
        $this->_unitOfWork->scheduleForDelete($user);
63
        $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user));
64
    }
65
66
67
    /* Operational tests */
68
69
    public function testSavingSingleEntityWithIdentityColumnForcesInsert()
70
    {
71
        // Setup fake persister and id generator for identity generation
72
        $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\Models\Forum\ForumUser'));
73
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister);
74
        $userPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
75
76
        // Test
77
        $user = new ForumUser();
78
        $user->username = 'romanb';
79
        $this->_unitOfWork->persist($user);
80
81
        // Check
82
        $this->assertEquals(0, count($userPersister->getInserts()));
83
        $this->assertEquals(0, count($userPersister->getUpdates()));
84
        $this->assertEquals(0, count($userPersister->getDeletes()));
85
        $this->assertFalse($this->_unitOfWork->isInIdentityMap($user));
86
        // should no longer be scheduled for insert
87
        $this->assertTrue($this->_unitOfWork->isScheduledForInsert($user));
88
89
        // Now lets check whether a subsequent commit() does anything
90
        $userPersister->reset();
91
92
        // Test
93
        $this->_unitOfWork->commit();
94
95
        // Check.
96
        $this->assertEquals(1, count($userPersister->getInserts()));
97
        $this->assertEquals(0, count($userPersister->getUpdates()));
98
        $this->assertEquals(0, count($userPersister->getDeletes()));
99
100
        // should have an id
101
        $this->assertTrue(is_numeric($user->id));
102
    }
103
104
    /**
105
     * Tests a scenario where a save() operation is cascaded from a ForumUser
106
     * to its associated ForumAvatar, both entities using IDENTITY id generation.
107
     */
108
    public function testCascadedIdentityColumnInsert()
109
    {
110
        // Setup fake persister and id generator for identity generation
111
        //ForumUser
112
        $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\Models\Forum\ForumUser'));
113
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister);
114
        $userPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
115
        // ForumAvatar
116
        $avatarPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\Models\Forum\ForumAvatar'));
117
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarPersister);
118
        $avatarPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
119
120
        // Test
121
        $user = new ForumUser();
122
        $user->username = 'romanb';
123
        $avatar = new ForumAvatar();
124
        $user->avatar = $avatar;
125
        $this->_unitOfWork->persist($user); // save cascaded to avatar
126
127
        $this->_unitOfWork->commit();
128
129
        $this->assertTrue(is_numeric($user->id));
130
        $this->assertTrue(is_numeric($avatar->id));
131
132
        $this->assertEquals(1, count($userPersister->getInserts()));
133
        $this->assertEquals(0, count($userPersister->getUpdates()));
134
        $this->assertEquals(0, count($userPersister->getDeletes()));
135
136
        $this->assertEquals(1, count($avatarPersister->getInserts()));
137
        $this->assertEquals(0, count($avatarPersister->getUpdates()));
138
        $this->assertEquals(0, count($avatarPersister->getDeletes()));
139
    }
140
141
    public function testChangeTrackingNotify()
142
    {
143
        $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\ORM\NotifyChangedEntity'));
144
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedEntity', $persister);
145
        $itemPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\ORM\NotifyChangedRelatedItem'));
146
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedRelatedItem', $itemPersister);
147
148
        $entity = new NotifyChangedEntity;
149
        $entity->setData('thedata');
150
        $this->_unitOfWork->persist($entity);
151
152
        $this->_unitOfWork->commit();
153
        $this->assertCount(1, $persister->getInserts());
154
155
        $persister->reset();
156
157
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
158
159
        $entity->setData('newdata');
160
        $entity->setTransient('newtransientvalue');
161
162
        $this->assertTrue($this->_unitOfWork->isScheduledForDirtyCheck($entity));
163
164
        $this->assertEquals(array('data' => array('thedata', 'newdata')), $this->_unitOfWork->getEntityChangeSet($entity));
165
166
        $item = new NotifyChangedRelatedItem();
167
        $entity->getItems()->add($item);
168
        $item->setOwner($entity);
169
        $this->_unitOfWork->persist($item);
170
171
        $this->_unitOfWork->commit();
172
        $this->assertEquals(1, count($itemPersister->getInserts()));
173
        $persister->reset();
174
        $itemPersister->reset();
175
176
177
        $entity->getItems()->removeElement($item);
178
        $item->setOwner(null);
179
        $this->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...
180
        $this->_unitOfWork->commit();
181
        $updates = $itemPersister->getUpdates();
182
        $this->assertEquals(1, count($updates));
183
        $this->assertTrue($updates[0] === $item);
184
    }
185
186
    public function testGetEntityStateOnVersionedEntityWithAssignedIdentifier()
187
    {
188
        $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity'));
189
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity', $persister);
190
191
        $e = new VersionedAssignedIdentifierEntity();
192
        $e->id = 42;
193
        $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($e));
194
        $this->assertFalse($persister->isExistsCalled());
195
    }
196
197
    public function testGetEntityStateWithAssignedIdentity()
198
    {
199
        $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\Models\CMS\CmsPhonenumber'));
200
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\CMS\CmsPhonenumber', $persister);
201
202
        $ph = new \Doctrine\Tests\Models\CMS\CmsPhonenumber();
203
        $ph->phonenumber = '12345';
204
205
        $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($ph));
206
        $this->assertTrue($persister->isExistsCalled());
207
208
        $persister->reset();
209
210
        // if the entity is already managed the exists() check should be skipped
211
        $this->_unitOfWork->registerManaged($ph, array('phonenumber' => '12345'), array());
212
        $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($ph));
213
        $this->assertFalse($persister->isExistsCalled());
214
        $ph2 = new \Doctrine\Tests\Models\CMS\CmsPhonenumber();
215
        $ph2->phonenumber = '12345';
216
        $this->assertEquals(UnitOfWork::STATE_DETACHED, $this->_unitOfWork->getEntityState($ph2));
217
        $this->assertFalse($persister->isExistsCalled());
218
    }
219
220
    /**
221
     * DDC-2086 [GH-484] Prevented 'Undefined index' notice when updating.
222
     */
223
    public function testNoUndefinedIndexNoticeOnScheduleForUpdateWithoutChanges()
224
    {
225
        // Setup fake persister and id generator
226
        $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata('Doctrine\Tests\Models\Forum\ForumUser'));
227
        $userPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
228
        $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister);
229
230
        // Create a test user
231
        $user = new ForumUser();
232
        $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...
233
        $this->_unitOfWork->persist($user);
234
        $this->_unitOfWork->commit();
235
236
        // Schedule user for update without changes
237
        $this->_unitOfWork->scheduleForUpdate($user);
238
239
        // This commit should not raise an E_NOTICE
240
        $this->_unitOfWork->commit();
241
    }
242
243
    /**
244
     * @group DDC-1984
245
     */
246
    public function testLockWithoutEntityThrowsException()
247
    {
248
        $this->setExpectedException('InvalidArgumentException');
249
        $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...
250
    }
251
252
    /**
253
     * @group DDC-3490
254
     *
255
     * @dataProvider invalidAssociationValuesDataProvider
256
     *
257
     * @param mixed $invalidValue
258
     */
259
    public function testRejectsPersistenceOfObjectsWithInvalidAssociationValue($invalidValue)
260
    {
261
        $this->_unitOfWork->setEntityPersister(
262
            'Doctrine\Tests\Models\Forum\ForumUser',
263
            new EntityPersisterMock(
264
                $this->_emMock,
265
                $this->_emMock->getClassMetadata('Doctrine\Tests\Models\Forum\ForumUser')
266
            )
267
        );
268
269
        $user           = new ForumUser();
270
        $user->username = 'John';
271
        $user->avatar   = $invalidValue;
272
273
        $this->setExpectedException('Doctrine\ORM\ORMInvalidArgumentException');
274
275
        $this->_unitOfWork->persist($user);
276
    }
277
278
    /**
279
     * @group DDC-3490
280
     *
281
     * @dataProvider invalidAssociationValuesDataProvider
282
     *
283
     * @param mixed $invalidValue
284
     */
285
    public function testRejectsChangeSetComputationForObjectsWithInvalidAssociationValue($invalidValue)
286
    {
287
        $metadata = $this->_emMock->getClassMetadata('Doctrine\Tests\Models\Forum\ForumUser');
288
289
        $this->_unitOfWork->setEntityPersister(
290
            'Doctrine\Tests\Models\Forum\ForumUser',
291
            new EntityPersisterMock($this->_emMock, $metadata)
292
        );
293
294
        $user = new ForumUser();
295
296
        $this->_unitOfWork->persist($user);
297
298
        $user->username = 'John';
299
        $user->avatar   = $invalidValue;
300
301
        $this->setExpectedException('Doctrine\ORM\ORMInvalidArgumentException');
302
303
        $this->_unitOfWork->computeChangeSet($metadata, $user);
304
    }
305
306
    /**
307
     * @group DDC-3619
308
     * @group 1338
309
     */
310
    public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGarbageCollected()
311
    {
312
        $entity     = new ForumUser();
313
        $entity->id = 123;
314
315
        $this->_unitOfWork->registerManaged($entity, array('id' => 123), array());
316
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
317
318
        $this->_unitOfWork->remove($entity);
319
        $this->assertFalse($this->_unitOfWork->isInIdentityMap($entity));
320
321
        $this->_unitOfWork->persist($entity);
322
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
323
    }
324
325
    /**
326
     * @group 5579
327
     */
328
    public function testEntityChangeSetNotClearAfterFlushOnEntityOrArrayOfEntity()
329
    {
330
        // Create and Set first entity
331
        $entity1 = new NotifyChangedEntity;
332
        $entity1->setData('thedata');
333
        $this->_unitOfWork->persist($entity1);
334
335
        // Create and Set second entity
336
        $entity2 = new NotifyChangedEntity;
337
        $entity2->setData('thedata');
338
        $this->_unitOfWork->persist($entity2);
339
340
        $this->_unitOfWork->commit($entity1);
341
        $this->assertCount(1, $this->_unitOfWork->getEntityChangeSet($entity2));
342
343
        // Create and Set third entity
344
        $entity3 = new NotifyChangedEntity;
345
        $entity3->setData('thedata');
346
        $this->_unitOfWork->persist($entity3);
347
348
        $this->_unitOfWork->commit([$entity1,$entity2]);
349
        $this->assertCount(1, $this->_unitOfWork->getEntityChangeSet($entity3));
350
351
    }
352
353
    /**
354
     * Data Provider
355
     *
356
     * @return mixed[][]
357
     */
358
    public function invalidAssociationValuesDataProvider()
359
    {
360
        return [
361
            ['foo'],
362
            [['foo']],
363
            [''],
364
            [[]],
365
            [new stdClass()],
366
            [new ArrayCollection()],
367
        ];
368
    }
369
}
370
371
/**
372
 * @Entity
373
 */
374
class NotifyChangedEntity implements NotifyPropertyChanged
375
{
376
    private $_listeners = array();
377
    /**
378
     * @Id
379
     * @Column(type="integer")
380
     * @GeneratedValue
381
     */
382
    private $id;
383
    /**
384
     * @Column(type="string")
385
     */
386
    private $data;
387
388
    private $transient; // not persisted
389
390
    /** @OneToMany(targetEntity="NotifyChangedRelatedItem", mappedBy="owner") */
391
    private $items;
392
393
    public function  __construct() {
394
        $this->items = new ArrayCollection;
395
    }
396
397
    public function getId() {
398
        return $this->id;
399
    }
400
401
    public function getItems() {
402
        return $this->items;
403
    }
404
405
    public function setTransient($value) {
406
        if ($value != $this->transient) {
407
            $this->_onPropertyChanged('transient', $this->transient, $value);
408
            $this->transient = $value;
409
        }
410
    }
411
412
    public function getData() {
413
        return $this->data;
414
    }
415
416
    public function setData($data) {
417
        if ($data != $this->data) {
418
            $this->_onPropertyChanged('data', $this->data, $data);
419
            $this->data = $data;
420
        }
421
    }
422
423
    public function addPropertyChangedListener(PropertyChangedListener $listener)
424
    {
425
        $this->_listeners[] = $listener;
426
    }
427
428
    protected function _onPropertyChanged($propName, $oldValue, $newValue) {
429
        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...
430
            foreach ($this->_listeners as $listener) {
431
                $listener->propertyChanged($this, $propName, $oldValue, $newValue);
432
            }
433
        }
434
    }
435
}
436
437
/** @Entity */
438
class NotifyChangedRelatedItem
439
{
440
    /**
441
     * @Id
442
     * @Column(type="integer")
443
     * @GeneratedValue
444
     */
445
    private $id;
446
447
    /** @ManyToOne(targetEntity="NotifyChangedEntity", inversedBy="items") */
448
    private $owner;
449
450
    public function getId() {
451
        return $this->id;
452
    }
453
454
    public function getOwner() {
455
        return $this->owner;
456
    }
457
458
    public function setOwner($owner) {
459
        $this->owner = $owner;
460
    }
461
}
462
463
/** @Entity */
464
class VersionedAssignedIdentifierEntity
465
{
466
    /**
467
     * @Id @Column(type="integer")
468
     */
469
    public $id;
470
    /**
471
     * @Version @Column(type="integer")
472
     */
473
    public $version;
474
}
475