Completed
Push — master ( 90b745...3bc61d )
by Marco
13:14
created

UnitOfWorkTest::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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