Skip to content

Commit 26d6ea5

Browse files
committed
feature: add support for ClassLocator
In the scope of doctrine/persistence#433 (available from `doctrine/persistence` >= 4.1) there was added `ColocatedMappingDriver::$classLocator`, which allows passing any instance of `ClassLocator` for the mapping driver to use. This commit integrates those changes into `AbstractDoctrineExtension`, used by respective ORM, MongoDB-ODM, PHPCR-ODM bundles. The solution registers a "mapping_class_finder" service that can be used by the client code to customize class finding logic. The changes come into play starting with doctrine/persistence >= 4.1, and the actual registration happens only if `AttributeDriver` supports `ClassLocator`. Dependent libraries would adhere to the same interface, where `ClassLocator` is in the first argument. The changes were introduced for: - ORM: doctrine/orm#12131; - ODM: doctrine/mongodb-odm#2802; - PHPCR ODM: doctrine/phpcr-odm#875.
1 parent 6ef2577 commit 26d6ea5

File tree

3 files changed

+149
-2
lines changed

3 files changed

+149
-2
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
"async-aws/sns": "^1.0",
135135
"cache/integration-tests": "dev-master",
136136
"doctrine/collections": "^1.8|^2.0",
137-
"doctrine/data-fixtures": "^1.1",
137+
"doctrine/data-fixtures": "^1.1|^2.0",
138138
"doctrine/dbal": "^3.6|^4",
139139
"doctrine/orm": "^2.15|^3",
140140
"dragonmantank/cron-expression": "^3.1",

src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@
1111

1212
namespace Symfony\Bridge\Doctrine\DependencyInjection;
1313

14+
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
15+
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
16+
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
1417
use Symfony\Component\DependencyInjection\Alias;
1518
use Symfony\Component\DependencyInjection\ContainerBuilder;
1619
use Symfony\Component\DependencyInjection\Definition;
1720
use Symfony\Component\DependencyInjection\Reference;
21+
use Symfony\Component\Finder\Finder;
1822
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
23+
use Symfony\Component\TypeInfo\Type;
24+
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
1925

2026
/**
2127
* This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need.
@@ -30,6 +36,8 @@ abstract class AbstractDoctrineExtension extends Extension
3036
protected array $aliasMap = [];
3137

3238
/**
39+
* @var array<string,array<string,string>> An array of directory paths by namespace, indexed by driver type.
40+
*
3341
* Used inside metadata driver method to simplify aggregation of data.
3442
*/
3543
protected array $drivers = [];
@@ -185,7 +193,8 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder
185193
}
186194

187195
foreach ($this->drivers as $driverType => $driverPaths) {
188-
$mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver');
196+
$driverName = $objectManager['name'].'_'.$driverType;
197+
$mappingService = $this->getObjectManagerElementName($driverName.'_metadata_driver');
189198
if ($container->hasDefinition($mappingService)) {
190199
$mappingDriverDef = $container->getDefinition($mappingService);
191200
$args = $mappingDriverDef->getArguments();
@@ -203,6 +212,20 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder
203212
$mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']);
204213
}
205214

215+
// Available since doctrine/persistence >= 4.1
216+
if (interface_exists(ClassLocator::class) && 'attribute' === $driverType) {
217+
$driverClass = $mappingDriverDef->getClass();
218+
219+
/** @var string[] $directoryPaths */
220+
$directoryPaths = $mappingDriverDef->getArgument(0);
221+
222+
$classLocator = $this->registerMappingClassLocatorService($driverClass, $driverName, $container, $directoryPaths);
223+
224+
if (null !== $classLocator) {
225+
$mappingDriverDef->replaceArgument(0, new Reference($classLocator));
226+
}
227+
}
228+
206229
$container->setDefinition($mappingService, $mappingDriverDef);
207230

208231
foreach ($driverPaths as $prefix => $driverPath) {
@@ -213,6 +236,56 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder
213236
$container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef);
214237
}
215238

239+
/**
240+
* @param class-string<MappingDriver> $driverClass
241+
* @param string[] $dirs
242+
*
243+
* @return ?string service id, or null if not available
244+
*/
245+
private function registerMappingClassLocatorService(string $driverClass, string $driverName, ContainerBuilder $container, array $dirs): ?string
246+
{
247+
$parameter = new \ReflectionParameter([$driverClass, '__construct'], 0);
248+
249+
$parameterType = TypeResolver::create()->resolve($parameter);
250+
251+
// It's possible that doctrine/persistence:^4.1 is installed with the older versions of ORM/ODM.
252+
// In this case it's necessary to check for actual driver support.
253+
if (!$parameterType->isIdentifiedBy(ClassLocator::class)) {
254+
return null;
255+
}
256+
257+
$classLocator = $this->getObjectManagerElementName($driverName.'_mapping_class_locator');
258+
259+
$locatorDefinition = new Definition(
260+
FileClassLocator::class,
261+
[new Reference($this->registerMappingClassFinderService($driverName, $container, $dirs))],
262+
);
263+
264+
$container->setDefinition($classLocator, $locatorDefinition);
265+
266+
return $classLocator;
267+
}
268+
269+
/** @param string[] $dirs */
270+
private function registerMappingClassFinderService(string $driverName, ContainerBuilder $container, array $dirs): string
271+
{
272+
$finderService = $this->getObjectManagerElementName($driverName.'_mapping_class_finder');
273+
274+
if ($container->hasDefinition($finderService)) {
275+
$finderDefinition = $container->getDefinition($finderService);
276+
} else {
277+
$finderDefinition = new Definition(Finder::class, []);
278+
}
279+
280+
$finderDefinition->addMethodCall('files');
281+
$finderDefinition->addMethodCall('name', ['*.php']);
282+
$finderDefinition->addMethodCall('in', [$dirs]);
283+
284+
$container->setDefinition($finderService, $finderDefinition);
285+
286+
return $finderService;
287+
}
288+
216289
/**
217290
* Assertion if the specified mapping information is valid.
218291
*

src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111

1212
namespace Symfony\Bridge\Doctrine\Tests\DependencyInjection;
1313

14+
use Composer\InstalledVersions;
15+
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
16+
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
17+
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
1418
use PHPUnit\Framework\Attributes\DataProvider;
1519
use PHPUnit\Framework\MockObject\MockObject;
1620
use PHPUnit\Framework\TestCase;
1721
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
1822
use Symfony\Component\DependencyInjection\ContainerBuilder;
1923
use Symfony\Component\DependencyInjection\Definition;
2024
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
25+
use Symfony\Component\Finder\Finder;
2126
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
2227

2328
/**
@@ -55,6 +60,54 @@ protected function setUp(): void
5560
->willReturn('orm');
5661
}
5762

63+
public function testMappingClassLocatorIsNotRegisteredWhenDriversConstructorDoesntAcceptIt(): void
64+
{
65+
if ($this->isClassLocatorSupportedByORM()) {
66+
$this->markTestSkipped('This test is only relevant for versions of doctrine/orm < 3.6.0, which did not support ClassLocator');
67+
}
68+
69+
$driverClass = AttributeDriver::class;
70+
$driverName = 'default_attribute';
71+
$container = $this->createContainer();
72+
$dirs = [__DIR__.'/../Fixtures'];
73+
74+
$locatorServiceId = $this->invokeRegisterMappingClassLocatorService($driverClass, $driverName, $container, $dirs);
75+
76+
$this->assertNull($locatorServiceId);
77+
}
78+
79+
public function testRegisterMappingClassLocatorService(): void
80+
{
81+
if (!$this->isClassLocatorSupportedByPersistence() || !$this->isClassLocatorSupportedByORM()) {
82+
$this->markTestSkipped('This test is only relevant for versions of doctrine/persistence >= 4.1 and doctrine/orm >= 3.6');
83+
}
84+
85+
$driverClass = AttributeDriver::class;
86+
$driverName = 'default_attribute';
87+
$container = $this->createContainer();
88+
$dirs = [__DIR__.'/../Fixtures'];
89+
90+
$locatorServiceId = $this->invokeRegisterMappingClassLocatorService($driverClass, $driverName, $container, $dirs);
91+
92+
$this->assertSame('doctrine.orm.default_attribute_mapping_class_locator', $locatorServiceId);
93+
$classLocator = $container->get($locatorServiceId);
94+
$this->assertInstanceOf(FileClassLocator::class, $classLocator);
95+
96+
$classNames = $classLocator->getClassNames();
97+
$this->assertGreaterThan(1, \count($classNames));
98+
99+
$finderServiceId = 'doctrine.orm.default_attribute_mapping_class_finder';
100+
$finderDefinition = $container->getDefinition($finderServiceId);
101+
102+
$this->assertSame(Finder::class, $finderDefinition->getClass());
103+
$this->assertTrue($finderDefinition->isShared());
104+
$this->assertSame([
105+
['files', []],
106+
['name', ['*.php']],
107+
['in', [$dirs]],
108+
], $finderDefinition->getMethodCalls());
109+
}
110+
58111
public function testFixManagersAutoMappingsWithTwoAutomappings()
59112
{
60113
$emConfigs = [
@@ -334,4 +387,25 @@ protected function createContainer(array $data = [], array $extraBundles = []):
334387
'kernel.project_dir' => __DIR__,
335388
], $data)));
336389
}
390+
391+
/** @param string[] $dirs */
392+
private function invokeRegisterMappingClassLocatorService(string $driverClass, string $driverName, ContainerBuilder $container, array $dirs): ?string
393+
{
394+
$method = new \ReflectionMethod($this->extension, 'registerMappingClassLocatorService');
395+
396+
/** @var string $locatorServiceId */
397+
$locatorServiceId = $method->invoke($this->extension, $driverClass, $driverName, $container, $dirs);
398+
399+
return $locatorServiceId;
400+
}
401+
402+
private function isClassLocatorSupportedByPersistence(): bool
403+
{
404+
return interface_exists(ClassLocator::class);
405+
}
406+
407+
private function isClassLocatorSupportedByORM(): bool
408+
{
409+
return version_compare(InstalledVersions::getVersion('doctrine/orm'), '3.6', '>=');
410+
}
337411
}

0 commit comments

Comments
 (0)