Skip to content

Commit 6fa3031

Browse files
committed
Add SnapshotManager
1 parent 7ea32fd commit 6fa3031

File tree

4 files changed

+305
-1
lines changed

4 files changed

+305
-1
lines changed

README.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Doctrine ORM Refetch
22

3-
This library allows to re-fetch Doctrine ORM objects after clear the object manager.
3+
This library allows to
4+
* re-fetch Doctrine ORM objects after clear the object manager
5+
* detach all entities attached since a snapshot
46

57
![Tests](https://github.com/e-commit/doctrine-orm-refetch/workflows/Tests/badge.svg)
68

@@ -113,6 +115,47 @@ $entityManager->flush();
113115
$entityManager->clear();
114116
```
115117

118+
### Snapshot ###
119+
120+
Detach all entities attached since a snapshot (entities attached before the snapshot are kept)
121+
122+
```php
123+
use Ecommit\DoctrineOrmRefetch\SnapshotManager;
124+
125+
$snapshotManager = SnapshotManager::create($entityManager);
126+
127+
$author = $entityManager->getRepository(Author::class)->find(1);
128+
129+
$snapshotManager->snapshot();
130+
131+
$queryBuilder = $entityManager->getRepository(Book::class)->createQueryBuilder('b');
132+
$queryBuilder->select('b')
133+
->andWhere('b.bookId != :bookId')
134+
->setParameter('bookId', 7);
135+
$iterableResult = $queryBuilder->getQuery()->iterate();
136+
137+
$i = 0;
138+
foreach ($iterableResult as $row) {
139+
++$i;
140+
/** @var Book $book */
141+
$book = current($row);
142+
143+
if (!$book->getAuthors()->contains($author)) {
144+
$book->addAuthor($author);
145+
}
146+
147+
if (0 === $i % 2) {
148+
// $author and $book are managed
149+
$entityManager->flush();
150+
$snapshotManager->clear(); // Detach all entities attached since the snapshot
151+
// Only $author is managed
152+
}
153+
}
154+
155+
$entityManager->flush();
156+
$snapshotManager->clear();
157+
```
158+
116159

117160
## License ##
118161

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the doctrine-orm-refetch package.
7+
*
8+
* (c) E-commit <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ecommit\DoctrineOrmRefetch\Exception;
15+
16+
class SnapshotNotDoneException extends \Exception implements ExceptionInterface
17+
{
18+
}

src/SnapshotManager.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the doctrine-orm-refetch package.
7+
*
8+
* (c) E-commit <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ecommit\DoctrineOrmRefetch;
15+
16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Ecommit\DoctrineOrmRefetch\Exception\SnapshotNotDoneException;
18+
19+
class SnapshotManager
20+
{
21+
/**
22+
* @var EntityManagerInterface
23+
*/
24+
private $entityManager;
25+
26+
private $snapshot;
27+
28+
public static function create(EntityManagerInterface $entityManager): self
29+
{
30+
return new self($entityManager);
31+
}
32+
33+
public function __construct(EntityManagerInterface $entityManager)
34+
{
35+
$this->entityManager = $entityManager;
36+
}
37+
38+
public function getEntityManager(): EntityManagerInterface
39+
{
40+
return $this->entityManager;
41+
}
42+
43+
public function snapshot(): void
44+
{
45+
$this->snapshot = $this->entityManager->getUnitOfWork()->getIdentityMap();
46+
}
47+
48+
public function clear(): void
49+
{
50+
if (null === $this->snapshot) {
51+
throw new SnapshotNotDoneException('The snapshot was not done');
52+
}
53+
54+
$identityMap = $this->entityManager->getUnitOfWork()->getIdentityMap();
55+
foreach ($identityMap as $class => $objects) {
56+
foreach ($objects as $id => $object) {
57+
if (!\array_key_exists($class, $this->snapshot) || !\array_key_exists($id, $this->snapshot[$class])) {
58+
$this->entityManager->detach($object);
59+
}
60+
}
61+
}
62+
}
63+
}

tests/SnapshotManagerTest.php

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the doctrine-orm-refetch package.
7+
*
8+
* (c) E-commit <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ecommit\DoctrineOrmRefetch\Tests;
15+
16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Doctrine\ORM\QueryBuilder;
18+
use Ecommit\DoctrineOrmRefetch\Exception\SnapshotNotDoneException;
19+
use Ecommit\DoctrineOrmRefetch\SnapshotManager;
20+
use Ecommit\DoctrineOrmRefetch\Tests\App\Doctrine;
21+
use Ecommit\DoctrineOrmRefetch\Tests\App\Entity\Author;
22+
use Ecommit\DoctrineOrmRefetch\Tests\App\Entity\Book;
23+
24+
class SnapshotManagerTest extends AbstractTest
25+
{
26+
/**
27+
* @var EntityManagerInterface
28+
*/
29+
protected $em;
30+
31+
/**
32+
* @var SnapshotManager
33+
*/
34+
protected $snapshotManager;
35+
36+
protected function setUp(): void
37+
{
38+
$this->em = Doctrine::getEntityManager();
39+
$this->em->getConnection()->beginTransaction();
40+
$this->snapshotManager = SnapshotManager::create($this->em);
41+
}
42+
43+
protected function tearDown(): void
44+
{
45+
$this->em->getConnection()->rollBack();
46+
$this->em->clear();
47+
}
48+
49+
public function testCreate(): void
50+
{
51+
$refetchManager = SnapshotManager::create($this->em);
52+
$this->assertEquals(SnapshotManager::class, \get_class($refetchManager));
53+
}
54+
55+
public function testGetEntityManager(): void
56+
{
57+
$this->assertSame($this->em, $this->snapshotManager->getEntityManager());
58+
}
59+
60+
public function testOneObjectWithoutClear(): void
61+
{
62+
/** @var Book $book */
63+
$book = $this->em->getRepository(Book::class)->find(6);
64+
$this->assertNotNull($book);
65+
66+
$this->snapshotManager->snapshot();
67+
68+
$this->checkUnitOfWork(2, [$book, $book->getCategory()]);
69+
$this->assertEquals(6, $book->getBookId());
70+
}
71+
72+
public function testOneObjectWithClearOutsideSnapshot(): void
73+
{
74+
$this->snapshotManager->snapshot();
75+
76+
/** @var Book $book */
77+
$book = $this->em->getRepository(Book::class)->find(6);
78+
$this->assertNotNull($book);
79+
80+
$this->snapshotManager->clear();
81+
82+
$this->checkUnitOfWork(0, []);
83+
}
84+
85+
public function testOneObjectWithClearInsideSnapshot(): void
86+
{
87+
/** @var Book $book */
88+
$book = $this->em->getRepository(Book::class)->find(6);
89+
$this->assertNotNull($book);
90+
91+
$this->snapshotManager->snapshot();
92+
$this->snapshotManager->clear();
93+
94+
$this->checkUnitOfWork(2, [$book, $book->getCategory()]);
95+
$this->assertEquals(6, $book->getBookId());
96+
}
97+
98+
public function testOneObjectWithClearAndCollection(): void
99+
{
100+
/** @var Book $book */
101+
$book = $this->em->getRepository(Book::class)->find(9);
102+
$this->assertNotNull($book);
103+
104+
$this->snapshotManager->snapshot();
105+
106+
$authors = $book->getAuthors()->getIterator();
107+
$this->checkUnitOfWork(4, [$book, $book->getCategory()]);
108+
109+
$this->snapshotManager->clear();
110+
111+
$this->checkUnitOfWork(2, [$book, $book->getCategory()]);
112+
}
113+
114+
public function testInIterate(): void
115+
{
116+
$entityManager = $this->em;
117+
118+
// Example in README.md - Beginning
119+
120+
$snapshotManager = SnapshotManager::create($entityManager);
121+
122+
$author = $entityManager->getRepository(Author::class)->find(1);
123+
$this->assertNotNull($author); // Remove this line in Example
124+
125+
$snapshotManager->snapshot();
126+
127+
$queryBuilder = $entityManager->getRepository(Book::class)->createQueryBuilder('b');
128+
$queryBuilder->select('b')
129+
->andWhere('b.bookId != :bookId')
130+
->setParameter('bookId', 7);
131+
$iterableResult = $queryBuilder->getQuery()->iterate();
132+
133+
$i = 0;
134+
foreach ($iterableResult as $row) {
135+
++$i;
136+
/** @var Book $book */
137+
$book = current($row);
138+
139+
if (!$book->getAuthors()->contains($author)) {
140+
$book->addAuthor($author);
141+
}
142+
143+
if (0 === $i % 2) {
144+
// $author and $book are managed
145+
$entityManager->flush();
146+
$snapshotManager->clear(); // Detach all entities attached since the snapshot
147+
// Only $author is managed
148+
149+
$this->checkUnitOfWork(1, [$author]); // Remove this line in Example
150+
}
151+
}
152+
153+
$entityManager->flush();
154+
$snapshotManager->clear();
155+
156+
// Example in README.md - End
157+
158+
/** @var QueryBuilder $queryBuilder */
159+
$queryBuilder = $this->em->getRepository(Book::class)->createQueryBuilder('b');
160+
$queryBuilder->select('count(b) as nbre')
161+
->leftJoin('b.authors', 'a')
162+
->andWhere('a.authorId = :authorId')
163+
->setParameter('authorId', 1);
164+
$count = $queryBuilder->getQuery()->getSingleScalarResult();
165+
166+
$this->assertEquals(9, $count);
167+
}
168+
169+
public function testSnapshotNotDone(): void
170+
{
171+
/** @var Book $book */
172+
$book = $this->em->getRepository(Book::class)->find(6);
173+
$this->assertNotNull($book);
174+
175+
$this->expectException(SnapshotNotDoneException::class);
176+
$this->expectDeprecationMessage('The snapshot was not done');
177+
178+
$this->snapshotManager->clear();
179+
}
180+
}

0 commit comments

Comments
 (0)