Skip to content

Commit

Permalink
Merge pull request #2521 from notrix/fix-proxy-state-on-init-exception
Browse files Browse the repository at this point in the history
Restore document proxy state to uninitialized on load exception
  • Loading branch information
malarzm authored Apr 13, 2023
2 parents 856de18 + 9bbe84a commit bce7e33
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 1 deletion.
11 changes: 10 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use ProxyManager\Factory\LazyLoadingGhostFactory;
use ProxyManager\Proxy\GhostObjectInterface;
use ReflectionProperty;
use Throwable;

use function array_filter;
use function count;
Expand Down Expand Up @@ -108,7 +109,15 @@ private function createInitializer(
$initializer = null;
$identifier = $metadata->getIdentifierValue($ghostObject);

if (! $documentPersister->load(['_id' => $identifier], $ghostObject)) {
try {
$document = $documentPersister->load(['_id' => $identifier], $ghostObject);
} catch (Throwable $exception) {
$initializer = $originalInitializer;

throw $exception;
}

if (! $document) {
$initializer = $originalInitializer;

if (! $this->lifecycleEventManager->documentNotFound($ghostObject, $identifier)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Tests\Proxy\Factory;

use Closure;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Event\DocumentNotFoundEventArgs;
use Doctrine\ODM\MongoDB\Events;
use Doctrine\ODM\MongoDB\LockException;
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
use Documents\Cart;
use MongoDB\Client;
use MongoDB\Collection;
use MongoDB\Database;
use PHPUnit\Framework\MockObject\MockObject;
use ProxyManager\Proxy\GhostObjectInterface;

class StaticProxyFactoryTest extends BaseTestCase
{
/** @var Client|MockObject */
private Client $client;

public function setUp(): void
{
parent::setUp();

$this->dm = $this->createMockedDocumentManager();
}

public function testProxyInitializeWithException(): void
{
$collection = $this->createMock(Collection::class);
$database = $this->createMock(Database::class);

$this->client->expects($this->once())
->method('selectDatabase')
->willReturn($database);

$database->expects($this->once())
->method('selectCollection')
->willReturn($collection);

$collection->expects($this->once())
->method('findOne')
->willThrowException(LockException::lockFailed(null));

$uow = $this->dm->getUnitOfWork();

$proxy = $this->dm->getReference(Cart::class, '123');
self::assertInstanceOf(GhostObjectInterface::class, $proxy);

$closure = static function (DocumentNotFoundEventArgs $eventArgs) {
self::fail('DocumentNotFoundListener should not be called');
};
$this->dm->getEventManager()->addEventListener(Events::documentNotFound, new DocumentNotFoundListener($closure));

try {
$proxy->initializeProxy();
self::fail('An exception should have been thrown');
} catch (LockException $exception) {
self::assertInstanceOf(LockException::class, $exception);
}

$uow->computeChangeSets();

self::assertFalse($proxy->isProxyInitialized(), 'Proxy should not be initialized');
}

public function tearDown(): void
{
// db connection is mocked, nothing to clean up
}

private function createMockedDocumentManager(): DocumentManager
{
$config = $this->getConfiguration();

$this->client = $this->createMock(Client::class);

return DocumentManager::create($this->client, $config);
}
}

class DocumentNotFoundListener
{
private Closure $closure;

public function __construct(Closure $closure)
{
$this->closure = $closure;
}

public function documentNotFound(DocumentNotFoundEventArgs $eventArgs): void
{
$closure = $this->closure;
$closure($eventArgs);
}
}

0 comments on commit bce7e33

Please sign in to comment.