Skip to content

Commit

Permalink
ManagerRegistry: support reseting entity manager [#104]
Browse files Browse the repository at this point in the history
  • Loading branch information
f3l1x committed Dec 13, 2024
1 parent ae5e762 commit 2941bca
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Integration of [Doctrine ORM](https://www.doctrine-project.org/projects/orm.html
- [Minimal configuration](#minimal-configuration)
- [Advanced configuration](#advanced-configuration)
- [Auto configuration](#auto-configuration)
- [EntityManager](#entitymanager)
- [Caching](#caching)
- [Mapping](#mapping)
- [Attributes](#attributes)
Expand Down Expand Up @@ -152,6 +153,37 @@ By default, this extension will try to autoconfigure itself.
- `3` means that the proxy classes are generated automatically using `eval()` (useful for debugging).
- `4` means that the proxy classes are generated automatically when the proxy file does not exist or when the proxied file changed.

### EntityManager

EntityManager is a central access point to ORM functionality. It is a wrapper around ObjectManager and holds the metadata and configuration of the ORM.

**EntityManagerDecorator**

You can use `entityManagerDecoratorClass` to decorate EntityManager.

```neon
nettrine.orm:
managers:
default:
connection: default
entityManagerDecoratorClass: App\MyEntityManagerDecorator
```

**Close & Reset**

If you hit `The EntityManager is closed.` exception, you can use `reset` method to reopen it.

```php
$managerRegistry = $container->getByType(Doctrine\Persistence\ManagerRegistry::class);
$managerRegistry->resetManager(); // default
$managerRegistry->resetManager('second');
```

> [!WARNING]
> Resetting the manager is a dangerous operation. It is also black magic, because you cannot just create a new EntityManager instance,
> you have to reset the current one using internal methods (reflection & binding).
> Class responsible for this operation is [`Nettrine\ORM\ManagerRegistry`](https://github.com/contributte/doctrine-orm/blob/master/src/ManagerRegistry.php).
### Caching

> [!TIP]
Expand Down Expand Up @@ -302,6 +334,7 @@ The XML mapping driver enables you to provide the ORM metadata in form of XML do
> - https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/xml-mapping.html
```xml

<doctrine-mapping
xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
Expand Down
9 changes: 9 additions & 0 deletions src/ManagerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Nettrine\ORM;

use Doctrine\ORM\EntityManager;
use Doctrine\Persistence\AbstractManagerRegistry;
use Doctrine\Persistence\Proxy;
use Nette\DI\Container;
use Nettrine\ORM\Utils\Binder;

class ManagerRegistry extends AbstractManagerRegistry
{
Expand Down Expand Up @@ -40,6 +42,13 @@ protected function getService(string $name): object

protected function resetService(string $name): void
{
$manager = $this->container->getService($name);

Binder::use($manager, function (): void {
/** @var EntityManager $this */
$this->closed = false; // @phpstan-ignore-line
});

$this->container->removeService($name);
}

Expand Down
18 changes: 18 additions & 0 deletions src/Utils/Binder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

namespace Nettrine\ORM\Utils;

use Closure;

final class Binder
{

/**
* @param object|class-string $objectOrClass
*/
public static function use(object|string $objectOrClass, Closure $closure): mixed
{
return $closure->bindTo(is_object($objectOrClass) ? $objectOrClass : null, $objectOrClass)();
}

}
72 changes: 72 additions & 0 deletions tests/Cases/ManagerRegistry.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Contributte\Tester\Toolkit;
use Contributte\Tester\Utils\ContainerBuilder;
use Contributte\Tester\Utils\Neonkit;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Nette\DI\Compiler;
use Nettrine\DBAL\DI\DbalExtension;
Expand All @@ -12,6 +13,7 @@ use Tests\Toolkit\Tests;

require_once __DIR__ . '/../bootstrap.php';

// Multiple managers
Toolkit::test(function (): void {
$container = ContainerBuilder::of()
->withCompiler(function (Compiler $compiler): void {
Expand Down Expand Up @@ -63,3 +65,73 @@ Toolkit::test(function (): void {

Assert::count(2, $registry->getManagers());
});

// Reset manager
Toolkit::test(function (): void {
$container = ContainerBuilder::of()
->withCompiler(function (Compiler $compiler): void {
$compiler->addExtension('nettrine.dbal', new DbalExtension());
$compiler->addExtension('nettrine.orm', new OrmExtension());
$compiler->addConfig([
'parameters' => [
'tempDir' => Tests::TEMP_PATH,
],
]);
$compiler->addConfig(Neonkit::load(
<<<'NEON'
nettrine.dbal:
connections:
default:
driver: pdo_sqlite
password: test
user: test
path: ":memory:"
second:
driver: pdo_sqlite
password: test
user: test
path: ":memory:"
nettrine.orm:
managers:
default:
connection: default
mapping:
App:
type: attributes
dirs: [app/Database]
namespace: App\Database
second:
connection: second
entityManagerDecoratorClass: Tests\Mocks\DummyEntityManagerDecorator
mapping:
App:
type: attributes
dirs: [app/Database]
namespace: App\Database
NEON
));
})
->build();

/** @var ManagerRegistry $registry */
$registry = $container->getByType(ManagerRegistry::class);

foreach (['default', 'second'] as $managerName) {
/** @var EntityManagerInterface $em1 */
$em1 = $registry->getManager($managerName);

Assert::true($em1->isOpen());
$em1->close();
Assert::false($em1->isOpen());

// Reset manager
$registry->resetManager($managerName);

/** @var EntityManagerInterface $em2 */
$em2 = $registry->getManager();
Assert::notSame($em1, $em2);

Assert::true($em1->isOpen());
Assert::true($em2->isOpen());
}
});

0 comments on commit 2941bca

Please sign in to comment.