From 4a2690791ea9753993c7f362da43e14e4dd408d6 Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Tue, 2 Apr 2024 21:13:11 +0200 Subject: [PATCH] ease multiple Orm extension instances initialization with connection reference [closes #658] --- docs/config-nette.md | 23 +++++- src/Bridges/NetteDI/OrmExtension.php | 35 +++++---- .../NetteDI/PhpDocRepositoryFinder.php | 11 ++- .../BridgeNetteDI/dic-extension-multiple.neon | 24 ++++++ .../BridgeNetteDI/dic-extension-multiple.phpt | 76 +++++++++++++++++++ 5 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 tests/cases/integration/BridgeNetteDI/dic-extension-multiple.neon create mode 100644 tests/cases/integration/BridgeNetteDI/dic-extension-multiple.phpt diff --git a/docs/config-nette.md b/docs/config-nette.md index e5b495ab..02ade780 100644 --- a/docs/config-nette.md +++ b/docs/config-nette.md @@ -4,7 +4,7 @@ Orm comes with `OrmExtension` that will help you integrate all needed services w #### PhpDoc Repository Definition -The most common use-case is to define repositories as model class PhpDoc annotations. Orm extension will take care of your repositories and automatically creates their definition for DI container. Also, a lazy loader will be injected into the model. The loader will provide repositories directly from your DI container. +The most common use-case is to define repositories as model class PhpDoc annotations. Orm extension will take care of your repositories and automatically create their definition for DI container. Also, a lazy loader will be injected into the model. The loader will provide repositories directly from your DI container. To define model repository use PhpDoc `@property-read` annotation: @@ -82,7 +82,7 @@ Repositories are registered also with their names that are generated from the re #### Customizations -By default, Orm classes utilize a Cache service. You may redefine your own: +By default, Orm classes use a Cache service. You may redefine your own: ```neon services: @@ -104,3 +104,22 @@ Orm allows injecting dependencies into your entities. This is dependency provide services: nextras.orm.dependencyProvider: MyApp\DependencyProvider ``` + +Orm setups all internal services as autowired. This may be toggled by `autowiredInternalServices` option. This may be useful, especially when the Orm extension is used multiple times. The `connection` option allows specifying the related connection instance. + +```neon +extensions: + nextras.orm1: Nextras\Orm\Bridges\NetteDI\OrmExtension + nextras.orm2: Nextras\Orm\Bridges\NetteDI\OrmExtension + nextras.dbal1: Nextras\Dbal\Bridges\NetteDI\DbalExtension + nextras.dbal2: Nextras\Dbal\Bridges\NetteDI\DbalExtension + +nextras.orm1: + model: MainModel + connection: @nextras.dbal1.connection + +nextras.orm2: + model: AnotherModel + connection: @nextras.dbal2.connection + autowiredInternalServices: false +``` diff --git a/src/Bridges/NetteDI/OrmExtension.php b/src/Bridges/NetteDI/OrmExtension.php index a758ea43..aff19d80 100644 --- a/src/Bridges/NetteDI/OrmExtension.php +++ b/src/Bridges/NetteDI/OrmExtension.php @@ -14,6 +14,7 @@ use Nextras\Orm\Entity\Reflection\MetadataParser; use Nextras\Orm\Exception\InvalidStateException; use Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator; +use Nextras\Orm\Model\IModel; use Nextras\Orm\Model\MetadataStorage; use Nextras\Orm\Model\Model; use Nextras\Orm\Repository\IRepository; @@ -26,14 +27,12 @@ */ class OrmExtension extends CompilerExtension { - /** @var ContainerBuilder */ - protected $builder; + protected ContainerBuilder $builder; - /** @var IRepositoryFinder */ - protected $repositoryFinder; + protected IRepositoryFinder $repositoryFinder; - /** @var string */ - protected $modelClass; + /** @var class-string */ + protected string $modelClass; public function getConfigSchema(): Schema @@ -42,6 +41,8 @@ public function getConfigSchema(): Schema 'model' => Expect::string()->default(Model::class), 'repositoryFinder' => Expect::string()->default(PhpDocRepositoryFinder::class), 'initializeMetadata' => Expect::bool()->default(false), + 'autowiredInternalServices' => Expect::bool()->default(true), + 'connection' => Expect::string(), ]); } @@ -49,7 +50,6 @@ public function getConfigSchema(): Schema public function loadConfiguration(): void { $this->builder = $this->getContainerBuilder(); - $this->modelClass = $this->config->model; $repositoryFinderClass = $this->config->repositoryFinder; @@ -113,7 +113,8 @@ protected function setupDependencyProvider(): void } $this->builder->addDefinition($providerName) - ->setType(DependencyProvider::class); + ->setType(DependencyProvider::class) + ->setAutowired($this->config->autowiredInternalServices); } @@ -124,10 +125,16 @@ protected function setupDbalMapperDependencies(): void } $name = $this->prefix('mapperCoordinator'); - if (!$this->builder->hasDefinition($name)) { - $this->builder->addDefinition($name) - ->setType(DbalMapperCoordinator::class); + if ($this->builder->hasDefinition($name)) { + return; } + + $this->builder->addDefinition($name) + ->setType(DbalMapperCoordinator::class) + ->setArguments( + $this->config->connection !== null ? ['connection' => $this->config->connection] : [], + ) + ->setAutowired($this->config->autowiredInternalServices); } @@ -142,7 +149,8 @@ protected function setupMetadataParserFactory(): void ->setImplement(IMetadataParserFactory::class) ->getResultDefinition() ->setType(MetadataParser::class) - ->setArguments(['$entityClassesMap']); + ->setArguments(['$entityClassesMap']) + ->setAutowired($this->config->autowiredInternalServices); } @@ -163,7 +171,8 @@ protected function setupMetadataStorage(array $entityClassMap): void 'cache' => $this->prefix('@cache'), 'metadataParserFactory' => $this->prefix('@metadataParserFactory'), 'repositoryLoader' => $this->prefix('@repositoryLoader'), - ]); + ]) + ->setAutowired($this->config->autowiredInternalServices); } diff --git a/src/Bridges/NetteDI/PhpDocRepositoryFinder.php b/src/Bridges/NetteDI/PhpDocRepositoryFinder.php index c52e405b..e18fdffc 100644 --- a/src/Bridges/NetteDI/PhpDocRepositoryFinder.php +++ b/src/Bridges/NetteDI/PhpDocRepositoryFinder.php @@ -113,11 +113,20 @@ protected function setupMapperService(string $repositoryName, string $repository throw new InvalidStateException("Unknown mapper for '{$repositoryName}' repository."); } + /** @var \stdClass $config */ + $config = $this->extension->getConfig(); + if ($config->connection !== null) { + $connection = ['connection' => $config->connection]; + } else { + $connection = []; + } + $this->builder->addDefinition($mapperName) ->setType($mapperClass) ->setArguments([ 'cache' => $this->extension->prefix('@cache'), - ]); + 'mapperCoordinator' => $this->extension->prefix('@mapperCoordinator'), + ] + $connection); } diff --git a/tests/cases/integration/BridgeNetteDI/dic-extension-multiple.neon b/tests/cases/integration/BridgeNetteDI/dic-extension-multiple.neon new file mode 100644 index 00000000..3b53711c --- /dev/null +++ b/tests/cases/integration/BridgeNetteDI/dic-extension-multiple.neon @@ -0,0 +1,24 @@ +extensions: + nextras.orm1: Nextras\Orm\Bridges\NetteDI\OrmExtension + nextras.orm2: Nextras\Orm\Bridges\NetteDI\OrmExtension + nextras.dbal1: Nextras\Dbal\Bridges\NetteDI\DbalExtension + nextras.dbal2: Nextras\Dbal\Bridges\NetteDI\DbalExtension + nette.cache: Nette\Bridges\CacheDI\CacheExtension(%tempDir%) + +nextras.orm1: + model: NextrasTests\Orm\Model + connection: @nextras.dbal1.connection + +nextras.orm2: + model: NextrasTests\Orm\Integration\BridgeNetteDI\Model2 + connection: @nextras.dbal2.connection + autowiredInternalServices: false + +nextras.dbal1: + driver: mysqli + +nextras.dbal2: + driver: pgsql + +services: + - Nette\Caching\Cache diff --git a/tests/cases/integration/BridgeNetteDI/dic-extension-multiple.phpt b/tests/cases/integration/BridgeNetteDI/dic-extension-multiple.phpt new file mode 100644 index 00000000..9d661e60 --- /dev/null +++ b/tests/cases/integration/BridgeNetteDI/dic-extension-multiple.phpt @@ -0,0 +1,76 @@ +load(function (Compiler $compiler) use ($config, $cacheDir): void { + $compiler->addExtension('extensions', new ExtensionsExtension()); + $compiler->addConfig(['parameters' => ['tempDir' => $cacheDir]]); + $compiler->loadConfig($config); + }, $key); + + /** @var Container $dic */ + $dic = new $className(); + return $dic; +} + +/** + * @property int $id {primary} + */ +class TestEntity extends Entity +{ +} + +/** + * @extends Repository + */ +class TestRepository extends Repository +{ + public static function getEntityClassNames(): array + { + return [TestEntity::class]; + } + +} + +/** + * @extends DbalMapper + */ +class TestMapper extends DbalMapper +{ +} + +/** + * @property-read TestRepository $test + */ +class Model2 extends Model +{ +} + +$container = buildDic(__DIR__ . '/dic-extension-multiple.neon'); +Assert::count(2, $container->findByType(IModel::class)); +Assert::count(2, $container->findByType(DbalMapperCoordinator::class)); +// check that returns only one instance +Assert::type(DbalMapperCoordinator::class, $container->getByType(DbalMapperCoordinator::class)); +Assert::type(BooksRepository::class, $container->getByType(BooksRepository::class)); +Assert::type(TestRepository::class, $container->getByType(TestRepository::class));