Skip to content

Commit 7b42e8c

Browse files
committed
add Extension extensibility entry point
1 parent ead7ce1 commit 7b42e8c

11 files changed

Lines changed: 208 additions & 20 deletions

src/Bridges/NetteDI/DIRepositoryFinder.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ class DIRepositoryFinder implements IRepositoryFinder
1919

2020

2121
// @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/587
22-
public function __construct(string $modelClass, ContainerBuilder $containerBuilder, OrmExtension $extension)
22+
public function __construct(
23+
string $modelClass,
24+
protected readonly array $extensions,
25+
ContainerBuilder $containerBuilder,
26+
OrmExtension $extension,
27+
)
2328
{
2429
$this->builder = $containerBuilder;
2530
$this->extension = $extension;
@@ -87,6 +92,7 @@ protected function setupRepositoryLoader(array $repositoriesMap): void
8792
->setType(RepositoryLoader::class)
8893
->setArguments([
8994
'repositoryNamesMap' => $repositoriesMap,
95+
'extensions' => $this->extensions,
9096
]);
9197
}
9298

src/Bridges/NetteDI/IRepositoryFinder.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55

66
use Nette\DI\ContainerBuilder;
7+
use Nette\DI\Definitions\Statement;
78
use Nextras\Orm\Entity\IEntity;
89
use Nextras\Orm\Model\IModel;
910
use Nextras\Orm\Repository\IRepository;
@@ -13,8 +14,9 @@ interface IRepositoryFinder
1314
{
1415
/**
1516
* @param class-string<IModel> $modelClass
17+
* @param list<Statement> $extensions
1618
*/
17-
public function __construct(string $modelClass, ContainerBuilder $containerBuilder, OrmExtension $extension);
19+
public function __construct(string $modelClass, array $extensions, ContainerBuilder $containerBuilder, OrmExtension $extension);
1820

1921

2022
/**

src/Bridges/NetteDI/OrmExtension.php

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
use Nette\Caching\Cache;
77
use Nette\DI\CompilerExtension;
88
use Nette\DI\ContainerBuilder;
9+
use Nette\DI\Definitions\Statement;
910
use Nette\Schema\Expect;
1011
use Nette\Schema\Schema;
1112
use Nextras\Dbal\IConnection;
1213
use Nextras\Orm\Entity\IEntity;
13-
use Nextras\Orm\Entity\Reflection\IMetadataParserFactory;
14-
use Nextras\Orm\Entity\Reflection\MetadataParser;
14+
use Nextras\Orm\Entity\Reflection\MetadataParserFactory;
1515
use Nextras\Orm\Exception\InvalidStateException;
16+
use Nextras\Orm\Extension;
1617
use Nextras\Orm\Mapper\Dbal\DbalMapperCoordinator;
1718
use Nextras\Orm\Model\IModel;
1819
use Nextras\Orm\Model\MetadataStorage;
@@ -39,6 +40,7 @@ public function getConfigSchema(): Schema
3940
{
4041
return Expect::structure([
4142
'model' => Expect::string()->default(Model::class),
43+
'extensions' => Expect::arrayOf('string|Nette\DI\Definitions\Statement')->default([]),
4244
'repositoryFinder' => Expect::string()->default(PhpDocRepositoryFinder::class),
4345
'initializeMetadata' => Expect::bool()->default(false),
4446
'autowiredInternalServices' => Expect::bool()->default(true),
@@ -52,23 +54,28 @@ public function loadConfiguration(): void
5254
$this->builder = $this->getContainerBuilder();
5355
$this->modelClass = $this->config->model;
5456

57+
$extensions = [];
58+
foreach ($this->config->extensions as $extension) {
59+
$extensions[] = is_string($extension) ? new Statement($extension) : $extension;
60+
}
61+
5562
$repositoryFinderClass = $this->config->repositoryFinder;
5663
if (!is_subclass_of($repositoryFinderClass, IRepositoryFinder::class)) {
5764
throw new InvalidStateException('Repository finder does not implement Nextras\Orm\Bridges\NetteDI\IRepositoryFinder interface.');
5865
}
59-
$this->repositoryFinder = new $repositoryFinderClass($this->modelClass, $this->builder, $this);
66+
$this->repositoryFinder = new $repositoryFinderClass($this->modelClass, $extensions, $this->builder, $this);
6067

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

6370
$this->setupCache();
6471
$this->setupDependencyProvider();
6572
$this->setupDbalMapperDependencies();
66-
$this->setupMetadataParserFactory();
73+
$this->setupMetadataParserFactory($extensions);
6774

6875
if ($repositories !== null) {
6976
$repositoriesConfig = Model::getConfiguration($repositories);
7077
$this->setupMetadataStorage($repositoriesConfig[2]);
71-
$this->setupModel($this->modelClass, $repositoriesConfig);
78+
$this->setupModel($this->modelClass, $repositoriesConfig, $extensions);
7279
}
7380

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

8289
if ($repositories !== null) {
90+
$extensions = [];
91+
foreach ($this->config->extensions as $extension) {
92+
$extensions[] = is_string($extension) ? new Statement($extension) : $extension;
93+
}
94+
8395
$repositoriesConfig = Model::getConfiguration($repositories);
8496
$this->setupMetadataStorage($repositoriesConfig[2]);
85-
$this->setupModel($this->modelClass, $repositoriesConfig);
97+
$this->setupModel($this->modelClass, $repositoriesConfig, $extensions);
8698
}
8799

88100
$this->setupDbalMapperDependencies();
@@ -138,18 +150,19 @@ protected function setupDbalMapperDependencies(): void
138150
}
139151

140152

141-
protected function setupMetadataParserFactory(): void
153+
/**
154+
* @param list<Extension> $extensions
155+
*/
156+
protected function setupMetadataParserFactory(array $extensions): void
142157
{
143158
$factoryName = $this->prefix('metadataParserFactory');
144159
if ($this->builder->hasDefinition($factoryName)) {
145160
return;
146161
}
147162

148-
$this->builder->addFactoryDefinition($factoryName)
149-
->setImplement(IMetadataParserFactory::class)
150-
->getResultDefinition()
151-
->setType(MetadataParser::class)
152-
->setArguments(['$entityClassesMap'])
163+
$this->builder->addDefinition($factoryName)
164+
->setType(MetadataParserFactory::class)
165+
->setArgument('extensions', $extensions)
153166
->setAutowired($this->config->autowiredInternalServices);
154167
}
155168

@@ -182,8 +195,9 @@ protected function setupMetadataStorage(array $entityClassMap): void
182195
* array<string, class-string<IRepository<IEntity>>>,
183196
* array<class-string<IEntity>, class-string<IRepository<IEntity>>>
184197
* } $repositoriesConfig
198+
* @param list<Statement> $extensions
185199
*/
186-
protected function setupModel(string $modelClass, array $repositoriesConfig): void
200+
protected function setupModel(string $modelClass, array $repositoriesConfig, array $extensions): void
187201
{
188202
$modelName = $this->prefix('model');
189203
if ($this->builder->hasDefinition($modelName)) {
@@ -196,7 +210,8 @@ protected function setupModel(string $modelClass, array $repositoriesConfig): vo
196210
'configuration' => $repositoriesConfig,
197211
'repositoryLoader' => $this->prefix('@repositoryLoader'),
198212
'metadataStorage' => $this->prefix('@metadataStorage'),
199-
]);
213+
])
214+
->addSetup('foreach (? as $e) { $e->configureModel($service); }', [$extensions]);
200215
}
201216

202217

src/Bridges/NetteDI/PhpDocRepositoryFinder.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class PhpDocRepositoryFinder implements IRepositoryFinder
1818
{
1919
public function __construct(
2020
protected readonly string $modelClass,
21+
protected readonly array $extensions,
2122
protected readonly ContainerBuilder $builder,
2223
protected readonly OrmExtension $extension,
2324
)
@@ -143,6 +144,7 @@ protected function setupRepositoryLoader(array $repositoriesMap): void
143144
->setType(RepositoryLoader::class)
144145
->setArguments([
145146
'repositoryNamesMap' => $repositoriesMap,
147+
'extensions' => $this->extensions,
146148
]);
147149
}
148150
}

src/Bridges/NetteDI/RepositoryLoader.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@
55

66
use Nette\DI\Container;
77
use Nextras\Orm\Entity\IEntity;
8+
use Nextras\Orm\Extension;
89
use Nextras\Orm\Model\IRepositoryLoader;
910
use Nextras\Orm\Repository\IRepository;
1011

1112

1213
class RepositoryLoader implements IRepositoryLoader
1314
{
15+
/** @var array<string, true> */
16+
private array $configuredRepositories = [];
17+
18+
1419
/**
1520
* @param array<class-string<IRepository<IEntity>>, string> $repositoryNamesMap
21+
* @param list<Extension> $extensions
1622
*/
1723
public function __construct(
1824
private readonly Container $container,
1925
private readonly array $repositoryNamesMap,
26+
private readonly array $extensions,
2027
)
2128
{
2229
}
@@ -37,7 +44,17 @@ public function hasRepository(string $className): bool
3744
public function getRepository(string $className): IRepository
3845
{
3946
/** @var R */
40-
return $this->container->getService($this->repositoryNamesMap[$className]);
47+
$repository = $this->container->getService($this->repositoryNamesMap[$className]);
48+
49+
if (!isset($this->configuredRepositories[$className])) {
50+
$this->configuredRepositories[$className] = true;
51+
foreach ($this->extensions as $extensions) {
52+
$extensions->configureRepository($repository);
53+
$extensions->configureMapper($repository->getMapper());
54+
}
55+
}
56+
57+
return $repository;
4158
}
4259

4360

src/Entity/Reflection/MetadataParser.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Nextras\Orm\Entity\PropertyWrapper\PrimaryProxyWrapper;
1919
use Nextras\Orm\Exception\InvalidStateException;
2020
use Nextras\Orm\Exception\NotSupportedException;
21+
use Nextras\Orm\Extension;
2122
use Nextras\Orm\Relationships\HasMany;
2223
use Nextras\Orm\Relationships\ManyHasMany;
2324
use Nextras\Orm\Relationships\ManyHasOne;
@@ -94,8 +95,12 @@ class MetadataParser implements IMetadataParser
9495
/**
9596
* @param array<string, string> $entityClassesMap
9697
* @param array<class-string<IEntity>, class-string<IRepository<IEntity>>> $entityClassesMap
98+
* @param list<Extension> $extensions
9799
*/
98-
public function __construct(array $entityClassesMap)
100+
public function __construct(
101+
array $entityClassesMap,
102+
protected array $extensions = [],
103+
)
99104
{
100105
$this->entityClassesMap = $entityClassesMap;
101106
$this->modifierParser = new ModifierParser();
@@ -135,6 +140,10 @@ public function parseMetadata(string $entityClass, array|null &$fileDependencies
135140
$this->loadProperties($fileDependencies);
136141
$this->initPrimaryKey();
137142

143+
foreach ($this->extensions as $extension) {
144+
$extension->configureEntityMetadata($this->metadata);
145+
}
146+
138147
if ($fileDependencies !== null) {
139148
$fileDependencies = array_values(array_unique($fileDependencies));
140149
}
@@ -239,6 +248,11 @@ protected function parseProperty(
239248
$this->parseAnnotationValue($property, $propertyNode->description);
240249
$this->processPropertyGettersSetters($property, $methods);
241250
$this->processDefaultPropertyWrappers($property);
251+
252+
foreach ($this->extensions as $extension) {
253+
$extension->configureEntityPropertyMetadata($this->metadata, $property, $propertyNode->type);
254+
}
255+
242256
return $property;
243257
}
244258

src/Entity/Reflection/MetadataParserFactory.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@
33
namespace Nextras\Orm\Entity\Reflection;
44

55

6+
use Nextras\Orm\Extension;
7+
8+
69
class MetadataParserFactory implements IMetadataParserFactory
710
{
11+
/** @param list<Extension> $extensions */
12+
public function __construct(
13+
private readonly array $extensions = [],
14+
)
15+
{
16+
}
17+
18+
819
public function create(array $entityClassesMap): IMetadataParser
920
{
10-
return new MetadataParser($entityClassesMap);
21+
return new MetadataParser($entityClassesMap, $this->extensions);
1122
}
1223
}

src/Extension.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Nextras\Orm;
4+
5+
6+
use Nextras\Orm\Bridges\NetteDI\OrmExtension;
7+
use Nextras\Orm\Entity\Reflection\EntityMetadata;
8+
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
9+
use Nextras\Orm\Mapper\IMapper;
10+
use Nextras\Orm\Model\IModel;
11+
use Nextras\Orm\Repository\IRepository;
12+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
13+
14+
15+
/**
16+
* An entry point for various extensions for Orm.
17+
*
18+
* To implement the extension, override methods you want to utilize.
19+
* Registering a new configurator extension is done through {@see OrmExtension} for Nette DIC.
20+
*/
21+
abstract class Extension
22+
{
23+
/**
24+
* Modifies the model instance.
25+
*
26+
* Runs once when the model is instantiated.
27+
*/
28+
public function configureModel(
29+
IModel $model,
30+
): void
31+
{
32+
}
33+
34+
/**
35+
* Modifies the repository instance.
36+
*
37+
* Runs every time the mapper is instantiated in runtime.
38+
*
39+
* @param IRepository<*> $repository
40+
*/
41+
public function configureRepository(
42+
IRepository $repository,
43+
): void
44+
{
45+
}
46+
47+
48+
/**
49+
* Modifies the mapper instance.
50+
*
51+
* Runs every time the mapper is instantiated in runtime.
52+
*
53+
* @param IMapper<*> $mapper
54+
*/
55+
public function configureMapper(
56+
IMapper $mapper,
57+
): void
58+
{
59+
}
60+
61+
62+
/**
63+
* Modifies the entity metadata instance.
64+
*
65+
* Runs when entity property metadata are parsed during compile time (before cache serialization).
66+
*/
67+
public function configureEntityMetadata(
68+
EntityMetadata $metadata,
69+
): void
70+
{
71+
}
72+
73+
74+
/**
75+
* Modifies the entity property metadata instance.
76+
*
77+
* Runs when entity property metadata are parsed during compile time (before cache serialization).
78+
*/
79+
public function configureEntityPropertyMetadata(
80+
EntityMetadata $entityMetadata,
81+
PropertyMetadata $propertyMetadata,
82+
TypeNode $propertyType,
83+
): void
84+
{
85+
}
86+
}

0 commit comments

Comments
 (0)