Skip to content
Merged
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
56 changes: 50 additions & 6 deletions docs/en/reference/advanced-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ steps of configuration.

$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);

Expand Down Expand Up @@ -154,15 +154,59 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;

$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);

The path information to the entities is required for the attribute
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
the console could not work correctly. Metadata drivers can accept either
a single directory as a string or an array of directories.

AttributeDriver also accepts ``Doctrine\Persistence\Mapping\Driver\ClassLocator``,
allowing one to customize file discovery logic. You may choose to use Symfony Finder, or
utilize directory scan with ``FileClassLocator::createFromDirectories()``:

.. code-block:: php

<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;

$paths = ['/path/to/lib/MyProject/Entities'];
$classLocator = FileClassLocator::createFromDirectories($paths);

$driverImpl = new AttributeDriver($classLocator);
$config->setMetadataDriverImpl($driverImpl);

With this feature, you're empowered to provide a fine-grained iterator of only necessary
files to the Driver. For example, if you are using Vertical Slice architecture, you can
exclude ``*Test.php``, ``*Controller.php``, ``*Service.php``, etc.:

.. code-block:: php

<?php
use Symfony\Component\Finder\Finder;

$finder = new Finder()->files()->in($paths)
->name('*.php')
->notName(['*Test.php', '*Controller.php', '*Service.php']);

$classLocator = new FileClassLocator($finder);

If you know the list of class names you want to track, use
``Doctrine\Persistence\Mapping\Driver\ClassNames``:

.. code-block:: php

<?php
use Doctrine\Persistence\Mapping\Driver\ClassNames;
use App\Entity\{Article, Book};

$entityClasses = [Article::class, Book::class];
$classLocator = new ClassNames($entityClasses);

$driverImpl = new AttributeDriver($classLocator);
$config->setMetadataDriverImpl($driverImpl);

Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
14 changes: 10 additions & 4 deletions src/Mapping/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use InvalidArgumentException;
Expand All @@ -35,10 +36,10 @@ class AttributeDriver implements MappingDriver
private readonly AttributeReader $reader;

/**
* @param array<string> $paths
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
* @param string[]|ClassLocator $paths a ClassLocator, or an array of directories.
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
*/
public function __construct(array $paths, bool $reportFieldsWhereDeclared = true)
public function __construct(array|ClassLocator $paths, bool $reportFieldsWhereDeclared = true)
{
if (! $reportFieldsWhereDeclared) {
throw new InvalidArgumentException(sprintf(
Expand All @@ -48,7 +49,12 @@ public function __construct(array $paths, bool $reportFieldsWhereDeclared = true
}

$this->reader = new AttributeReader();
$this->addPaths($paths);

if ($paths instanceof ClassLocator) {
$this->classLocator = $paths;
} else {
$this->addPaths($paths);
}
}

public function isTransient(string $className): bool
Expand Down
9 changes: 5 additions & 4 deletions src/ORMSetup.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Psr\Cache\CacheItemPoolInterface;
use Redis;
use RuntimeException;
Expand All @@ -28,10 +29,10 @@ final class ORMSetup
/**
* Creates a configuration with an attribute metadata driver.
*
* @param string[] $paths
* @param string[]|ClassLocator $paths
*/
public static function createAttributeMetadataConfiguration(
array $paths,
array|ClassLocator $paths,
bool $isDevMode = false,
string|null $proxyDir = null,
CacheItemPoolInterface|null $cache = null,
Expand All @@ -55,10 +56,10 @@ public static function createAttributeMetadataConfiguration(
/**
* Creates a configuration with an attribute metadata driver.
*
* @param string[] $paths
* @param string[]|ClassLocator $paths
*/
public static function createAttributeMetadataConfig(
array $paths,
array|ClassLocator $paths,
bool $isDevMode = false,
string|null $cacheNamespaceSeed = null,
CacheItemPoolInterface|null $cache = null,
Expand Down
17 changes: 8 additions & 9 deletions tests/Performance/EntityManagerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\TestUtil;

use function array_map;
use function realpath;

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

TestUtil::configureProxies($config);
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
$config->setMetadataDriverImpl(new AttributeDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/GeoNames'),
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([
__DIR__ . '/../Tests/Models/Cache',
__DIR__ . '/../Tests/Models/GeoNames',
]));

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

TestUtil::configureProxies($config);
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
$config->setMetadataDriverImpl(new AttributeDriver([
realpath(__DIR__ . '/Models/Cache'),
realpath(__DIR__ . '/Models/Generic'),
realpath(__DIR__ . '/Models/GeoNames'),
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([
__DIR__ . '/../Tests/Models/Cache',
__DIR__ . '/../Tests/Models/Generic',
__DIR__ . '/../Tests/Models/GeoNames',
]));

// A connection that doesn't really do anything
Expand Down
34 changes: 34 additions & 0 deletions tests/Tests/Mocks/AttributeDriverFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Mocks;

use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;

use function interface_exists;

final class AttributeDriverFactory
{
/** @param list<string> $paths */
public static function createAttributeDriver(array $paths = []): AttributeDriver
{
if (! self::isClassLocatorSupported()) {
// Persistence < 4.1
return new AttributeDriver($paths);
}

// Persistence >= 4.1
$classLocator = FileClassLocator::createFromDirectories($paths);

return new AttributeDriver($classLocator);
}

/** Supported since doctrine/persistence >= 4.1 */
public static function isClassLocatorSupported(): bool
{
return interface_exists(ClassLocator::class);
}
}
3 changes: 1 addition & 2 deletions tests/Tests/Mocks/EntityManagerMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\TestUtil;
Expand All @@ -26,7 +25,7 @@ public function __construct(Connection $conn, Configuration|null $config = null,
if ($config === null) {
$config = new Configuration();
TestUtil::configureProxies($config);
$config->setMetadataDriverImpl(new AttributeDriver([]));
$config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver());
}

parent::__construct($conn, $config, $eventManager);
Expand Down
7 changes: 4 additions & 3 deletions tests/Tests/ORM/Functional/EnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
use Doctrine\DBAL\Types\EnumType;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Query\Expr\Func;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\DataTransferObjects\DtoWithArrayOfEnums;
use Doctrine\Tests\Models\DataTransferObjects\DtoWithEnum;
use Doctrine\Tests\Models\Enums\Card;
Expand All @@ -28,7 +28,6 @@
use PHPUnit\Framework\Attributes\DataProvider;

use function class_exists;
use function dirname;
use function sprintf;
use function uniqid;

Expand All @@ -38,7 +37,9 @@ public function setUp(): void
{
parent::setUp();

$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums'], true));
$mappingDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../Models/Enums']);

$this->_em = $this->getEntityManager(null, $mappingDriver);
$this->_schemaTool = new SchemaTool($this->_em);

if ($this->isSecondLevelCacheEnabled) {
Expand Down
5 changes: 3 additions & 2 deletions tests/Tests/ORM/Functional/Locking/LockAgentWorker.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\ORM\Functional\Locking\Doctrine\ORM\Query;
use Doctrine\Tests\TestUtil;
use GearmanWorker;
Expand Down Expand Up @@ -116,8 +117,8 @@ protected function createEntityManager(Connection $conn): EntityManagerInterface
TestUtil::configureProxies($config);
$config->setAutoGenerateProxyClasses(true);

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

$config->setQueryCache(new ArrayAdapter());
Expand Down
11 changes: 4 additions & 7 deletions tests/Tests/ORM/Functional/ReadonlyPropertiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@

namespace Doctrine\Tests\ORM\Functional;

use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\ReadonlyProperties\Author;
use Doctrine\Tests\Models\ReadonlyProperties\Book;
use Doctrine\Tests\Models\ReadonlyProperties\SimpleBook;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\TestUtil;

use function dirname;

class ReadonlyPropertiesTest extends OrmFunctionalTestCase
{
protected function setUp(): void
Expand All @@ -22,10 +20,9 @@ protected function setUp(): void
static::$sharedConn = TestUtil::getConnection();
}

$this->_em = $this->getEntityManager(null, new AttributeDriver(
[dirname(__DIR__, 2) . '/Models/ReadonlyProperties'],
true,
));
$attributeDriver = AttributeDriverFactory::createAttributeDriver([__DIR__ . '/../../Models/ReadonlyProperties']);

$this->_em = $this->getEntityManager(null, $attributeDriver);
$this->_schemaTool = new SchemaTool($this->_em);

parent::setUp();
Expand Down
19 changes: 17 additions & 2 deletions tests/Tests/ORM/Mapping/AttributeDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\JoinColumnMapping;
use Doctrine\ORM\Mapping\MappingAttribute;
use Doctrine\Persistence\Mapping\Driver\ClassNames;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\ORM\Mapping\Fixtures\AttributeEntityWithNestedJoinColumns;
use InvalidArgumentException;
use stdClass;
Expand All @@ -19,9 +22,21 @@ class AttributeDriverTest extends MappingDriverTestCase
{
protected function loadDriver(): MappingDriver
{
$paths = [];
return AttributeDriverFactory::createAttributeDriver();
}

public function testDriverCanAcceptClassLocator(): void
{
if (! AttributeDriverFactory::isClassLocatorSupported()) {
self::markTestSkipped('This test is only relevant for versions of doctrine/persistence >= 4.1');
}

$classLocator = new ClassNames([City::class]);

$driver = new AttributeDriver($classLocator);

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

public function testOriginallyNestedAttributesDeclaredWithoutOriginalParent(): void
Expand Down
20 changes: 19 additions & 1 deletion tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\SchemaValidator;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
use Doctrine\Tests\Mocks\AttributeDriverFactory;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Models\BinaryPrimaryKey\BinaryIdType;
use Doctrine\Tests\Models\BinaryPrimaryKey\Category;
Expand Down Expand Up @@ -65,7 +68,10 @@ private function createEntityManager(): EntityManager
return $this->entityManager;
}

$config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true);
$config = ORMSetup::createAttributeMetadataConfiguration(
$this->getClassLocator(),
isDevMode: true,
);
$config->enableNativeLazyObjects(PHP_VERSION_ID >= 80400);

if (! DbalType::hasType(BinaryIdType::NAME)) {
Expand All @@ -85,4 +91,16 @@ private function createEntityManager(): EntityManager

return $entityManager;
}

/** @return list<string>|ClassLocator */
private function getClassLocator(): array|ClassLocator
{
$paths = [__DIR__ . '/../../Models/BinaryPrimaryKey'];

if (! AttributeDriverFactory::isClassLocatorSupported()) {
return $paths;
}

return FileClassLocator::createFromDirectories($paths);
}
}
Loading