Skip to content

Commit

Permalink
ease multiple Orm extension instances initialization with connection …
Browse files Browse the repository at this point in the history
…reference

[closes #658]
  • Loading branch information
hrach committed Apr 2, 2024
1 parent 59846da commit 8cdb2dd
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 16 deletions.
23 changes: 21 additions & 2 deletions docs/config-nette.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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:
Expand All @@ -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
```
35 changes: 22 additions & 13 deletions src/Bridges/NetteDI/OrmExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<IModel> */
protected string $modelClass;


public function getConfigSchema(): Schema
Expand All @@ -42,14 +41,15 @@ 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(),
]);
}


public function loadConfiguration(): void
{
$this->builder = $this->getContainerBuilder();

$this->modelClass = $this->config->model;

$repositoryFinderClass = $this->config->repositoryFinder;
Expand Down Expand Up @@ -113,7 +113,8 @@ protected function setupDependencyProvider(): void
}

$this->builder->addDefinition($providerName)
->setType(DependencyProvider::class);
->setType(DependencyProvider::class)
->setAutowired($this->config->autowiredInternalServices);
}


Expand All @@ -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);
}


Expand All @@ -142,7 +149,8 @@ protected function setupMetadataParserFactory(): void
->setImplement(IMetadataParserFactory::class)
->getResultDefinition()
->setType(MetadataParser::class)
->setArguments(['$entityClassesMap']);
->setArguments(['$entityClassesMap'])
->setAutowired($this->config->autowiredInternalServices);
}


Expand All @@ -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);
}


Expand Down
9 changes: 8 additions & 1 deletion src/Bridges/NetteDI/PhpDocRepositoryFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,18 @@ protected function setupMapperService(string $repositoryName, string $repository
throw new InvalidStateException("Unknown mapper for '{$repositoryName}' repository.");
}

if ($this->extension->getConfig()->connection !== null) {

Check failure on line 116 in src/Bridges/NetteDI/PhpDocRepositoryFinder.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3)

Cannot access property $connection on array|object.
$connection = ['connection' => $this->extension->getConfig()->connection];

Check failure on line 117 in src/Bridges/NetteDI/PhpDocRepositoryFinder.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.3)

Cannot access property $connection on array|object.
} else {
$connection = [];
}

$this->builder->addDefinition($mapperName)
->setType($mapperClass)
->setArguments([
'cache' => $this->extension->prefix('@cache'),
]);
'mapperCoordinator' => $this->extension->prefix('@mapperCoordinator'),
] + $connection);
}


Expand Down
24 changes: 24 additions & 0 deletions tests/cases/integration/BridgeNetteDI/dic-extension-multiple.neon
Original file line number Diff line number Diff line change
@@ -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
76 changes: 76 additions & 0 deletions tests/cases/integration/BridgeNetteDI/dic-extension-multiple.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php declare(strict_types = 1);

namespace NextrasTests\Orm\Integration\BridgeNetteDI;


use Nette\DI\Compiler;
use Nette\DI\Container;
use Nette\DI\ContainerLoader;
use Nette\DI\Extensions\ExtensionsExtension;
use Nextras\Orm\Entity\Entity;use Nextras\Orm\Mapper\Dbal\DbalMapper;
use Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Model\Model;
use Nextras\Orm\Repository\Repository;
use NextrasTests\Orm\BooksRepository;
use Tester\Assert;


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

function buildDic(string $config): Container
{
$cacheDir = TEMP_DIR . '/cache/bridge-nette-dic-extension-multiple';
$loader = new ContainerLoader($cacheDir);
$key = __FILE__ . ':' . __LINE__ . ':' . $config;
$className = $loader->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<TestEntity>
*/
class TestRepository extends Repository
{
public static function getEntityClassNames(): array
{
return [TestEntity::class];
}

}

/**
* @extends DbalMapper<TestEntity>
*/
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));

0 comments on commit 8cdb2dd

Please sign in to comment.