-
-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/Bridges/SymfonyBundle/DependencyInjection/Configuration.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Nextras\Orm\Bridges\SymfonyBundle\DependencyInjection; | ||
|
||
|
||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; | ||
use Symfony\Component\Config\Definition\Builder\TreeBuilder; | ||
use Symfony\Component\Config\Definition\ConfigurationInterface; | ||
|
||
|
||
class Configuration implements ConfigurationInterface | ||
{ | ||
public function getConfigTreeBuilder() | ||
{ | ||
$treeBuilder = new TreeBuilder('nextras_orm'); | ||
|
||
$root = $treeBuilder->getRootNode(); | ||
assert($root instanceof ArrayNodeDefinition); | ||
|
||
// @formatter:off | ||
$root | ||
->children() | ||
->scalarNode('model') | ||
->isRequired() | ||
->end(); | ||
// @formatter:on | ||
|
||
return $treeBuilder; | ||
} | ||
} |
241 changes: 241 additions & 0 deletions
241
src/Bridges/SymfonyBundle/DependencyInjection/NextrasOrmExtension.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Nextras\Orm\Bridges\SymfonyBundle\DependencyInjection; | ||
|
||
|
||
use Nette\Caching\Cache; | ||
use Nette\Caching\IStorage; | ||
use Nette\Caching\Storages\FileStorage; | ||
use Nette\Utils\Reflection; | ||
use Nextras\Dbal\IConnection; | ||
use Nextras\Orm\Bridges\SymfonyBundle\RepositoryLoader; | ||
use Nextras\Orm\Entity\Reflection\IMetadataParserFactory; | ||
use Nextras\Orm\Entity\Reflection\MetadataParserFactory; | ||
use Nextras\Orm\Exception\InvalidStateException; | ||
use Nextras\Orm\Exception\RuntimeException; | ||
use Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator; | ||
use Nextras\Orm\Model\IModel; | ||
use Nextras\Orm\Model\IRepositoryLoader; | ||
use Nextras\Orm\Model\MetadataStorage; | ||
use Nextras\Orm\Model\Model; | ||
use Nextras\Orm\Repository\IRepository; | ||
use ReflectionClass; | ||
use Symfony\Component\Config\Resource\FileResource; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
use Symfony\Component\HttpKernel\DependencyInjection\Extension; | ||
|
||
|
||
class NextrasOrmExtension extends Extension | ||
{ | ||
/** | ||
* @param array<mixed> $configs | ||
*/ | ||
public function load(array $configs, ContainerBuilder $builder): void | ||
{ | ||
$configuration = new Configuration(); | ||
$config = $this->processConfiguration($configuration, $configs); | ||
|
||
$modelClass = $config['model']; | ||
$repositories = $this->findRepositories($builder, $modelClass); | ||
$modelConfig = Model::getConfiguration($repositories); | ||
|
||
foreach ($repositories as $repositoryName => $repositoryClass) { | ||
$mapperClass = str_replace('Repository', 'Mapper', $repositoryClass); | ||
$this->setupMapper($builder, $mapperClass); | ||
$this->setupRepository($builder, $repositoryClass, $mapperClass, $modelClass); | ||
} | ||
|
||
$this->setupCacheStorage($builder); | ||
$this->setupCache($builder); | ||
$this->setupDbalMapperCoordinator($builder); | ||
$this->setupRepositoryLoader($builder); | ||
$this->setupMetadataParserFactory($builder); | ||
$this->setupMetadataStorage($builder, $modelConfig[2]); | ||
$this->setupModel($builder, $modelClass, $modelConfig); | ||
} | ||
|
||
|
||
/** | ||
* @return array<string, string> | ||
* @phpstan-param class-string<\Nextras\Orm\Model\IModel> $modelClass | ||
* @phpstan-return array<string, class-string<IRepository<\Nextras\Orm\Entity\IEntity>>> | ||
*/ | ||
protected function findRepositories(ContainerBuilder $builder, string $modelClass): array | ||
{ | ||
if ($modelClass === Model::class) { | ||
throw new InvalidStateException('Your model has to inherit from ' . Model::class . '. Use compiler extension configuration - model key.'); | ||
} | ||
|
||
$modelReflection = new ReflectionClass($modelClass); | ||
$classFileName = $modelReflection->getFileName(); | ||
assert($classFileName !== false); | ||
$builder->addResource(new FileResource($classFileName)); | ||
|
||
$repositories = []; | ||
preg_match_all( | ||
'~^ [ \t*]* @property(?:|-read) [ \t]+ ([^\s$]+) [ \t]+ \$ (\w+) ()~mx', | ||
(string) $modelReflection->getDocComment(), $matches, PREG_SET_ORDER | ||
); | ||
|
||
/** | ||
* @var string $type | ||
* @var string $name | ||
*/ | ||
foreach ($matches as [, $type, $name]) { | ||
/** @phpstan-var class-string<IRepository<\Nextras\Orm\Entity\IEntity>> $type */ | ||
$type = Reflection::expandClassName($type, $modelReflection); | ||
if (!class_exists($type)) { | ||
throw new RuntimeException("Repository '{$type}' does not exist."); | ||
} | ||
|
||
$rc = new ReflectionClass($type); | ||
assert($rc->implementsInterface(IRepository::class), sprintf( | ||
'Property "%s" of class "%s" with type "%s" does not implement interface %s.', | ||
$modelClass, $name, $type, IRepository::class | ||
)); | ||
|
||
$repositories[$name] = $type; | ||
} | ||
|
||
return $repositories; | ||
} | ||
|
||
|
||
private function setupCacheStorage(ContainerBuilder $builder): void | ||
{ | ||
if ($builder->has(IStorage::class)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition(FileStorage::class); | ||
$definition->setArgument('$dir', $builder->getParameter('kernel.cache_dir')); | ||
|
||
$builder->setDefinition(FileStorage::class, $definition); | ||
$builder->setAlias(IStorage::class, FileStorage::class); | ||
} | ||
|
||
|
||
private function setupCache(ContainerBuilder $builder): void | ||
{ | ||
if ($builder->has(Cache::class)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition(Cache::class); | ||
$definition->setArgument('$storage', new Reference(IStorage::class)); | ||
$definition->setArgument('$namespace', 'nextras_orm'); | ||
|
||
$builder->setDefinition(Cache::class, $definition); | ||
} | ||
|
||
|
||
private function setupDbalMapperCoordinator(ContainerBuilder $builder): void | ||
{ | ||
if ($builder->has(DbalMapperCoordinator::class)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition(DbalMapperCoordinator::class); | ||
$definition->setArgument('$connection', new Reference(IConnection::class)); | ||
|
||
$builder->setDefinition(DbalMapperCoordinator::class, $definition); | ||
} | ||
|
||
|
||
private function setupMapper(ContainerBuilder $builder, string $mapperClass): void | ||
{ | ||
if ($builder->has($mapperClass)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition($mapperClass); | ||
$definition->setArgument('$connection', new Reference(IConnection::class)); | ||
$definition->setArgument('$mapperCoordinator', new Reference(DbalMapperCoordinator::class)); | ||
$definition->setArgument('$cache', new Reference(Cache::class)); | ||
|
||
$builder->setDefinition($mapperClass, $definition); | ||
} | ||
|
||
|
||
private function setupRepository(ContainerBuilder $builder, string $repositoryClass, string $mapperClass, string $modelClass): void | ||
{ | ||
if ($builder->has($repositoryClass)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition($repositoryClass); | ||
$definition->setArgument('$mapper', new Reference($mapperClass)); | ||
$definition->addMethodCall('setModel', [new Reference($modelClass)]); | ||
$definition->setPublic(true); | ||
|
||
$builder->setDefinition($repositoryClass, $definition); | ||
} | ||
|
||
|
||
private function setupRepositoryLoader(ContainerBuilder $builder): void | ||
{ | ||
if ($builder->has(IRepositoryLoader::class)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition(RepositoryLoader::class); | ||
$definition->setArgument('$container', new Reference(ContainerInterface::class)); | ||
|
||
$builder->setDefinition(RepositoryLoader::class, $definition); | ||
$builder->setAlias(IRepositoryLoader::class, RepositoryLoader::class); | ||
} | ||
|
||
|
||
private function setupMetadataParserFactory(ContainerBuilder $builder): void | ||
{ | ||
if ($builder->has(IMetadataParserFactory::class)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition(MetadataParserFactory::class); | ||
|
||
$builder->setDefinition(MetadataParserFactory::class, $definition); | ||
$builder->setAlias(IMetadataParserFactory::class, MetadataParserFactory::class); | ||
} | ||
|
||
|
||
/** | ||
* @param array<string, string> $entityClassMap | ||
*/ | ||
private function setupMetadataStorage(ContainerBuilder $builder, array $entityClassMap): void | ||
{ | ||
if ($builder->has(MetadataStorage::class)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition(MetadataStorage::class); | ||
$definition->setArgument('$entityClassesMap', $entityClassMap); | ||
$definition->setArgument('$cache', new Reference(Cache::class)); | ||
$definition->setArgument('$metadataParserFactory', new Reference(IMetadataParserFactory::class)); | ||
$definition->setArgument('$repositoryLoader', new Reference(IRepositoryLoader::class)); | ||
|
||
$builder->setDefinition(MetadataStorage::class, $definition); | ||
} | ||
|
||
|
||
/** | ||
* @param mixed[] $config | ||
*/ | ||
private function setupModel(ContainerBuilder $builder, string $modelClass, array $config): void | ||
{ | ||
if ($builder->has($modelClass)) { | ||
return; | ||
} | ||
|
||
$definition = new Definition($modelClass); | ||
$definition->setArgument('$configuration', $config); | ||
$definition->setArgument('$repositoryLoader', new Reference(IRepositoryLoader::class)); | ||
$definition->setArgument('$metadataStorage', new Reference(MetadataStorage::class)); | ||
|
||
$builder->setDefinition($modelClass, $definition); | ||
$builder->setAlias(IModel::class, $modelClass); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Nextras\Orm\Bridges\SymfonyBundle; | ||
|
||
|
||
use Symfony\Component\HttpKernel\Bundle\Bundle; | ||
|
||
|
||
class NextrasOrmBundle extends Bundle | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Nextras\Orm\Bridges\SymfonyBundle; | ||
|
||
use Nextras; | ||
use Nextras\Orm\Repository\IRepository; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
||
|
||
class RepositoryLoader implements Nextras\Orm\Model\IRepositoryLoader | ||
{ | ||
/** @var ContainerInterface */ | ||
private $container; | ||
|
||
|
||
public function __construct(ContainerInterface $container) | ||
{ | ||
$this->container = $container; | ||
} | ||
|
||
|
||
public function hasRepository(string $className): bool | ||
{ | ||
return $this->container->has($className); | ||
} | ||
|
||
|
||
/** | ||
* @phpstan-template R of IRepository<\Nextras\Orm\Entity\IEntity> | ||
* @phpstan-param class-string<R> $className | ||
* @phpstan-return R | ||
*/ | ||
public function getRepository(string $className): IRepository | ||
{ | ||
/** @phpstan-var R */ | ||
return $this->container->get($className); | ||
} | ||
|
||
|
||
public function isCreated(string $className): bool | ||
{ | ||
return $this->container->initialized($className); | ||
} | ||
} |