Skip to content

Commit ed9ba16

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` (`ClassLocator`) property, which allows passing any instance of `ClassLocator` for the mapping driver to use. This commit integrates those changes into `AttributeDriver`. Since `doctrine/orm` maintains the support for `doctrine/persistence` of older versions, tests ensure that `ClassLocator` actually exists. The old paths' behaviour can be adapted into the new by passing `FileClassLocator` into `AttributeDriver` (see `FileClassLocator::createFromDirectories($directoryPaths)`).
1 parent c1f7a60 commit ed9ba16

File tree

14 files changed

+169
-57
lines changed

14 files changed

+169
-57
lines changed

docs/en/reference/advanced-configuration.rst

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ steps of configuration.
2929
3030
$config = new Configuration;
3131
$config->setMetadataCache($metadataCache);
32-
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
32+
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
3333
$config->setMetadataDriverImpl($driverImpl);
3434
$config->setQueryCache($queryCache);
3535
@@ -154,15 +154,59 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
154154
<?php
155155
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
156156
157-
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
157+
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
158158
$config->setMetadataDriverImpl($driverImpl);
159159
160160
The path information to the entities is required for the attribute
161161
driver, because otherwise mass-operations on all entities through
162-
the console could not work correctly. All of metadata drivers
163-
accept either a single directory as a string or an array of
164-
directories. With this feature a single driver can support multiple
165-
directories of Entities.
162+
the console could not work correctly. Metadata drivers can accept either
163+
a single directory as a string or an array of directories.
164+
165+
AttributeDriver also accepts ``Doctrine\Persistence\Mapping\Driver\ClassLocator``,
166+
allowing one to customize file discovery logic. You may choose to use Symfony Finder, or
167+
utilize directory scan with ``FileClassLocator::createFromDirectories()``:
168+
169+
.. code-block:: php
170+
171+
<?php
172+
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
173+
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
174+
175+
$paths = ['/path/to/lib/MyProject/Entities'];
176+
$classLocator = FileClassLocator::createFromDirectories($paths);
177+
178+
$driverImpl = new AttributeDriver($classLocator);
179+
$config->setMetadataDriverImpl($driverImpl);
180+
181+
With this feature, you're empowered to provide a fine-grained iterator of only necessary
182+
files to the Driver. For example, if you are using Vertical Slice architecture, you can
183+
exclude ``*Test.php``, ``*Controller.php``, ``*Service.php``, etc.:
184+
185+
.. code-block:: php
186+
187+
<?php
188+
use Symfony\Component\Finder\Finder;
189+
190+
$finder = new Finder()->files()->in($paths)
191+
->name('*.php')
192+
->notName(['*Test.php', '*Controller.php', '*Service.php']);
193+
194+
$classLocator = new FileClassLocator($finder);
195+
196+
If you know the list of class names you want to track, use
197+
``Doctrine\Persistence\Mapping\Driver\ClassNames``:
198+
199+
.. code-block:: php
200+
201+
<?php
202+
use Doctrine\Persistence\Mapping\Driver\ClassNames;
203+
use App\Entity\{Article, Book};
204+
205+
$entityClasses = [Article::class, Book::class];
206+
$classLocator = new ClassNames($entityClasses);
207+
208+
$driverImpl = new AttributeDriver($classLocator);
209+
$config->setMetadataDriverImpl($driverImpl);
166210
167211
Metadata Cache (**RECOMMENDED**)
168212
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/Mapping/Driver/AttributeDriver.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Doctrine\ORM\Mapping\ClassMetadata;
1111
use Doctrine\ORM\Mapping\MappingException;
1212
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
13+
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
1314
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
1415
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
1516
use InvalidArgumentException;
@@ -35,10 +36,10 @@ class AttributeDriver implements MappingDriver
3536
private readonly AttributeReader $reader;
3637

3738
/**
38-
* @param array<string> $paths
39-
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
39+
* @param string[]|ClassLocator $paths a ClassLocator, or an array of directories.
40+
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
4041
*/
41-
public function __construct(array $paths, bool $reportFieldsWhereDeclared = true)
42+
public function __construct(array|ClassLocator $paths, bool $reportFieldsWhereDeclared = true)
4243
{
4344
if (! $reportFieldsWhereDeclared) {
4445
throw new InvalidArgumentException(sprintf(
@@ -48,7 +49,12 @@ public function __construct(array $paths, bool $reportFieldsWhereDeclared = true
4849
}
4950

5051
$this->reader = new AttributeReader();
51-
$this->addPaths($paths);
52+
53+
if ($paths instanceof ClassLocator) {
54+
$this->classLocator = $paths;
55+
} else {
56+
$this->addPaths($paths);
57+
}
5258
}
5359

5460
public function isTransient(string $className): bool

src/ORMSetup.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\Deprecations\Deprecation;
88
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
99
use Doctrine\ORM\Mapping\Driver\XmlDriver;
10+
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
1011
use Psr\Cache\CacheItemPoolInterface;
1112
use Redis;
1213
use RuntimeException;
@@ -28,10 +29,10 @@ final class ORMSetup
2829
/**
2930
* Creates a configuration with an attribute metadata driver.
3031
*
31-
* @param string[] $paths
32+
* @param string[]|ClassLocator $paths
3233
*/
3334
public static function createAttributeMetadataConfiguration(
34-
array $paths,
35+
array|ClassLocator $paths,
3536
bool $isDevMode = false,
3637
string|null $proxyDir = null,
3738
CacheItemPoolInterface|null $cache = null,
@@ -55,10 +56,10 @@ public static function createAttributeMetadataConfiguration(
5556
/**
5657
* Creates a configuration with an attribute metadata driver.
5758
*
58-
* @param string[] $paths
59+
* @param string[]|ClassLocator $paths
5960
*/
6061
public static function createAttributeMetadataConfig(
61-
array $paths,
62+
array|ClassLocator $paths,
6263
bool $isDevMode = false,
6364
string|null $cacheNamespaceSeed = null,
6465
CacheItemPoolInterface|null $cache = null,

tests/Performance/EntityManagerFactory.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@
1313
use Doctrine\ORM\Configuration;
1414
use Doctrine\ORM\EntityManager;
1515
use Doctrine\ORM\EntityManagerInterface;
16-
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
1716
use Doctrine\ORM\Proxy\ProxyFactory;
1817
use Doctrine\ORM\Tools\SchemaTool;
1918
use Doctrine\Tests\Mocks\ArrayResultFactory;
19+
use Doctrine\Tests\Mocks\AttributeDriverFactory;
2020
use Doctrine\Tests\TestUtil;
2121

2222
use function array_map;
23-
use function realpath;
2423

2524
final class EntityManagerFactory
2625
{
@@ -30,9 +29,9 @@ public static function getEntityManager(array $schemaClassNames): EntityManagerI
3029

3130
TestUtil::configureProxies($config);
3231
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
33-
$config->setMetadataDriverImpl(new AttributeDriver([
34-
realpath(__DIR__ . '/Models/Cache'),
35-
realpath(__DIR__ . '/Models/GeoNames'),
32+
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([
33+
__DIR__ . '/../Tests/Models/Cache',
34+
__DIR__ . '/../Tests/Models/GeoNames',
3635
]));
3736

3837
$entityManager = new EntityManager(
@@ -55,10 +54,10 @@ public static function makeEntityManagerWithNoResultsConnection(): EntityManager
5554

5655
TestUtil::configureProxies($config);
5756
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
58-
$config->setMetadataDriverImpl(new AttributeDriver([
59-
realpath(__DIR__ . '/Models/Cache'),
60-
realpath(__DIR__ . '/Models/Generic'),
61-
realpath(__DIR__ . '/Models/GeoNames'),
57+
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([
58+
__DIR__ . '/../Tests/Models/Cache',
59+
__DIR__ . '/../Tests/Models/Generic',
60+
__DIR__ . '/../Tests/Models/GeoNames',
6261
]));
6362

6463
// A connection that doesn't really do anything
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Mocks;
6+
7+
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
8+
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
9+
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
10+
11+
use function interface_exists;
12+
13+
final class AttributeDriverFactory
14+
{
15+
/** @param list<string> $paths */
16+
public static function createAttributeDriver(array $paths = []): AttributeDriver
17+
{
18+
if (! self::isClassLocatorSupported()) {
19+
// Persistence < 4.1
20+
return new AttributeDriver($paths);
21+
}
22+
23+
// Persistence >= 4.1
24+
$classLocator = FileClassLocator::createFromDirectories($paths);
25+
26+
return new AttributeDriver($classLocator);
27+
}
28+
29+
/** Supported since doctrine/persistence >= 4.1 */
30+
public static function isClassLocatorSupported(): bool
31+
{
32+
return interface_exists(ClassLocator::class);
33+
}
34+
}

tests/Tests/Mocks/EntityManagerMock.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Doctrine\DBAL\Connection;
99
use Doctrine\ORM\Configuration;
1010
use Doctrine\ORM\EntityManager;
11-
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
1211
use Doctrine\ORM\Proxy\ProxyFactory;
1312
use Doctrine\ORM\UnitOfWork;
1413
use Doctrine\Tests\TestUtil;
@@ -26,7 +25,7 @@ public function __construct(Connection $conn, Configuration|null $config = null,
2625
if ($config === null) {
2726
$config = new Configuration();
2827
TestUtil::configureProxies($config);
29-
$config->setMetadataDriverImpl(new AttributeDriver([]));
28+
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver());
3029
}
3130

3231
parent::__construct($conn, $config, $eventManager);

tests/Tests/ORM/Functional/EnumTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
use Doctrine\DBAL\Types\EnumType;
88
use Doctrine\ORM\AbstractQuery;
99
use Doctrine\ORM\Mapping\Column;
10-
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
1110
use Doctrine\ORM\Mapping\MappingException;
1211
use Doctrine\ORM\Query\Expr\Func;
1312
use Doctrine\ORM\Tools\SchemaTool;
13+
use Doctrine\Tests\Mocks\AttributeDriverFactory;
1414
use Doctrine\Tests\Models\DataTransferObjects\DtoWithArrayOfEnums;
1515
use Doctrine\Tests\Models\DataTransferObjects\DtoWithEnum;
1616
use Doctrine\Tests\Models\Enums\Card;
@@ -28,7 +28,6 @@
2828
use PHPUnit\Framework\Attributes\DataProvider;
2929

3030
use function class_exists;
31-
use function dirname;
3231
use function sprintf;
3332
use function uniqid;
3433

@@ -38,7 +37,9 @@ public function setUp(): void
3837
{
3938
parent::setUp();
4039

41-
$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums'], true));
40+
$mappingDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../Models/Enums']);
41+
42+
$this->_em = $this->getEntityManager(null, $mappingDriver);
4243
$this->_schemaTool = new SchemaTool($this->_em);
4344

4445
if ($this->isSecondLevelCacheEnabled) {

tests/Tests/ORM/Functional/Locking/LockAgentWorker.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Doctrine\ORM\Configuration;
1010
use Doctrine\ORM\EntityManager;
1111
use Doctrine\ORM\EntityManagerInterface;
12+
use Doctrine\Tests\Mocks\AttributeDriverFactory;
1213
use Doctrine\Tests\ORM\Functional\Locking\Doctrine\ORM\Query;
1314
use Doctrine\Tests\TestUtil;
1415
use GearmanWorker;
@@ -116,8 +117,8 @@ protected function createEntityManager(Connection $conn): EntityManagerInterface
116117
TestUtil::configureProxies($config);
117118
$config->setAutoGenerateProxyClasses(true);
118119

119-
$annotDriver = new AttributeDriver([__DIR__ . '/../../../Models/']);
120-
$config->setMetadataDriverImpl($annotDriver);
120+
$attributeDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../../Models']);
121+
$config->setMetadataDriverImpl($attributeDriver);
121122
$config->setMetadataCache(new ArrayAdapter());
122123

123124
$config->setQueryCache(new ArrayAdapter());

tests/Tests/ORM/Functional/ReadonlyPropertiesTest.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@
44

55
namespace Doctrine\Tests\ORM\Functional;
66

7-
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
87
use Doctrine\ORM\Tools\SchemaTool;
8+
use Doctrine\Tests\Mocks\AttributeDriverFactory;
99
use Doctrine\Tests\Models\ReadonlyProperties\Author;
1010
use Doctrine\Tests\Models\ReadonlyProperties\Book;
1111
use Doctrine\Tests\Models\ReadonlyProperties\SimpleBook;
1212
use Doctrine\Tests\OrmFunctionalTestCase;
1313
use Doctrine\Tests\TestUtil;
1414

15-
use function dirname;
16-
1715
class ReadonlyPropertiesTest extends OrmFunctionalTestCase
1816
{
1917
protected function setUp(): void
@@ -22,10 +20,9 @@ protected function setUp(): void
2220
static::$sharedConn = TestUtil::getConnection();
2321
}
2422

25-
$this->_em = $this->getEntityManager(null, new AttributeDriver(
26-
[dirname(__DIR__, 2) . '/Models/ReadonlyProperties'],
27-
true,
28-
));
23+
$attributeDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../Models/ReadonlyProperties']);
24+
25+
$this->_em = $this->getEntityManager(null, $attributeDriver);
2926
$this->_schemaTool = new SchemaTool($this->_em);
3027

3128
parent::setUp();

tests/Tests/ORM/Mapping/AttributeDriverTest.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
1111
use Doctrine\ORM\Mapping\JoinColumnMapping;
1212
use Doctrine\ORM\Mapping\MappingAttribute;
13+
use Doctrine\Persistence\Mapping\Driver\ClassNames;
1314
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
15+
use Doctrine\Tests\Mocks\AttributeDriverFactory;
16+
use Doctrine\Tests\Models\Cache\City;
1417
use Doctrine\Tests\ORM\Mapping\Fixtures\AttributeEntityWithNestedJoinColumns;
1518
use InvalidArgumentException;
1619
use stdClass;
@@ -19,9 +22,21 @@ class AttributeDriverTest extends MappingDriverTestCase
1922
{
2023
protected function loadDriver(): MappingDriver
2124
{
22-
$paths = [];
25+
return AttributeDriverFactory::createAttributeDriver();
26+
}
27+
28+
public function testDriverCanAcceptClassLocator(): void
29+
{
30+
if (! AttributeDriverFactory::isClassLocatorSupported()) {
31+
self::markTestSkipped('This test is only relevant for versions of doctrine/persistence >= 4.1');
32+
}
33+
34+
$classLocator = new ClassNames([City::class]);
35+
36+
$driver = new AttributeDriver($classLocator);
2337

24-
return new AttributeDriver($paths, true);
38+
self::assertSame([], $driver->getPaths(), 'Directory paths must be empty, since file paths are used');
39+
self::assertSame([City::class], $driver->getAllClassNames());
2540
}
2641

2742
public function testOriginallyNestedAttributesDeclaredWithoutOriginalParent(): void

0 commit comments

Comments
 (0)