diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 0d832086f31..aeb85af73ed 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -43,26 +43,38 @@ jobs: - "pdo_sqlite" deps: - "highest" + stability: + - "stable" native_lazy: - "0" include: - php-version: "8.2" dbal-version: "4@dev" extension: "pdo_sqlite" + stability: "stable" native_lazy: "0" - php-version: "8.2" dbal-version: "4@dev" extension: "sqlite3" + stability: "stable" native_lazy: "0" - php-version: "8.1" dbal-version: "default" deps: "lowest" extension: "pdo_sqlite" + stability: "stable" native_lazy: "0" - php-version: "8.4" dbal-version: "default" deps: "highest" extension: "pdo_sqlite" + stability: "stable" + native_lazy: "1" + - php-version: "8.4" + dbal-version: "default" + deps: "highest" + extension: "sqlite3" + stability: "dev" native_lazy: "1" steps: @@ -79,6 +91,14 @@ jobs: coverage: "pcov" ini-values: "zend.assertions=1, apc.enable_cli=1" + - name: "Allow dev dependencies" + run: | + composer config minimum-stability dev + composer remove --no-update --dev phpbench/phpbench phpdocumentor/guides-cli + composer require --no-update symfony/console:^8 symfony/var-exporter:^8 doctrine/dbal:^4.4 + composer require --dev --no-update symfony/cache:^8 + if: "${{ matrix.stability == 'dev' }}" + - name: "Require specific DBAL version" run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update" if: "${{ matrix.dbal-version != 'default' }}" diff --git a/phpstan-dbal3.neon b/phpstan-dbal3.neon index 7d22f4b0bf5..c07559f56d3 100644 --- a/phpstan-dbal3.neon +++ b/phpstan-dbal3.neon @@ -137,3 +137,8 @@ parameters: - message: '~inferType.*never returns~' path: src/Query/ParameterTypeInferer.php + + # Compatibility with Symfony 8 + - + message: '#^Call to function method_exists\(\) with ''Symfony\\\\Component\\\\VarExporter\\\\ProxyHelper'' and ''generateLazyGhost'' will always evaluate to true\.$#' + path: src/Proxy/ProxyFactory.php diff --git a/phpstan.neon b/phpstan.neon index c1d69d8b45a..e0aecc9b1bc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -50,3 +50,8 @@ parameters: - message: '#Expression on left side of \?\? is not nullable.#' path: src/Mapping/Driver/AttributeDriver.php + + # Compatibility with Symfony 8 + - + message: '#^Call to function method_exists\(\) with ''Symfony\\\\Component\\\\VarExporter\\\\ProxyHelper'' and ''generateLazyGhost'' will always evaluate to true\.$#' + path: src/Proxy/ProxyFactory.php diff --git a/src/ORMInvalidArgumentException.php b/src/ORMInvalidArgumentException.php index acf13936191..144b93913f8 100644 --- a/src/ORMInvalidArgumentException.php +++ b/src/ORMInvalidArgumentException.php @@ -160,6 +160,11 @@ public static function proxyDirectoryRequired(): self return new self('You must configure a proxy directory. See docs for details'); } + public static function lazyGhostUnavailable(): self + { + return new self('Symfony LazyGhost is not available. Please install the "symfony/var-exporter" package version 6.4 or 7 to use this feature or enable PHP 8.4 native lazy objects.'); + } + public static function proxyNamespaceRequired(): self { return new self('You must configure a proxy namespace'); diff --git a/src/Proxy/ProxyFactory.php b/src/Proxy/ProxyFactory.php index 4518dfc187d..7118811d277 100644 --- a/src/Proxy/ProxyFactory.php +++ b/src/Proxy/ProxyFactory.php @@ -37,6 +37,7 @@ use function is_int; use function is_writable; use function ltrim; +use function method_exists; use function mkdir; use function preg_match_all; use function random_bytes; @@ -161,6 +162,10 @@ public function __construct( ); } + if (! method_exists(ProxyHelper::class, 'generateLazyGhost')) { + throw ORMInvalidArgumentException::lazyGhostUnavailable(); + } + if (! $proxyDir) { throw ORMInvalidArgumentException::proxyDirectoryRequired(); } diff --git a/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php b/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php index fdeaeddb12a..f1ab47277b7 100644 --- a/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php +++ b/tests/Tests/ORM/Persisters/BinaryIdPersisterTest.php @@ -16,6 +16,8 @@ use Doctrine\Tests\Models\BinaryPrimaryKey\Category; use Doctrine\Tests\OrmTestCase; +use const PHP_VERSION_ID; + final class BinaryIdPersisterTest extends OrmTestCase { private EntityManager|null $entityManager = null; @@ -64,6 +66,7 @@ private function createEntityManager(): EntityManager } $config = ORMSetup::createAttributeMetadataConfiguration([__DIR__ . '/../../Models/BinaryPrimaryKey'], isDevMode: true); + $config->enableNativeLazyObjects(PHP_VERSION_ID >= 80400); if (! DbalType::hasType(BinaryIdType::NAME)) { DbalType::addType(BinaryIdType::NAME, BinaryIdType::class); diff --git a/tests/Tests/ORM/Proxy/ProxyFactoryTest.php b/tests/Tests/ORM/Proxy/ProxyFactoryTest.php index d63bdf39a1b..4283fa6e1a6 100644 --- a/tests/Tests/ORM/Proxy/ProxyFactoryTest.php +++ b/tests/Tests/ORM/Proxy/ProxyFactoryTest.php @@ -10,6 +10,7 @@ use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\EntityNotFoundException; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\ORMInvalidArgumentException; use Doctrine\ORM\Persisters\Entity\BasicEntityPersister; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\Persistence\Mapping\RuntimeReflectionService; @@ -21,11 +22,13 @@ use Doctrine\Tests\Models\ECommerce\ECommerceFeature; use Doctrine\Tests\OrmTestCase; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresMethod; use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\WithoutErrorHandler; use ReflectionClass; use ReflectionProperty; use stdClass; +use Symfony\Component\VarExporter\ProxyHelper; use function assert; use function method_exists; @@ -247,6 +250,7 @@ public function testProxyFactoryAcceptsNullProxyArgsWhenNativeLazyObjectsAreEnab } #[RequiresPhp('8.4')] + #[RequiresMethod(ProxyHelper::class, 'generateLazyGhost')] #[WithoutErrorHandler] public function testProxyFactoryTriggersDeprecationWhenNativeLazyObjectsAreDisabled(): void { @@ -276,6 +280,25 @@ public function testProxyFactoryDoesNotTriggerDeprecationWhenNativeLazyObjectsAr ProxyFactory::AUTOGENERATE_ALWAYS, ); } + + public function testProxyFactoryThrowsIfLazyGhostsAreUnavailable(): void + { + if (method_exists(ProxyHelper::class, 'generateLazyGhost')) { + self::markTestSkipped('This test is not relevant when lazy ghosts are available'); + } + + $this->emMock->getConfiguration()->enableNativeLazyObjects(false); + + $this->expectException(ORMInvalidArgumentException::class); + $this->expectExceptionMessage('Symfony LazyGhost is not available. Please install the "symfony/var-exporter" package version 6.4 or 7 to use this feature or enable PHP 8.4 native lazy objects.'); + + new ProxyFactory( + $this->emMock, + sys_get_temp_dir(), + 'Proxies', + ProxyFactory::AUTOGENERATE_ALWAYS, + ); + } } abstract class AbstractClass