Completed
Push — master ( 6e6be3...cd1a5f )
by Marco
11s
created

EntityWithRandomlyGeneratedField::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
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\EventManager;
7
use Doctrine\Common\NotifyPropertyChanged;
8
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
9
use Doctrine\Common\PropertyChangedListener;
10
use Doctrine\ORM\Events;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use Doctrine\ORM\ORMInvalidArgumentException;
13
use Doctrine\ORM\UnitOfWork;
14
use Doctrine\Tests\Mocks\ConnectionMock;
15
use Doctrine\Tests\Mocks\DriverMock;
16
use Doctrine\Tests\Mocks\EntityManagerMock;
17
use Doctrine\Tests\Mocks\EntityPersisterMock;
18
use Doctrine\Tests\Mocks\UnitOfWorkMock;
19
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
20
use Doctrine\Tests\Models\CMS\CmsUser;
21
use Doctrine\Tests\Models\Forum\ForumAvatar;
22
use Doctrine\Tests\Models\Forum\ForumUser;
23
use Doctrine\Tests\Models\GeoNames\City;
24
use Doctrine\Tests\Models\GeoNames\Country;
25
use Doctrine\Tests\OrmTestCase;
26
use stdClass;
27
28
/**
29
 * UnitOfWork tests.
30
 */
31
class UnitOfWorkTest extends OrmTestCase
32
{
33
    /**
34
     * SUT
35
     *
36
     * @var UnitOfWorkMock
37
     */
38
    private $_unitOfWork;
39
40
    /**
41
     * Provides a sequence mock to the UnitOfWork
42
     *
43
     * @var ConnectionMock
44
     */
45
    private $_connectionMock;
46
47
    /**
48
     * The EntityManager mock that provides the mock persisters
49
     *
50
     * @var EntityManagerMock
51
     */
52
    private $_emMock;
53
54
    /**
55
     * @var EventManager|\PHPUnit_Framework_MockObject_MockObject
56
     */
57
    private $eventManager;
58
59
    protected function setUp()
60
    {
61
        parent::setUp();
62
        $this->_connectionMock = new ConnectionMock([], new DriverMock());
63
        $this->eventManager = $this->getMockBuilder(EventManager::class)->getMock();
64
        $this->_emMock = EntityManagerMock::create($this->_connectionMock, null, $this->eventManager);
65
        // SUT
66
        $this->_unitOfWork = new UnitOfWorkMock($this->_emMock);
67
        $this->_emMock->setUnitOfWork($this->_unitOfWork);
68
    }
69
70
    public function testRegisterRemovedOnNewEntityIsIgnored()
71
    {
72
        $user = new ForumUser();
73
        $user->username = 'romanb';
74
        $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user));
75
        $this->_unitOfWork->scheduleForDelete($user);
76
        $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user));
77
    }
78
79
80
    /* Operational tests */
81
82
    public function testSavingSingleEntityWithIdentityColumnForcesInsert()
83
    {
84
        // Setup fake persister and id generator for identity generation
85
        $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(ForumUser::class));
86
        $this->_unitOfWork->setEntityPersister(ForumUser::class, $userPersister);
87
        $userPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
88
89
        // Test
90
        $user = new ForumUser();
91
        $user->username = 'romanb';
92
        $this->_unitOfWork->persist($user);
93
94
        // Check
95
        $this->assertEquals(0, count($userPersister->getInserts()));
96
        $this->assertEquals(0, count($userPersister->getUpdates()));
97
        $this->assertEquals(0, count($userPersister->getDeletes()));
98
        $this->assertFalse($this->_unitOfWork->isInIdentityMap($user));
99
        // should no longer be scheduled for insert
100
        $this->assertTrue($this->_unitOfWork->isScheduledForInsert($user));
101
102
        // Now lets check whether a subsequent commit() does anything
103
        $userPersister->reset();
104
105
        // Test
106
        $this->_unitOfWork->commit();
107
108
        // Check.
109
        $this->assertEquals(1, count($userPersister->getInserts()));
110
        $this->assertEquals(0, count($userPersister->getUpdates()));
111
        $this->assertEquals(0, count($userPersister->getDeletes()));
112
113
        // should have an id
114
        $this->assertTrue(is_numeric($user->id));
115
    }
116
117
    /**
118
     * Tests a scenario where a save() operation is cascaded from a ForumUser
119
     * to its associated ForumAvatar, both entities using IDENTITY id generation.
120
     */
121
    public function testCascadedIdentityColumnInsert()
122
    {
123
        // Setup fake persister and id generator for identity generation
124
        //ForumUser
125
        $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(ForumUser::class));
126
        $this->_unitOfWork->setEntityPersister(ForumUser::class, $userPersister);
127
        $userPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
128
        // ForumAvatar
129
        $avatarPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(ForumAvatar::class));
130
        $this->_unitOfWork->setEntityPersister(ForumAvatar::class, $avatarPersister);
131
        $avatarPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
132
133
        // Test
134
        $user = new ForumUser();
135
        $user->username = 'romanb';
136
        $avatar = new ForumAvatar();
137
        $user->avatar = $avatar;
138
        $this->_unitOfWork->persist($user); // save cascaded to avatar
139
140
        $this->_unitOfWork->commit();
141
142
        $this->assertTrue(is_numeric($user->id));
143
        $this->assertTrue(is_numeric($avatar->id));
144
145
        $this->assertEquals(1, count($userPersister->getInserts()));
146
        $this->assertEquals(0, count($userPersister->getUpdates()));
147
        $this->assertEquals(0, count($userPersister->getDeletes()));
148
149
        $this->assertEquals(1, count($avatarPersister->getInserts()));
150
        $this->assertEquals(0, count($avatarPersister->getUpdates()));
151
        $this->assertEquals(0, count($avatarPersister->getDeletes()));
152
    }
153
154
    public function testChangeTrackingNotify()
155
    {
156
        $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(NotifyChangedEntity::class));
157
        $this->_unitOfWork->setEntityPersister(NotifyChangedEntity::class, $persister);
158
        $itemPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(NotifyChangedRelatedItem::class));
159
        $this->_unitOfWork->setEntityPersister(NotifyChangedRelatedItem::class, $itemPersister);
160
161
        $entity = new NotifyChangedEntity;
162
        $entity->setData('thedata');
163
        $this->_unitOfWork->persist($entity);
164
165
        $this->_unitOfWork->commit();
166
        $this->assertEquals(1, count($persister->getInserts()));
167
        $persister->reset();
168
169
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
170
171
        $entity->setData('newdata');
172
        $entity->setTransient('newtransientvalue');
173
174
        $this->assertTrue($this->_unitOfWork->isScheduledForDirtyCheck($entity));
175
176
        $this->assertEquals(['data' => ['thedata', 'newdata']], $this->_unitOfWork->getEntityChangeSet($entity));
177
178
        $item = new NotifyChangedRelatedItem();
179
        $entity->getItems()->add($item);
180
        $item->setOwner($entity);
181
        $this->_unitOfWork->persist($item);
182
183
        $this->_unitOfWork->commit();
184
        $this->assertEquals(1, count($itemPersister->getInserts()));
185
        $persister->reset();
186
        $itemPersister->reset();
187
188
189
        $entity->getItems()->removeElement($item);
190
        $item->setOwner(null);
191
        $this->assertTrue($entity->getItems()->isDirty());
192
        $this->_unitOfWork->commit();
193
        $updates = $itemPersister->getUpdates();
194
        $this->assertEquals(1, count($updates));
195
        $this->assertTrue($updates[0] === $item);
196
    }
197
198
    public function testGetEntityStateOnVersionedEntityWithAssignedIdentifier()
199
    {
200
        $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(VersionedAssignedIdentifierEntity::class));
201
        $this->_unitOfWork->setEntityPersister(VersionedAssignedIdentifierEntity::class, $persister);
202
203
        $e = new VersionedAssignedIdentifierEntity();
204
        $e->id = 42;
205
        $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($e));
206
        $this->assertFalse($persister->isExistsCalled());
207
    }
208
209
    public function testGetEntityStateWithAssignedIdentity()
210
    {
211
        $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(CmsPhonenumber::class));
212
        $this->_unitOfWork->setEntityPersister(CmsPhonenumber::class, $persister);
213
214
        $ph = new CmsPhonenumber();
215
        $ph->phonenumber = '12345';
216
217
        $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($ph));
218
        $this->assertTrue($persister->isExistsCalled());
219
220
        $persister->reset();
221
222
        // if the entity is already managed the exists() check should be skipped
223
        $this->_unitOfWork->registerManaged($ph, ['phonenumber' => '12345'], []);
224
        $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($ph));
225
        $this->assertFalse($persister->isExistsCalled());
226
        $ph2 = new CmsPhonenumber();
227
        $ph2->phonenumber = '12345';
228
        $this->assertEquals(UnitOfWork::STATE_DETACHED, $this->_unitOfWork->getEntityState($ph2));
229
        $this->assertFalse($persister->isExistsCalled());
230
    }
231
232
    /**
233
     * DDC-2086 [GH-484] Prevented 'Undefined index' notice when updating.
234
     */
235
    public function testNoUndefinedIndexNoticeOnScheduleForUpdateWithoutChanges()
236
    {
237
        // Setup fake persister and id generator
238
        $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(ForumUser::class));
239
        $userPersister->setMockIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
240
        $this->_unitOfWork->setEntityPersister(ForumUser::class, $userPersister);
241
242
        // Create a test user
243
        $user = new ForumUser();
244
        $user->name = 'Jasper';
245
        $this->_unitOfWork->persist($user);
246
        $this->_unitOfWork->commit();
247
248
        // Schedule user for update without changes
249
        $this->_unitOfWork->scheduleForUpdate($user);
250
251
        // This commit should not raise an E_NOTICE
252
        $this->_unitOfWork->commit();
253
    }
254
255
    /**
256
     * @group DDC-1984
257
     */
258
    public function testLockWithoutEntityThrowsException()
259
    {
260
        $this->expectException(\InvalidArgumentException::class);
261
        $this->_unitOfWork->lock(null, null, null);
262
    }
263
264
    /**
265
     * @group DDC-3490
266
     *
267
     * @dataProvider invalidAssociationValuesDataProvider
268
     *
269
     * @param mixed $invalidValue
270
     */
271
    public function testRejectsPersistenceOfObjectsWithInvalidAssociationValue($invalidValue)
272
    {
273
        $this->_unitOfWork->setEntityPersister(
274
            ForumUser::class,
275
            new EntityPersisterMock(
276
                $this->_emMock,
277
                $this->_emMock->getClassMetadata(ForumUser::class)
278
            )
279
        );
280
281
        $user           = new ForumUser();
282
        $user->username = 'John';
283
        $user->avatar   = $invalidValue;
284
285
        $this->expectException(\Doctrine\ORM\ORMInvalidArgumentException::class);
286
287
        $this->_unitOfWork->persist($user);
288
    }
289
290
    /**
291
     * @group DDC-3490
292
     *
293
     * @dataProvider invalidAssociationValuesDataProvider
294
     *
295
     * @param mixed $invalidValue
296
     */
297
    public function testRejectsChangeSetComputationForObjectsWithInvalidAssociationValue($invalidValue)
298
    {
299
        $metadata = $this->_emMock->getClassMetadata(ForumUser::class);
300
301
        $this->_unitOfWork->setEntityPersister(
302
            ForumUser::class,
303
            new EntityPersisterMock($this->_emMock, $metadata)
304
        );
305
306
        $user = new ForumUser();
307
308
        $this->_unitOfWork->persist($user);
309
310
        $user->username = 'John';
311
        $user->avatar   = $invalidValue;
312
313
        $this->expectException(\Doctrine\ORM\ORMInvalidArgumentException::class);
314
315
        $this->_unitOfWork->computeChangeSet($metadata, $user);
316
    }
317
318
    /**
319
     * @group DDC-3619
320
     * @group 1338
321
     */
322
    public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGarbageCollected()
323
    {
324
        $entity     = new ForumUser();
325
        $entity->id = 123;
326
327
        $this->_unitOfWork->registerManaged($entity, ['id' => 123], []);
328
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
329
330
        $this->_unitOfWork->remove($entity);
331
        $this->assertFalse($this->_unitOfWork->isInIdentityMap($entity));
332
333
        $this->_unitOfWork->persist($entity);
334
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
335
    }
336
337
    /**
338
     * @group 5849
339
     * @group 5850
340
     */
341
    public function testPersistedEntityAndClearManager()
342
    {
343
        $entity1 = new City(123, 'London');
344
        $entity2 = new Country(456, 'United Kingdom');
345
346
        $this->_unitOfWork->persist($entity1);
347
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1));
348
349
        $this->_unitOfWork->persist($entity2);
350
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity2));
351
352
        $this->_unitOfWork->clear(Country::class);
353
        $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1));
354
        $this->assertFalse($this->_unitOfWork->isInIdentityMap($entity2));
355
        $this->assertTrue($this->_unitOfWork->isScheduledForInsert($entity1));
356
        $this->assertFalse($this->_unitOfWork->isScheduledForInsert($entity2));
357
    }
358
359
    /**
360
     * Data Provider
361
     *
362
     * @return mixed[][]
363
     */
364
    public function invalidAssociationValuesDataProvider()
365
    {
366
        return [
367
            ['foo'],
368
            [['foo']],
369
            [''],
370
            [[]],
371
            [new stdClass()],
372
            [new ArrayCollection()],
373
        ];
374
    }
375
376
    /**
377
     * @dataProvider entitiesWithValidIdentifiersProvider
378
     *
379
     * @param object $entity
380
     * @param string $idHash
381
     *
382
     * @return void
383
     */
384
    public function testAddToIdentityMapValidIdentifiers($entity, $idHash)
385
    {
386
        $this->_unitOfWork->persist($entity);
387
        $this->_unitOfWork->addToIdentityMap($entity);
388
389
        self::assertSame($entity, $this->_unitOfWork->getByIdHash($idHash, get_class($entity)));
390
    }
391
392
    public function entitiesWithValidIdentifiersProvider()
393
    {
394
        $emptyString = new EntityWithStringIdentifier();
395
396
        $emptyString->id = '';
397
398
        $nonEmptyString = new EntityWithStringIdentifier();
399
400
        $nonEmptyString->id = uniqid('id', true);
401
402
        $emptyStrings = new EntityWithCompositeStringIdentifier();
403
404
        $emptyStrings->id1 = '';
405
        $emptyStrings->id2 = '';
406
407
        $nonEmptyStrings = new EntityWithCompositeStringIdentifier();
408
409
        $nonEmptyStrings->id1 = uniqid('id1', true);
410
        $nonEmptyStrings->id2 = uniqid('id2', true);
411
412
        $booleanTrue = new EntityWithBooleanIdentifier();
413
414
        $booleanTrue->id = true;
415
416
        $booleanFalse = new EntityWithBooleanIdentifier();
417
418
        $booleanFalse->id = false;
419
420
        return [
421
            'empty string, single field'     => [$emptyString, ''],
422
            'non-empty string, single field' => [$nonEmptyString, $nonEmptyString->id],
423
            'empty strings, two fields'      => [$emptyStrings, ' '],
424
            'non-empty strings, two fields'  => [$nonEmptyStrings, $nonEmptyStrings->id1 . ' ' . $nonEmptyStrings->id2],
425
            'boolean true'                   => [$booleanTrue, '1'],
426
            'boolean false'                  => [$booleanFalse, ''],
427
        ];
428
    }
429
430
    public function testRegisteringAManagedInstanceRequiresANonEmptyIdentifier()
431
    {
432
        $this->expectException(ORMInvalidArgumentException::class);
433
434
        $this->_unitOfWork->registerManaged(new EntityWithBooleanIdentifier(), [], []);
435
    }
436
437
    /**
438
     * @dataProvider entitiesWithInvalidIdentifiersProvider
439
     *
440
     * @param object $entity
441
     * @param array  $identifier
442
     *
443
     * @return void
444
     */
445
    public function testAddToIdentityMapInvalidIdentifiers($entity, array $identifier)
446
    {
447
        $this->expectException(ORMInvalidArgumentException::class);
448
449
        $this->_unitOfWork->registerManaged($entity, $identifier, []);
450
    }
451
452
453
    public function entitiesWithInvalidIdentifiersProvider()
454
    {
455
        $firstNullString  = new EntityWithCompositeStringIdentifier();
456
457
        $firstNullString->id2 = uniqid('id2', true);
458
459
        $secondNullString = new EntityWithCompositeStringIdentifier();
460
461
        $secondNullString->id1 = uniqid('id1', true);
462
463
        return [
464
            'null string, single field'      => [new EntityWithStringIdentifier(), ['id' => null]],
465
            'null strings, two fields'       => [new EntityWithCompositeStringIdentifier(), ['id1' => null, 'id2' => null]],
466
            'first null string, two fields'  => [$firstNullString, ['id1' => null, 'id2' => $firstNullString->id2]],
467
            'second null string, two fields' => [$secondNullString, ['id1' => $secondNullString->id1, 'id2' => null]],
468
        ];
469
    }
470
471
    /**
472
     * @group 5689
473
     * @group 1465
474
     */
475
    public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMap()
476
    {
477
        $user       = new CmsUser();
478
        $user->name = 'ocramius';
479
        $mergedUser = $this->_unitOfWork->merge($user);
480
481
        self::assertSame([], $this->_unitOfWork->getOriginalEntityData($user), 'No original data was stored');
482
        self::assertSame([], $this->_unitOfWork->getOriginalEntityData($mergedUser), 'No original data was stored');
483
484
485
        $user       = null;
486
        $mergedUser = null;
487
488
        // force garbage collection of $user (frees the used object hashes, which may be recycled)
489
        gc_collect_cycles();
490
491
        $newUser       = new CmsUser();
492
        $newUser->name = 'ocramius';
493
494
        $this->_unitOfWork->persist($newUser);
495
496
        self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored');
497
    }
498
499
    /**
500
     * @group DDC-1955
501
     * @group 5570
502
     * @group 6174
503
     */
504
    public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData()
505
    {
506
        $entity = new EntityWithRandomlyGeneratedField();
507
508
        $generatedFieldValue = $entity->generatedField;
509
510
        $this
511
            ->eventManager
512
            ->expects(self::any())
513
            ->method('hasListeners')
514
            ->willReturnCallback(function ($eventName) {
515
                return $eventName === Events::prePersist;
516
            });
517
        $this
518
            ->eventManager
519
            ->expects(self::once())
520
            ->method('dispatchEvent')
521
            ->with(
522
                self::anything(),
523
                self::callback(function (LifecycleEventArgs $args) use ($entity, $generatedFieldValue) {
524
                    /* @var $object EntityWithRandomlyGeneratedField */
525
                    $object = $args->getObject();
526
527
                    self::assertInstanceOf(EntityWithRandomlyGeneratedField::class, $object);
528
                    self::assertNotSame($entity, $object);
529
                    self::assertSame($generatedFieldValue, $object->generatedField);
530
531
                    return true;
532
                })
533
            );
534
535
        /* @var $object EntityWithRandomlyGeneratedField */
536
        $object = $this->_unitOfWork->merge($entity);
537
538
        self::assertNotSame($object, $entity);
539
        self::assertInstanceOf(EntityWithRandomlyGeneratedField::class, $object);
540
        self::assertSame($object->generatedField, $entity->generatedField);
541
    }
542
543
    /**
544
     * @group DDC-1955
545
     * @group 5570
546
     * @group 6174
547
     */
548
    public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners()
549
    {
550
        $persistedEntity = new EntityWithRandomlyGeneratedField();
551
        $mergedEntity    = new EntityWithRandomlyGeneratedField();
552
553
        $mergedEntity->id = $persistedEntity->id;
554
        $mergedEntity->generatedField = mt_rand(
555
            $persistedEntity->generatedField + 1,
556
            $persistedEntity->generatedField + 1000
557
        );
558
559
        $this
560
            ->eventManager
561
            ->expects(self::any())
562
            ->method('hasListeners')
563
            ->willReturnCallback(function ($eventName) {
564
                return $eventName === Events::prePersist;
565
            });
566
        $this->eventManager->expects(self::never())->method('dispatchEvent');
567
568
        $this->_unitOfWork->registerManaged(
569
            $persistedEntity,
570
            ['id' => $persistedEntity->id],
571
            ['generatedField' => $persistedEntity->generatedField]
572
        );
573
574
        /* @var $merged EntityWithRandomlyGeneratedField */
575
        $merged = $this->_unitOfWork->merge($mergedEntity);
576
577
        self::assertSame($merged, $persistedEntity);
578
        self::assertSame($persistedEntity->generatedField, $mergedEntity->generatedField);
579
    }
580
}
581
582
/**
583
 * @Entity
584
 */
585
class NotifyChangedEntity implements NotifyPropertyChanged
586
{
587
    private $_listeners = [];
588
    /**
589
     * @Id
590
     * @Column(type="integer")
591
     * @GeneratedValue
592
     */
593
    private $id;
594
    /**
595
     * @Column(type="string")
596
     */
597
    private $data;
598
599
    private $transient; // not persisted
600
601
    /** @OneToMany(targetEntity="NotifyChangedRelatedItem", mappedBy="owner") */
602
    private $items;
603
604
    public function  __construct() {
605
        $this->items = new ArrayCollection;
606
    }
607
608
    public function getId() {
609
        return $this->id;
610
    }
611
612
    public function getItems() {
613
        return $this->items;
614
    }
615
616
    public function setTransient($value) {
617
        if ($value != $this->transient) {
618
            $this->_onPropertyChanged('transient', $this->transient, $value);
619
            $this->transient = $value;
620
        }
621
    }
622
623
    public function getData() {
624
        return $this->data;
625
    }
626
627
    public function setData($data) {
628
        if ($data != $this->data) {
629
            $this->_onPropertyChanged('data', $this->data, $data);
630
            $this->data = $data;
631
        }
632
    }
633
634
    public function addPropertyChangedListener(PropertyChangedListener $listener)
635
    {
636
        $this->_listeners[] = $listener;
637
    }
638
639
    protected function _onPropertyChanged($propName, $oldValue, $newValue) {
640
        if ($this->_listeners) {
641
            foreach ($this->_listeners as $listener) {
642
                $listener->propertyChanged($this, $propName, $oldValue, $newValue);
643
            }
644
        }
645
    }
646
}
647
648
/** @Entity */
649
class NotifyChangedRelatedItem
650
{
651
    /**
652
     * @Id
653
     * @Column(type="integer")
654
     * @GeneratedValue
655
     */
656
    private $id;
657
658
    /** @ManyToOne(targetEntity="NotifyChangedEntity", inversedBy="items") */
659
    private $owner;
660
661
    public function getId() {
662
        return $this->id;
663
    }
664
665
    public function getOwner() {
666
        return $this->owner;
667
    }
668
669
    public function setOwner($owner) {
670
        $this->owner = $owner;
671
    }
672
}
673
674
/** @Entity */
675
class VersionedAssignedIdentifierEntity
676
{
677
    /**
678
     * @Id @Column(type="integer")
679
     */
680
    public $id;
681
    /**
682
     * @Version @Column(type="integer")
683
     */
684
    public $version;
685
}
686
687
/** @Entity */
688
class EntityWithStringIdentifier
689
{
690
    /**
691
     * @Id @Column(type="string")
692
     *
693
     * @var string|null
694
     */
695
    public $id;
696
}
697
698
/** @Entity */
699
class EntityWithBooleanIdentifier
700
{
701
    /**
702
     * @Id @Column(type="boolean")
703
     *
704
     * @var bool|null
705
     */
706
    public $id;
707
}
708
709
/** @Entity */
710
class EntityWithCompositeStringIdentifier
711
{
712
    /**
713
     * @Id @Column(type="string")
714
     *
715
     * @var string|null
716
     */
717
    public $id1;
718
719
    /**
720
     * @Id @Column(type="string")
721
     *
722
     * @var string|null
723
     */
724
    public $id2;
725
}
726
727
/** @Entity */
728
class EntityWithRandomlyGeneratedField
729
{
730
    /** @Id @Column(type="string") */
731
    public $id;
732
733
    /**
734
     * @Column(type="integer")
735
     */
736
    public $generatedField;
737
738
    public function __construct()
739
    {
740
        $this->id             = uniqid('id', true);
741
        $this->generatedField = mt_rand(0, 100000);
742
    }
743
}
744