diff --git a/composer.json b/composer.json index 6616ae84c4a..e0483947161 100644 --- a/composer.json +++ b/composer.json @@ -31,13 +31,19 @@ "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", "doctrine/lexer": "^3", - "doctrine/persistence": "^3.3.1 || ^4", "psr/cache": "^1 || ^2 || ^3", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/var-exporter": "^6.3.9 || ^7.0" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/rela589n/doctrine-persistence.git" + } + ], "require-dev": { "doctrine/coding-standard": "^13.0", + "doctrine/persistence": "^3.3.1 || ^4.0@dev", "phpbench/phpbench": "^1.0", "phpdocumentor/guides-cli": "^1.4", "phpstan/extension-installer": "^1.4", diff --git a/docs/en/reference/advanced-configuration.rst b/docs/en/reference/advanced-configuration.rst index d320f436680..c7f160bb4c2 100644 --- a/docs/en/reference/advanced-configuration.rst +++ b/docs/en/reference/advanced-configuration.rst @@ -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); @@ -154,7 +154,7 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``: setMetadataDriverImpl($driverImpl); The path information to the entities is required for the attribute diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 14ac298cd44..b1bd7382f25 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1314,6 +1314,13 @@ parameters: count: 1 path: src/Mapping/DefaultQuoteStrategy.php + - + message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\Driver\\AttributeDriver\:\:\$filePaths\.$#' + identifier: property.notFound # property is available starting from doctrine/persistence 4.1 + count: 1 + path: src/Mapping/Driver/AttributeDriver.php + reportUnmatched: false + - message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\AttributeDriver\:\:isRepeatedPropertyDeclaration\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#' identifier: missingType.generics diff --git a/src/Mapping/Driver/AttributeDriver.php b/src/Mapping/Driver/AttributeDriver.php index 4445ea69437..0f74eabea4c 100644 --- a/src/Mapping/Driver/AttributeDriver.php +++ b/src/Mapping/Driver/AttributeDriver.php @@ -13,13 +13,18 @@ use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use InvalidArgumentException; +use LogicException; use ReflectionClass; use ReflectionMethod; +use Traversable; use function assert; use function class_exists; use function constant; use function defined; +use function is_file; +use function property_exists; +use function reset; use function sprintf; class AttributeDriver implements MappingDriver @@ -35,10 +40,10 @@ class AttributeDriver implements MappingDriver private readonly AttributeReader $reader; /** - * @param array $paths - * @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0 + * @param iterable $paths iterable of source file paths, 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(iterable $paths, bool $reportFieldsWhereDeclared = true) { if (! $reportFieldsWhereDeclared) { throw new InvalidArgumentException(sprintf( @@ -47,8 +52,19 @@ public function __construct(array $paths, bool $reportFieldsWhereDeclared = true )); } + $pathsAreFilePaths = $paths instanceof Traversable || ($paths !== [] && is_file(reset($paths))); + + if ($pathsAreFilePaths) { + if (! property_exists(self::class, 'filePaths')) { + throw new LogicException('Source file paths support for AttributeDriver is available since doctrine/persistence 4.1.'); + } + + $this->filePaths = $paths; + } else { + $this->addPaths($paths); + } + $this->reader = new AttributeReader(); - $this->addPaths($paths); } public function isTransient(string $className): bool diff --git a/src/ORMSetup.php b/src/ORMSetup.php index 458844bb232..a6ef2f20396 100644 --- a/src/ORMSetup.php +++ b/src/ORMSetup.php @@ -28,10 +28,10 @@ final class ORMSetup /** * Creates a configuration with an attribute metadata driver. * - * @param string[] $paths + * @param iterable $paths */ public static function createAttributeMetadataConfiguration( - array $paths, + iterable $paths, bool $isDevMode = false, string|null $proxyDir = null, CacheItemPoolInterface|null $cache = null, @@ -47,7 +47,7 @@ public static function createAttributeMetadataConfiguration( } $config = self::createConfiguration($isDevMode, $proxyDir, $cache); - $config->setMetadataDriverImpl(new AttributeDriver($paths)); + $config->setMetadataDriverImpl(new AttributeDriver($paths, true)); return $config; } @@ -55,16 +55,16 @@ public static function createAttributeMetadataConfiguration( /** * Creates a configuration with an attribute metadata driver. * - * @param string[] $paths + * @param iterable $paths */ public static function createAttributeMetadataConfig( - array $paths, + iterable $paths, bool $isDevMode = false, string|null $cacheNamespaceSeed = null, CacheItemPoolInterface|null $cache = null, ): Configuration { $config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache); - $config->setMetadataDriverImpl(new AttributeDriver($paths)); + $config->setMetadataDriverImpl(new AttributeDriver($paths, true)); return $config; } diff --git a/tests/Performance/EntityManagerFactory.php b/tests/Performance/EntityManagerFactory.php index 45b82405aca..f74a619e4f3 100644 --- a/tests/Performance/EntityManagerFactory.php +++ b/tests/Performance/EntityManagerFactory.php @@ -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 { @@ -30,10 +29,7 @@ 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( DriverManager::getConnection([ @@ -55,11 +51,7 @@ 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 $connection = new class ([], new Driver(), null, new EventManager()) extends Connection diff --git a/tests/Tests/Mocks/AttributeDriverFactory.php b/tests/Tests/Mocks/AttributeDriverFactory.php new file mode 100644 index 00000000000..37bf7a3c879 --- /dev/null +++ b/tests/Tests/Mocks/AttributeDriverFactory.php @@ -0,0 +1,37 @@ + $paths */ + public static function createAttributeDriver(array $paths = []): AttributeDriver + { + if (! self::isFilePathsSupported()) { + return new AttributeDriver($paths, true); + } + + $filePaths = new FilePathNameIterator(new DirectoryFilesIterator($paths)); + + return new AttributeDriver($filePaths, true); + } + + public static function isFilePathsSupported(): bool + { + return version_compare(InstalledVersions::getVersion('doctrine/persistence'), '4.1', '>='); + } + + public static function pathFiles(string $path): FilePathNameIterator + { + return new FilePathNameIterator(new DirectoryFilesIterator([$path])); + } +} diff --git a/tests/Tests/Mocks/EntityManagerMock.php b/tests/Tests/Mocks/EntityManagerMock.php index a594045b3f1..6cc9bf589f9 100644 --- a/tests/Tests/Mocks/EntityManagerMock.php +++ b/tests/Tests/Mocks/EntityManagerMock.php @@ -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; @@ -26,7 +25,8 @@ public function __construct(Connection $conn, Configuration|null $config = null, if ($config === null) { $config = new Configuration(); TestUtil::configureProxies($config); - $config->setMetadataDriverImpl(new AttributeDriver([])); + $attributeDriver = AttributeDriverFactory::createAttributeDriver(); + $config->setMetadataDriverImpl($attributeDriver); } parent::__construct($conn, $config, $eventManager); diff --git a/tests/Tests/ORM/Functional/EnumTest.php b/tests/Tests/ORM/Functional/EnumTest.php index f4384d3788b..ad97693aced 100644 --- a/tests/Tests/ORM/Functional/EnumTest.php +++ b/tests/Tests/ORM/Functional/EnumTest.php @@ -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; @@ -28,7 +28,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use function class_exists; -use function dirname; use function sprintf; use function uniqid; @@ -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) { diff --git a/tests/Tests/ORM/Functional/Locking/LockAgentWorker.php b/tests/Tests/ORM/Functional/Locking/LockAgentWorker.php index bd26efd07b0..542ed4b5435 100644 --- a/tests/Tests/ORM/Functional/Locking/LockAgentWorker.php +++ b/tests/Tests/ORM/Functional/Locking/LockAgentWorker.php @@ -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; @@ -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()); diff --git a/tests/Tests/ORM/Functional/ReadonlyPropertiesTest.php b/tests/Tests/ORM/Functional/ReadonlyPropertiesTest.php index 67fa9ac1c10..e430b03dcf3 100644 --- a/tests/Tests/ORM/Functional/ReadonlyPropertiesTest.php +++ b/tests/Tests/ORM/Functional/ReadonlyPropertiesTest.php @@ -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 @@ -22,10 +20,8 @@ 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(); diff --git a/tests/Tests/ORM/Mapping/AttributeDriverTest.php b/tests/Tests/ORM/Mapping/AttributeDriverTest.php index b16ec788d33..bf99a926077 100644 --- a/tests/Tests/ORM/Mapping/AttributeDriverTest.php +++ b/tests/Tests/ORM/Mapping/AttributeDriverTest.php @@ -11,17 +11,42 @@ use Doctrine\ORM\Mapping\JoinColumnMapping; use Doctrine\ORM\Mapping\MappingAttribute; 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 LogicException; use stdClass; class AttributeDriverTest extends MappingDriverTestCase { protected function loadDriver(): MappingDriver { - $paths = []; + return AttributeDriverFactory::createAttributeDriver(); + } + + public function testDriverCanAcceptListOfFilePaths(): void + { + if (! AttributeDriverFactory::isFilePathsSupported()) { + self::markTestSkipped('This test is only relevant for versions of doctrine/persistence >= 4.1'); + } + + $driver = new AttributeDriver(['1' => __DIR__ . '/../../Models/Cache/City.php']); + + self::assertSame([], $driver->getPaths(), 'Directory paths must be empty, since file paths are used'); + self::assertSame([0 => City::class], $driver->getAllClassNames()); + } + + public function testFilePathsCanBeUsedOnlyStartingFromPersistence41(): void + { + if (AttributeDriverFactory::isFilePathsSupported()) { + self::markTestSkipped('This test is only relevant for versions of doctrine/persistence < 4.1'); + } + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('available since doctrine/persistence 4.1'); - return new AttributeDriver($paths, true); + new AttributeDriver([__DIR__ . '/../../Models/Cache/City.php']); } public function testOriginallyNestedAttributesDeclaredWithoutOriginalParent(): void diff --git a/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php b/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php index fdeaeddb12a..d6ea31310af 100644 --- a/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php +++ b/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php @@ -11,11 +11,14 @@ use Doctrine\ORM\ORMSetup; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\SchemaValidator; +use Doctrine\Tests\Mocks\AttributeDriverFactory; use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Models\BinaryPrimaryKey\BinaryIdType; use Doctrine\Tests\Models\BinaryPrimaryKey\Category; use Doctrine\Tests\OrmTestCase; +use function iterator_to_array; + final class BinaryIdPersisterTest extends OrmTestCase { private EntityManager|null $entityManager = null; @@ -63,7 +66,10 @@ private function createEntityManager(): EntityManager return $this->entityManager; } - $config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true); + $config = ORMSetup::createAttributeMetadataConfiguration( + $this->getDriverPaths(), + isDevMode: true, + ); if (! DbalType::hasType(BinaryIdType::NAME)) { DbalType::addType(BinaryIdType::NAME, BinaryIdType::class); @@ -82,4 +88,16 @@ private function createEntityManager(): EntityManager return $entityManager; } + + /** @return string[] */ + private function getDriverPaths(): iterable + { + $path = __DIR__ . '/../../Models/BinaryPrimaryKey'; + + if (! AttributeDriverFactory::isFilePathsSupported()) { + return [$path]; + } + + return iterator_to_array(AttributeDriverFactory::pathFiles($path)); + } } diff --git a/tests/Tests/ORM/Tools/Console/Command/SchemaTool/CommandTestCase.php b/tests/Tests/ORM/Tools/Console/Command/SchemaTool/CommandTestCase.php index 82279e337b0..05b7ac215a6 100644 --- a/tests/Tests/ORM/Tools/Console/Command/SchemaTool/CommandTestCase.php +++ b/tests/Tests/ORM/Tools/Console/Command/SchemaTool/CommandTestCase.php @@ -5,9 +5,9 @@ namespace Doctrine\Tests\ORM\Tools\Console\Command\SchemaTool; use Doctrine\DBAL\Platforms\SQLitePlatform; -use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Tools\Console\Command\SchemaTool\AbstractCommand; use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider; +use Doctrine\Tests\Mocks\AttributeDriverFactory; use Doctrine\Tests\OrmFunctionalTestCase; use Symfony\Component\Console\Tester\CommandTester; @@ -16,9 +16,7 @@ abstract class CommandTestCase extends OrmFunctionalTestCase /** @param class-string $commandClass */ protected function getCommandTester(string $commandClass, string|null $commandName = null): CommandTester { - $entityManager = $this->getEntityManager(null, new AttributeDriver([ - __DIR__ . '/Models', - ])); + $entityManager = $this->getEntityManager(null, AttributeDriverFactory::createAttributeDriver([__DIR__ . '/Models'])); if (! $entityManager->getConnection()->getDatabasePlatform() instanceof SQLitePlatform) { self::markTestSkipped('We are testing the symfony/console integration'); diff --git a/tests/Tests/OrmFunctionalTestCase.php b/tests/Tests/OrmFunctionalTestCase.php index 67a42d30909..ecf6102695f 100644 --- a/tests/Tests/OrmFunctionalTestCase.php +++ b/tests/Tests/OrmFunctionalTestCase.php @@ -22,7 +22,6 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Tools\DebugUnitOfWorkListener; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\ORM\Tools\ToolsException; @@ -31,6 +30,7 @@ use Doctrine\Tests\DbalExtensions\QueryLog; use Doctrine\Tests\DbalTypes\Rot13Type; use Doctrine\Tests\EventListener\CacheMetadataListener; +use Doctrine\Tests\Mocks\AttributeDriverFactory; use Doctrine\Tests\Models\Cache\Action; use Doctrine\Tests\Models\Cache\Address; use Doctrine\Tests\Models\Cache\Attraction; @@ -187,7 +187,6 @@ use function implode; use function is_object; use function method_exists; -use function realpath; use function sprintf; use function str_contains; use function strtolower; @@ -952,12 +951,12 @@ protected function getEntityManager( $config->enableNativeLazyObjects(true); } - $config->setMetadataDriverImpl( - $mappingDriver ?? new AttributeDriver([ - realpath(__DIR__ . '/Models/Cache'), - realpath(__DIR__ . '/Models/GeoNames'), - ], true), - ); + $mappingDriver ??= AttributeDriverFactory::createAttributeDriver([ + __DIR__ . '/Models/Cache', + __DIR__ . '/Models/GeoNames', + ]); + + $config->setMetadataDriverImpl($mappingDriver); $conn = $connection ?: static::$sharedConn; assert($conn !== null); diff --git a/tests/Tests/OrmTestCase.php b/tests/Tests/OrmTestCase.php index 9349e83d88b..1327ffeb2ce 100644 --- a/tests/Tests/OrmTestCase.php +++ b/tests/Tests/OrmTestCase.php @@ -17,6 +17,7 @@ use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Configuration; use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Doctrine\Tests\Mocks\AttributeDriverFactory; use Doctrine\Tests\Mocks\EntityManagerMock; use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemPoolInterface; @@ -24,7 +25,6 @@ use function enum_exists; use function method_exists; -use function realpath; use function sprintf; /** @@ -56,9 +56,10 @@ abstract class OrmTestCase extends TestCase private CacheItemPoolInterface|null $secondLevelCache = null; + /** @param list $paths */ protected function createAttributeDriver(array $paths = []): AttributeDriver { - return new AttributeDriver($paths); + return AttributeDriverFactory::createAttributeDriver($paths); } /** @@ -97,9 +98,7 @@ private function buildTestEntityManagerWithPlatform(Connection $connection): Ent TestUtil::configureProxies($config); $config->setMetadataCache($metadataCache); $config->setQueryCache(self::getSharedQueryCache()); - $config->setMetadataDriverImpl(new AttributeDriver([ - realpath(__DIR__ . '/Models/Cache'), - ], true)); + $config->setMetadataDriverImpl(AttributeDriverFactory::createAttributeDriver([__DIR__ . '/Models/Cache'])); if ($this->isSecondLevelCacheEnabled) { $cacheConfig = new CacheConfiguration();