Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Bridges/NetteDI/DIRepositoryFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ class DIRepositoryFinder implements IRepositoryFinder


// @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/587
public function __construct(string $modelClass, ContainerBuilder $containerBuilder, OrmExtension $extension)
public function __construct(
string $modelClass,
protected readonly array $extensions,
ContainerBuilder $containerBuilder,
OrmExtension $extension,
)
{
$this->builder = $containerBuilder;
$this->extension = $extension;
Expand Down Expand Up @@ -87,6 +92,7 @@ protected function setupRepositoryLoader(array $repositoriesMap): void
->setType(RepositoryLoader::class)
->setArguments([
'repositoryNamesMap' => $repositoriesMap,
'extensions' => $this->extensions,
]);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Bridges/NetteDI/IRepositoryFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


use Nette\DI\ContainerBuilder;
use Nette\DI\Definitions\Statement;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Repository\IRepository;
Expand All @@ -13,8 +14,9 @@ interface IRepositoryFinder
{
/**
* @param class-string<IModel> $modelClass
* @param list<Statement> $extensions
*/
public function __construct(string $modelClass, ContainerBuilder $containerBuilder, OrmExtension $extension);
public function __construct(string $modelClass, array $extensions, ContainerBuilder $containerBuilder, OrmExtension $extension);


/**
Expand Down
43 changes: 29 additions & 14 deletions src/Bridges/NetteDI/OrmExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
use Nette\Caching\Cache;
use Nette\DI\CompilerExtension;
use Nette\DI\ContainerBuilder;
use Nette\DI\Definitions\Statement;
use Nette\Schema\Expect;
use Nette\Schema\Schema;
use Nextras\Dbal\IConnection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\IMetadataParserFactory;
use Nextras\Orm\Entity\Reflection\MetadataParser;
use Nextras\Orm\Entity\Reflection\MetadataParserFactory;
use Nextras\Orm\Exception\InvalidStateException;
use Nextras\Orm\Extension;
use Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Model\MetadataStorage;
Expand All @@ -39,6 +40,7 @@ public function getConfigSchema(): Schema
{
return Expect::structure([
'model' => Expect::string()->default(Model::class),
'extensions' => Expect::arrayOf('string|Nette\DI\Definitions\Statement')->default([]),
'repositoryFinder' => Expect::string()->default(PhpDocRepositoryFinder::class),
'initializeMetadata' => Expect::bool()->default(false),
'autowiredInternalServices' => Expect::bool()->default(true),
Expand All @@ -52,23 +54,28 @@ public function loadConfiguration(): void
$this->builder = $this->getContainerBuilder();
$this->modelClass = $this->config->model;

$extensions = [];
foreach ($this->config->extensions as $extension) {
$extensions[] = is_string($extension) ? new Statement($extension) : $extension;
}

$repositoryFinderClass = $this->config->repositoryFinder;
if (!is_subclass_of($repositoryFinderClass, IRepositoryFinder::class)) {
throw new InvalidStateException('Repository finder does not implement Nextras\Orm\Bridges\NetteDI\IRepositoryFinder interface.');
}
$this->repositoryFinder = new $repositoryFinderClass($this->modelClass, $this->builder, $this);
$this->repositoryFinder = new $repositoryFinderClass($this->modelClass, $extensions, $this->builder, $this);

$repositories = $this->repositoryFinder->loadConfiguration();

$this->setupCache();
$this->setupDependencyProvider();
$this->setupDbalMapperDependencies();
$this->setupMetadataParserFactory();
$this->setupMetadataParserFactory($extensions);

if ($repositories !== null) {
$repositoriesConfig = Model::getConfiguration($repositories);
$this->setupMetadataStorage($repositoriesConfig[2]);
$this->setupModel($this->modelClass, $repositoriesConfig);
$this->setupModel($this->modelClass, $repositoriesConfig, $extensions);
}

$this->initializeMetadata($this->config->initializeMetadata);
Expand All @@ -80,9 +87,14 @@ public function beforeCompile(): void
$repositories = $this->repositoryFinder->beforeCompile();

if ($repositories !== null) {
$extensions = [];
foreach ($this->config->extensions as $extension) {
$extensions[] = is_string($extension) ? new Statement($extension) : $extension;
}

$repositoriesConfig = Model::getConfiguration($repositories);
$this->setupMetadataStorage($repositoriesConfig[2]);
$this->setupModel($this->modelClass, $repositoriesConfig);
$this->setupModel($this->modelClass, $repositoriesConfig, $extensions);
}

$this->setupDbalMapperDependencies();
Expand Down Expand Up @@ -138,18 +150,19 @@ protected function setupDbalMapperDependencies(): void
}


protected function setupMetadataParserFactory(): void
/**
* @param list<Extension> $extensions
*/
protected function setupMetadataParserFactory(array $extensions): void
{
$factoryName = $this->prefix('metadataParserFactory');
if ($this->builder->hasDefinition($factoryName)) {
return;
}

$this->builder->addFactoryDefinition($factoryName)
->setImplement(IMetadataParserFactory::class)
->getResultDefinition()
->setType(MetadataParser::class)
->setArguments(['$entityClassesMap'])
$this->builder->addDefinition($factoryName)
->setType(MetadataParserFactory::class)
->setArgument('extensions', $extensions)
->setAutowired($this->config->autowiredInternalServices);
}

Expand Down Expand Up @@ -182,8 +195,9 @@ protected function setupMetadataStorage(array $entityClassMap): void
* array<string, class-string<IRepository<IEntity>>>,
* array<class-string<IEntity>, class-string<IRepository<IEntity>>>
* } $repositoriesConfig
* @param list<Statement> $extensions
*/
protected function setupModel(string $modelClass, array $repositoriesConfig): void
protected function setupModel(string $modelClass, array $repositoriesConfig, array $extensions): void
{
$modelName = $this->prefix('model');
if ($this->builder->hasDefinition($modelName)) {
Expand All @@ -196,7 +210,8 @@ protected function setupModel(string $modelClass, array $repositoriesConfig): vo
'configuration' => $repositoriesConfig,
'repositoryLoader' => $this->prefix('@repositoryLoader'),
'metadataStorage' => $this->prefix('@metadataStorage'),
]);
])
->addSetup('foreach (? as $e) { $e->configureModel($service); }', [$extensions]);
}


Expand Down
2 changes: 2 additions & 0 deletions src/Bridges/NetteDI/PhpDocRepositoryFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class PhpDocRepositoryFinder implements IRepositoryFinder
{
public function __construct(
protected readonly string $modelClass,
protected readonly array $extensions,
protected readonly ContainerBuilder $builder,
protected readonly OrmExtension $extension,
)
Expand Down Expand Up @@ -143,6 +144,7 @@ protected function setupRepositoryLoader(array $repositoriesMap): void
->setType(RepositoryLoader::class)
->setArguments([
'repositoryNamesMap' => $repositoriesMap,
'extensions' => $this->extensions,
]);
}
}
19 changes: 18 additions & 1 deletion src/Bridges/NetteDI/RepositoryLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@

use Nette\DI\Container;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Extension;
use Nextras\Orm\Model\IRepositoryLoader;
use Nextras\Orm\Repository\IRepository;


class RepositoryLoader implements IRepositoryLoader
{
/** @var array<string, true> */
private array $configuredRepositories = [];


/**
* @param array<class-string<IRepository<IEntity>>, string> $repositoryNamesMap
* @param list<Extension> $extensions
*/
public function __construct(
private readonly Container $container,
private readonly array $repositoryNamesMap,
private readonly array $extensions,
)
{
}
Expand All @@ -37,7 +44,17 @@ public function hasRepository(string $className): bool
public function getRepository(string $className): IRepository
{
/** @var R */
return $this->container->getService($this->repositoryNamesMap[$className]);
$repository = $this->container->getService($this->repositoryNamesMap[$className]);

if (!isset($this->configuredRepositories[$className])) {
$this->configuredRepositories[$className] = true;
foreach ($this->extensions as $extensions) {
$extensions->configureRepository($repository);
$extensions->configureMapper($repository->getMapper());
}
}

return $repository;
}


Expand Down
16 changes: 15 additions & 1 deletion src/Entity/Reflection/MetadataParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Nextras\Orm\Entity\PropertyWrapper\PrimaryProxyWrapper;
use Nextras\Orm\Exception\InvalidStateException;
use Nextras\Orm\Exception\NotSupportedException;
use Nextras\Orm\Extension;
use Nextras\Orm\Relationships\HasMany;
use Nextras\Orm\Relationships\ManyHasMany;
use Nextras\Orm\Relationships\ManyHasOne;
Expand Down Expand Up @@ -94,8 +95,12 @@ class MetadataParser implements IMetadataParser
/**
* @param array<string, string> $entityClassesMap
* @param array<class-string<IEntity>, class-string<IRepository<IEntity>>> $entityClassesMap
* @param list<Extension> $extensions
*/
public function __construct(array $entityClassesMap)
public function __construct(
array $entityClassesMap,
protected array $extensions = [],
)
{
$this->entityClassesMap = $entityClassesMap;
$this->modifierParser = new ModifierParser();
Expand Down Expand Up @@ -135,6 +140,10 @@ public function parseMetadata(string $entityClass, array|null &$fileDependencies
$this->loadProperties($fileDependencies);
$this->initPrimaryKey();

foreach ($this->extensions as $extension) {
$extension->configureEntityMetadata($this->metadata);
}

if ($fileDependencies !== null) {
$fileDependencies = array_values(array_unique($fileDependencies));
}
Expand Down Expand Up @@ -239,6 +248,11 @@ protected function parseProperty(
$this->parseAnnotationValue($property, $propertyNode->description);
$this->processPropertyGettersSetters($property, $methods);
$this->processDefaultPropertyWrappers($property);

foreach ($this->extensions as $extension) {
$extension->configureEntityPropertyMetadata($this->metadata, $property, $propertyNode->type);
}

return $property;
}

Expand Down
13 changes: 12 additions & 1 deletion src/Entity/Reflection/MetadataParserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@
namespace Nextras\Orm\Entity\Reflection;


use Nextras\Orm\Extension;


class MetadataParserFactory implements IMetadataParserFactory
{
/** @param list<Extension> $extensions */
public function __construct(
private readonly array $extensions = [],
)
{
}


public function create(array $entityClassesMap): IMetadataParser
{
return new MetadataParser($entityClassesMap);
return new MetadataParser($entityClassesMap, $this->extensions);
}
}
86 changes: 86 additions & 0 deletions src/Extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm;


use Nextras\Orm\Bridges\NetteDI\OrmExtension;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Repository\IRepository;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;


/**
* An entry point for various extensions for Orm.
*
* To implement the extension, override methods you want to utilize.
* Registering a new configurator extension is done through {@see OrmExtension} for Nette DIC.
*/
abstract class Extension
{
/**
* Modifies the model instance.
*
* Runs once when the model is instantiated.
*/
public function configureModel(
IModel $model,
): void
{
}

/**
* Modifies the repository instance.
*
* Runs every time the mapper is instantiated in runtime.
*
* @param IRepository<*> $repository
*/
public function configureRepository(
IRepository $repository,
): void
{
}


/**
* Modifies the mapper instance.
*
* Runs every time the mapper is instantiated in runtime.
*
* @param IMapper<*> $mapper
*/
public function configureMapper(
IMapper $mapper,
): void
{
}


/**
* Modifies the entity metadata instance.
*
* Runs when entity property metadata are parsed during compile time (before cache serialization).
*/
public function configureEntityMetadata(
EntityMetadata $metadata,
): void
{
}


/**
* Modifies the entity property metadata instance.
*
* Runs when entity property metadata are parsed during compile time (before cache serialization).
*/
public function configureEntityPropertyMetadata(
EntityMetadata $entityMetadata,
PropertyMetadata $propertyMetadata,
TypeNode $propertyType,
): void
{
}
}
Loading
Loading