Skip to content
Closed
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
"async-aws/sns": "^1.0",
"cache/integration-tests": "dev-master",
"doctrine/collections": "^1.8|^2.0",
"doctrine/data-fixtures": "^1.1",
"doctrine/data-fixtures": "^1.1|^2.0",
"doctrine/dbal": "^3.6|^4",
"doctrine/orm": "^2.15|^3",
"dragonmantank/cron-expression": "^3.1",
Expand Down
2 changes: 2 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
<referencedClass name="Random\*"/>
<!-- These classes have been added in PHP 8.4 -->
<referencedClass name="BcMath\Number"/>
<!-- This class is available from doctrine/persistence >= 4.1 -->
<referencedClass name="Doctrine\Persistence\Mapping\Driver\FileClassLocator"/>
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bridge/Doctrine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
---

* Deprecate `UniqueEntity::getRequiredOptions()` and `UniqueEntity::getDefaultOption()`
* Add support for `ClassLocator` from `doctrine/persistence` >= 4.1

7.3
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@

namespace Symfony\Bridge\Doctrine\DependencyInjection;

use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;

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

/**
* @var array<string,array<string,string>> An array of directory paths by namespace, indexed by driver type.
*
* Used inside metadata driver method to simplify aggregation of data.
*/
protected array $drivers = [];
Expand Down Expand Up @@ -75,8 +83,11 @@ protected function loadMappingInformation(array $objectManager, ContainerBuilder
$bundleMetadata = null;
foreach ($container->getParameter('kernel.bundles') as $name => $class) {
if ($mappingName === $name) {
/** @var array<string,array> $bundlesMetadata */
$bundlesMetadata = $container->getParameter('kernel.bundles_metadata');

$bundle = new \ReflectionClass($class);
$bundleMetadata = $container->getParameter('kernel.bundles_metadata')[$name];
$bundleMetadata = $bundlesMetadata[$name];

break;
}
Expand Down Expand Up @@ -182,7 +193,8 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder
}

foreach ($this->drivers as $driverType => $driverPaths) {
$mappingService = $this->getObjectManagerElementName($objectManager['name'].'_'.$driverType.'_metadata_driver');
$driverName = $objectManager['name'].'_'.$driverType;
$mappingService = $this->getObjectManagerElementName($driverName.'_metadata_driver');
if ($container->hasDefinition($mappingService)) {
$mappingDriverDef = $container->getDefinition($mappingService);
$args = $mappingDriverDef->getArguments();
Expand All @@ -200,6 +212,19 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder
$mappingDriverDef->addMethodCall('setGlobalBasename', ['mapping']);
}

if ('attribute' === $driverType) {
$driverClass = $mappingDriverDef->getClass();

/** @var string[] $directoryPaths */
$directoryPaths = $mappingDriverDef->getArgument(0);

$classLocator = $this->registerMappingClassLocatorService($driverClass, $driverName, $container, $directoryPaths);

if (null !== $classLocator) {
$mappingDriverDef->replaceArgument(0, new Reference($classLocator));
}
}

$container->setDefinition($mappingService, $mappingDriverDef);

foreach ($driverPaths as $prefix => $driverPath) {
Expand All @@ -210,6 +235,61 @@ protected function registerMappingDrivers(array $objectManager, ContainerBuilder
$container->setDefinition($this->getObjectManagerElementName($objectManager['name'].'_metadata_driver'), $chainDriverDef);
}

/**
* @param class-string<MappingDriver> $driverClass
* @param string[] $dirs
*
* @return ?string service id, or null if not available
*/
private function registerMappingClassLocatorService(string $driverClass, string $driverName, ContainerBuilder $container, array $dirs): ?string
{
// Available since doctrine/persistence >= 4.1
if (!interface_exists(ClassLocator::class)) {
return null;
}

$parameter = new \ReflectionParameter([$driverClass, '__construct'], 0);

$parameterType = TypeResolver::create()->resolve($parameter);

// It's possible that doctrine/persistence:^4.1 is installed with the older versions of ORM/ODM.
// In this case it's necessary to check for actual driver support.
if (!$parameterType->isIdentifiedBy(ClassLocator::class)) {
return null;
}

$classLocator = $this->getObjectManagerElementName($driverName.'_mapping_class_locator');

$locatorDefinition = new Definition(
FileClassLocator::class,
[new Reference($this->registerMappingClassFinderService($driverName, $container, $dirs))],
);

$container->setDefinition($classLocator, $locatorDefinition);

return $classLocator;
}

/** @param string[] $dirs */
private function registerMappingClassFinderService(string $driverName, ContainerBuilder $container, array $dirs): string
{
$finderService = $this->getObjectManagerElementName($driverName.'_mapping_class_finder');

if ($container->hasDefinition($finderService)) {
$finderDefinition = $container->getDefinition($finderService);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the service has already be declared in the application, it should not be modified. Otherwise I fail to see the purpose of customizing this service.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's necessary to make in($dirs) call to give context of where to look for the files.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If in([$dirs]) is forced, there's no real freedom to configure the finder. You have to mix the bundle configuration and your custom finder definition.

Instead of creating a service definition named $driverName.'_mapping_class_finder', I suggest you directly inject a definition as an argument to $driverName.'_mapping_class_locator', it is then necessary to define a full ClassLocator service, or replace its first argument if necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wait for your changes; maybe this PR won't be necessary at all

} else {
$finderDefinition = new Definition(Finder::class, []);
}

$finderDefinition->addMethodCall('files');
$finderDefinition->addMethodCall('name', ['*.php']);
$finderDefinition->addMethodCall('in', [$dirs]);

$container->setDefinition($finderService, $finderDefinition);

return $finderService;
}

/**
* Assertion if the specified mapping information is valid.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@

namespace Symfony\Bridge\Doctrine\Tests\DependencyInjection;

use Composer\InstalledVersions;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;

/**
Expand Down Expand Up @@ -55,6 +60,54 @@ protected function setUp(): void
->willReturn('orm');
}

public function testMappingClassLocatorIsNotRegisteredWhenDriversConstructorDoesntAcceptIt()
{
if ($this->isClassLocatorSupportedByORM()) {
$this->markTestSkipped('This test is only relevant for versions of doctrine/orm < 3.6.0, which did not support ClassLocator');
}

$driverClass = AttributeDriver::class;
$driverName = 'default_attribute';
$container = $this->createContainer();
$dirs = [__DIR__.'/../Fixtures'];

$locatorServiceId = $this->invokeRegisterMappingClassLocatorService($driverClass, $driverName, $container, $dirs);

$this->assertNull($locatorServiceId);
}

public function testRegisterMappingClassLocatorService()
{
if (!$this->isClassLocatorSupportedByPersistence() || !$this->isClassLocatorSupportedByORM()) {
$this->markTestSkipped('This test is only relevant for versions of doctrine/persistence >= 4.1 and doctrine/orm >= 3.6');
}

$driverClass = AttributeDriver::class;
$driverName = 'default_attribute';
$container = $this->createContainer();
$dirs = [__DIR__.'/../Fixtures'];

$locatorServiceId = $this->invokeRegisterMappingClassLocatorService($driverClass, $driverName, $container, $dirs);

$this->assertSame('doctrine.orm.default_attribute_mapping_class_locator', $locatorServiceId);
$classLocator = $container->get($locatorServiceId);
$this->assertInstanceOf(FileClassLocator::class, $classLocator);

$classNames = $classLocator->getClassNames();
$this->assertGreaterThan(1, \count($classNames));

$finderServiceId = 'doctrine.orm.default_attribute_mapping_class_finder';
$finderDefinition = $container->getDefinition($finderServiceId);

$this->assertSame(Finder::class, $finderDefinition->getClass());
$this->assertTrue($finderDefinition->isShared());
$this->assertSame([
['files', []],
['name', ['*.php']],
['in', [$dirs]],
], $finderDefinition->getMethodCalls());
}

public function testFixManagersAutoMappingsWithTwoAutomappings()
{
$emConfigs = [
Expand Down Expand Up @@ -334,4 +387,25 @@ protected function createContainer(array $data = [], array $extraBundles = []):
'kernel.project_dir' => __DIR__,
], $data)));
}

/** @param string[] $dirs */
private function invokeRegisterMappingClassLocatorService(string $driverClass, string $driverName, ContainerBuilder $container, array $dirs): ?string
{
$method = new \ReflectionMethod($this->extension, 'registerMappingClassLocatorService');

/** @var string $locatorServiceId */
$locatorServiceId = $method->invoke($this->extension, $driverClass, $driverName, $container, $dirs);

return $locatorServiceId;
}

private function isClassLocatorSupportedByPersistence(): bool
{
return interface_exists(ClassLocator::class);
}

private function isClassLocatorSupportedByORM(): bool
{
return version_compare(InstalledVersions::getVersion('doctrine/orm'), '3.6', '>=');
}
}
Loading