3 namespace Doctrine\Tests\ORM;
5 use Doctrine\ORM\UnitOfWork;
6 use Doctrine\Tests\Mocks\ConnectionMock;
7 use Doctrine\Tests\Mocks\EntityManagerMock;
8 use Doctrine\Tests\Mocks\UnitOfWorkMock;
9 use Doctrine\Tests\Mocks\EntityPersisterMock;
10 use Doctrine\Tests\Mocks\IdentityIdGeneratorMock;
11 use Doctrine\Tests\Models\Forum\ForumUser;
12 use Doctrine\Tests\Models\Forum\ForumAvatar;
14 require_once __DIR__ . '/../TestInit.php';
19 class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
23 // Provides a sequence mock to the UnitOfWork
24 private $_connectionMock;
25 // The EntityManager mock that provides the mock persisters
28 protected function setUp() {
30 $this->_connectionMock = new ConnectionMock(array(), new \Doctrine\Tests\Mocks\DriverMock());
31 $this->_emMock = EntityManagerMock::create($this->_connectionMock);
33 $this->_unitOfWork = new UnitOfWorkMock($this->_emMock);
34 $this->_emMock->setUnitOfWork($this->_unitOfWork);
37 protected function tearDown() {
40 public function testRegisterRemovedOnNewEntityIsIgnored()
42 $user = new ForumUser();
43 $user->username = 'romanb';
44 $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user));
45 $this->_unitOfWork->scheduleForDelete($user);
46 $this->assertFalse($this->_unitOfWork->isScheduledForDelete($user));
50 /* Operational tests */
52 public function testSavingSingleEntityWithIdentityColumnForcesInsert()
54 // Setup fake persister and id generator for identity generation
55 $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser"));
56 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister);
57 //$idGeneratorMock = new IdentityIdGeneratorMock($this->_emMock);
58 //$this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumUser', $idGeneratorMock);
59 $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY);
62 $user = new ForumUser();
63 $user->username = 'romanb';
64 $this->_unitOfWork->persist($user);
67 $this->assertEquals(0, count($userPersister->getInserts()));
68 $this->assertEquals(0, count($userPersister->getUpdates()));
69 $this->assertEquals(0, count($userPersister->getDeletes()));
70 $this->assertFalse($this->_unitOfWork->isInIdentityMap($user));
71 // should no longer be scheduled for insert
72 $this->assertTrue($this->_unitOfWork->isScheduledForInsert($user));
74 // Now lets check whether a subsequent commit() does anything
75 $userPersister->reset();
78 $this->_unitOfWork->commit();
81 $this->assertEquals(1, count($userPersister->getInserts()));
82 $this->assertEquals(0, count($userPersister->getUpdates()));
83 $this->assertEquals(0, count($userPersister->getDeletes()));
86 $this->assertTrue(is_numeric($user->id));
90 * Tests a scenario where a save() operation is cascaded from a ForumUser
91 * to its associated ForumAvatar, both entities using IDENTITY id generation.
93 public function testCascadedIdentityColumnInsert()
95 // Setup fake persister and id generator for identity generation
97 $userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumUser"));
98 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumUser', $userPersister);
99 //$userIdGeneratorMock = new IdentityIdGeneratorMock($this->_emMock);
100 //$this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumUser', $userIdGeneratorMock);
101 $userPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY);
103 $avatarPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\Forum\ForumAvatar"));
104 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarPersister);
105 //$avatarIdGeneratorMock = new IdentityIdGeneratorMock($this->_emMock);
106 //$this->_emMock->setIdGenerator('Doctrine\Tests\Models\Forum\ForumAvatar', $avatarIdGeneratorMock);
107 $avatarPersister->setMockIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY);
110 $user = new ForumUser();
111 $user->username = 'romanb';
112 $avatar = new ForumAvatar();
113 $user->avatar = $avatar;
114 $this->_unitOfWork->persist($user); // save cascaded to avatar
116 $this->_unitOfWork->commit();
118 $this->assertTrue(is_numeric($user->id));
119 $this->assertTrue(is_numeric($avatar->id));
121 $this->assertEquals(1, count($userPersister->getInserts()));
122 $this->assertEquals(0, count($userPersister->getUpdates()));
123 $this->assertEquals(0, count($userPersister->getDeletes()));
125 $this->assertEquals(1, count($avatarPersister->getInserts()));
126 $this->assertEquals(0, count($avatarPersister->getUpdates()));
127 $this->assertEquals(0, count($avatarPersister->getDeletes()));
130 public function testChangeTrackingNotify()
132 $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\NotifyChangedEntity"));
133 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedEntity', $persister);
134 $itemPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\NotifyChangedRelatedItem"));
135 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedRelatedItem', $itemPersister);
137 $entity = new NotifyChangedEntity;
138 $entity->setData('thedata');
139 $this->_unitOfWork->persist($entity);
141 $this->_unitOfWork->commit();
142 $this->assertEquals(1, count($persister->getInserts()));
145 $this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
147 $entity->setData('newdata');
148 $entity->setTransient('newtransientvalue');
150 $this->assertTrue($this->_unitOfWork->isScheduledForDirtyCheck($entity));
152 $this->assertEquals(array('data' => array('thedata', 'newdata')), $this->_unitOfWork->getEntityChangeSet($entity));
154 $item = new NotifyChangedRelatedItem();
155 $entity->getItems()->add($item);
156 $item->setOwner($entity);
157 $this->_unitOfWork->persist($item);
159 $this->_unitOfWork->commit();
160 $this->assertEquals(1, count($itemPersister->getInserts()));
162 $itemPersister->reset();
165 $entity->getItems()->removeElement($item);
166 $item->setOwner(null);
167 $this->assertTrue($entity->getItems()->isDirty());
168 $this->_unitOfWork->commit();
169 $updates = $itemPersister->getUpdates();
170 $this->assertEquals(1, count($updates));
171 $this->assertTrue($updates[0] === $item);
174 public function testGetEntityStateOnVersionedEntityWithAssignedIdentifier()
176 $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity"));
177 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\VersionedAssignedIdentifierEntity', $persister);
179 $e = new VersionedAssignedIdentifierEntity();
181 $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($e));
182 $this->assertFalse($persister->isExistsCalled());
185 public function testGetEntityStateWithAssignedIdentity()
187 $persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\Models\CMS\CmsPhonenumber"));
188 $this->_unitOfWork->setEntityPersister('Doctrine\Tests\Models\CMS\CmsPhonenumber', $persister);
190 $ph = new \Doctrine\Tests\Models\CMS\CmsPhonenumber();
191 $ph->phonenumber = '12345';
193 $this->assertEquals(UnitOfWork::STATE_NEW, $this->_unitOfWork->getEntityState($ph));
194 $this->assertTrue($persister->isExistsCalled());
198 // if the entity is already managed the exists() check should be skipped
199 $this->_unitOfWork->registerManaged($ph, array('phonenumber' => '12345'), array());
200 $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($ph));
201 $this->assertFalse($persister->isExistsCalled());
202 $ph2 = new \Doctrine\Tests\Models\CMS\CmsPhonenumber();
203 $ph2->phonenumber = '12345';
204 $this->assertEquals(UnitOfWork::STATE_DETACHED, $this->_unitOfWork->getEntityState($ph2));
205 $this->assertFalse($persister->isExistsCalled());
212 class NotifyChangedEntity implements \Doctrine\Common\NotifyPropertyChanged
214 private $_listeners = array();
217 * @Column(type="integer")
222 * @Column(type="string")
226 private $transient; // not persisted
228 /** @OneToMany(targetEntity="NotifyChangedRelatedItem", mappedBy="owner") */
231 public function __construct() {
232 $this->items = new \Doctrine\Common\Collections\ArrayCollection;
235 public function getId() {
239 public function getItems() {
243 public function setTransient($value) {
244 if ($value != $this->transient) {
245 $this->_onPropertyChanged('transient', $this->transient, $value);
246 $this->transient = $value;
250 public function getData() {
254 public function setData($data) {
255 if ($data != $this->data) {
256 $this->_onPropertyChanged('data', $this->data, $data);
261 public function addPropertyChangedListener(\Doctrine\Common\PropertyChangedListener $listener)
263 $this->_listeners[] = $listener;
266 protected function _onPropertyChanged($propName, $oldValue, $newValue) {
267 if ($this->_listeners) {
268 foreach ($this->_listeners as $listener) {
269 $listener->propertyChanged($this, $propName, $oldValue, $newValue);
276 class NotifyChangedRelatedItem
280 * @Column(type="integer")
285 /** @ManyToOne(targetEntity="NotifyChangedEntity", inversedBy="items") */
288 public function getId() {
292 public function getOwner() {
296 public function setOwner($owner) {
297 $this->owner = $owner;
302 class VersionedAssignedIdentifierEntity
305 * @Id @Column(type="integer")
309 * @Version @Column(type="integer")