-
-
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
328 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
29 changes: 29 additions & 0 deletions
29
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,29 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace Nextras\Orm\Bridges\SymfonyBundle\DependencyInjection; | ||
|
||
|
||
use Symfony\Component\Config\Definition\Builder\TreeBuilder; | ||
use Symfony\Component\Config\Definition\ConfigurationInterface; | ||
use function array_key_exists; | ||
use function is_array; | ||
|
||
|
||
class Configuration implements ConfigurationInterface | ||
{ | ||
public function getConfigTreeBuilder() | ||
{ | ||
$treeBuilder = new TreeBuilder('nextras_orm'); | ||
|
||
// @formatter:off | ||
$treeBuilder->getRootNode() | ||
->children() | ||
->scalarNode('model') | ||
->isRequired() | ||
->end() | ||
->end(); | ||
// @formatter:on | ||
|
||
return $treeBuilder; | ||
} | ||
} |
244 changes: 244 additions & 0 deletions
244
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,244 @@ | ||
<?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\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 | ||
{ | ||
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>> | ||
*/ | ||
protected function findRepositories(ContainerBuilder $container, string $modelClass): array | ||
{ | ||
if ($modelClass === Model::class) { | ||
throw new \Nextras\Orm\Exception\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); | ||
$container->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> $type */ | ||
$type = Reflection::expandClassName($type, $modelReflection); | ||
if (!class_exists($type)) { | ||
throw new \Nextras\Orm\Exception\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,41 @@ | ||
<?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); | ||
} | ||
|
||
|
||
public function getRepository(string $className): IRepository | ||
{ | ||
$repository = $this->container->get($className); | ||
assert($repository instanceof IRepository); | ||
|
||
return $repository; | ||
} | ||
|
||
|
||
public function isCreated(string $className): bool | ||
{ | ||
return $this->container->initialized($className); | ||
} | ||
} |