diff --git a/composer.json b/composer.json index 802bb2f..34973ab 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/translation-contracts": "^2.5|^3.0", "symfony/validator": "^6.4|^7.0|^8.0", "symfony/form": "^6.4|^7.0|^8.0", - "symfony/var-exporter": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", "ergebnis/classy": "^1.6", "symfony/translation": "^7.0|^6.4|^8.0", "symfony/deprecation-contracts": "^3.4" diff --git a/src/Manager/SettingsCloner.php b/src/Manager/SettingsCloner.php index 2367415..51f672d 100644 --- a/src/Manager/SettingsCloner.php +++ b/src/Manager/SettingsCloner.php @@ -32,12 +32,12 @@ use Jbtronics\SettingsBundle\Helper\PropertyAccessHelper; use Jbtronics\SettingsBundle\Metadata\MetadataManager; use Jbtronics\SettingsBundle\Metadata\ParameterMetadata; +use Jbtronics\SettingsBundle\Proxy\LegacyProxyHelper; use Jbtronics\SettingsBundle\Proxy\ProxyFactoryInterface; use Jbtronics\SettingsBundle\Proxy\SettingsProxyInterface; use Jbtronics\SettingsBundle\Settings\CloneAndMergeAwareSettingsInterface; use Jbtronics\SettingsBundle\Settings\ResettableSettingsInterface; use PhpParser\Node\Param; -use Symfony\Component\VarExporter\LazyObjectInterface; /** * @internal @@ -138,7 +138,7 @@ public function mergeCopyInternal(object $copy, object $into, bool $recursive, a continue; } - if ($copyEmbedded instanceof SettingsProxyInterface && $copyEmbedded instanceof LazyObjectInterface && !$copyEmbedded->isLazyObjectInitialized()) { //Fallback for older PHP versions + if ($copyEmbedded instanceof SettingsProxyInterface && LegacyProxyHelper::isLegacyProxyUninitialized($copyEmbedded)) { //Fallback for older PHP versions continue; } @@ -218,4 +218,4 @@ private function cloneDataIfNeeded(mixed $data, ParameterMetadata $parameter): m return $data; } -} \ No newline at end of file +} diff --git a/src/Manager/SettingsManager.php b/src/Manager/SettingsManager.php index a22aca0..7ee0fa9 100644 --- a/src/Manager/SettingsManager.php +++ b/src/Manager/SettingsManager.php @@ -31,9 +31,9 @@ use Jbtronics\SettingsBundle\Metadata\EnvVarMode; use Jbtronics\SettingsBundle\Metadata\MetadataManagerInterface; use Jbtronics\SettingsBundle\Metadata\ParameterMetadata; +use Jbtronics\SettingsBundle\Proxy\LegacyProxyHelper; use Jbtronics\SettingsBundle\Proxy\ProxyFactoryInterface; use Jbtronics\SettingsBundle\Proxy\SettingsProxyInterface; -use Symfony\Component\VarExporter\LazyObjectInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -210,7 +210,7 @@ public function save(string|object|array|null $settings = null, bool $cascade = continue; } - if ($instance instanceof SettingsProxyInterface && $instance instanceof LazyObjectInterface && !$instance->isLazyObjectInitialized()) { //Fallback for older PHP versions + if ($instance instanceof SettingsProxyInterface && LegacyProxyHelper::isLegacyProxyUninitialized($instance)) { //Fallback for older PHP versions continue; } @@ -234,6 +234,7 @@ public function reset(): void $this->settings_by_class = []; } + public function isEnvVarOverwritten( object|string $settings, ParameterMetadata|string|\ReflectionProperty $property @@ -311,4 +312,4 @@ public function mergeTemporaryCopy(object|string $copy, bool $cascade = true): v //Use the cloner service to merge the temporary copy back to the original instance $this->settingsCloner->mergeCopy($copy, $original, $cascade); } -} \ No newline at end of file +} diff --git a/src/Proxy/LegacyProxyHelper.php b/src/Proxy/LegacyProxyHelper.php new file mode 100644 index 0000000..b8a90aa --- /dev/null +++ b/src/Proxy/LegacyProxyHelper.php @@ -0,0 +1,32 @@ +getNumberOfParameters() >= 1) { + return !$instance->isLazyObjectInitialized(false); + } + + return !$instance->isLazyObjectInitialized(); + } +} \ No newline at end of file diff --git a/src/Proxy/ProxyFactory.php b/src/Proxy/ProxyFactory.php index 5cebd58..f4a1883 100644 --- a/src/Proxy/ProxyFactory.php +++ b/src/Proxy/ProxyFactory.php @@ -66,7 +66,6 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); */ private readonly bool $useNativeGhostObject; - public function __construct( private readonly string $proxyDir, private readonly string $proxyNamespace, @@ -187,7 +186,12 @@ private function generateProxyClassFile(string $className): void */ protected function generateUseLazyGhostTrait(\ReflectionClass $reflClass): string { - $code = ProxyHelper::generateLazyGhost($reflClass); + $proxyHelper = new \ReflectionClass(ProxyHelper::class); + if (!$proxyHelper->hasMethod('generateLazyGhost')) { + throw new \RuntimeException('Lazy ghost proxy generation requires symfony/var-exporter < 8 or PHP 8.4+ native lazy objects.'); + } + + $code = (string) $proxyHelper->getMethod('generateLazyGhost')->invoke(null, $reflClass); $code = substr($code, 7 + (int) strpos($code, "\n{")); $code = substr($code, 0, (int) strpos($code, "\n}")); /*$code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { @@ -221,4 +225,4 @@ public function getProxyClassName(string $class): string { return rtrim($this->proxyNamespace, '\\') . '\\' . SettingsProxyInterface::MARKER . '\\' . ltrim($class, '\\'); } -} \ No newline at end of file +} diff --git a/src/Proxy/SettingsProxyInterface.php b/src/Proxy/SettingsProxyInterface.php index 1fc02d8..1d9f7fe 100644 --- a/src/Proxy/SettingsProxyInterface.php +++ b/src/Proxy/SettingsProxyInterface.php @@ -28,16 +28,24 @@ namespace Jbtronics\SettingsBundle\Proxy; -use \Symfony\Component\VarExporter\LazyObjectInterface; - /** * This interface is implemented by proxies that lazy load settings. * @internal */ -interface SettingsProxyInterface extends LazyObjectInterface -{ - /** - * Marker for Proxy class names. - */ - public const MARKER = '__JB__'; -} \ No newline at end of file +if (interface_exists(\Symfony\Component\VarExporter\LazyObjectInterface::class)) { + interface SettingsProxyInterface extends \Symfony\Component\VarExporter\LazyObjectInterface + { + /** + * Marker for Proxy class names. + */ + public const MARKER = '__JB__'; + } +} else { + interface SettingsProxyInterface + { + /** + * Marker for Proxy class names. + */ + public const MARKER = '__JB__'; + } +} diff --git a/tests/Manager/SettingsManagerTest.php b/tests/Manager/SettingsManagerTest.php index 4ef00f5..ba762e6 100644 --- a/tests/Manager/SettingsManagerTest.php +++ b/tests/Manager/SettingsManagerTest.php @@ -37,7 +37,6 @@ use Jbtronics\SettingsBundle\Tests\TestApplication\Settings\ValidatableSettings; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Validator\DataCollector\ValidatorDataCollector; -use Symfony\Component\VarExporter\LazyObjectInterface; /** * The functional/integration test for the SettingsManager @@ -160,9 +159,7 @@ public function testGetEmbedded(): void $this->assertEquals('default', $settings->simpleSettings->getValue1()); - if ($settings->simpleSettings instanceof LazyObjectInterface) { - $this->assertTrue($settings->simpleSettings->isLazyObjectInitialized()); - } + $this->assertTrue(LazyObjectTestHelper::isLazyObjectInitialized($settings->simpleSettings)); } public function testGetEmbeddedCircular(): void diff --git a/tests/Migrations/EnvVarToSettingsMigratorTest.php b/tests/Migrations/EnvVarToSettingsMigratorTest.php index 3ab9519..6376515 100644 --- a/tests/Migrations/EnvVarToSettingsMigratorTest.php +++ b/tests/Migrations/EnvVarToSettingsMigratorTest.php @@ -44,7 +44,6 @@ use Jbtronics\SettingsBundle\Tests\TestApplication\Settings\ValidatableSettings; use Jbtronics\SettingsBundle\Tests\TestApplication\Settings\VersionedSettings; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\VarExporter\LazyObjectInterface; class EnvVarToSettingsMigratorTest extends KernelTestCase { diff --git a/tests/Proxy/LazyObjectTestHelper.php b/tests/Proxy/LazyObjectTestHelper.php index 2298d3d..26d3804 100644 --- a/tests/Proxy/LazyObjectTestHelper.php +++ b/tests/Proxy/LazyObjectTestHelper.php @@ -5,7 +5,6 @@ namespace Jbtronics\SettingsBundle\Tests\Proxy; use ReflectionClass; -use Symfony\Component\VarExporter\LazyObjectInterface; final class LazyObjectTestHelper { @@ -16,7 +15,13 @@ final class LazyObjectTestHelper */ public static function isLazyObject(object $obj): bool { - if ($obj instanceof LazyObjectInterface){ + if (interface_exists(\Symfony\Component\VarExporter\LazyObjectInterface::class) + && $obj instanceof \Symfony\Component\VarExporter\LazyObjectInterface + ) { + return true; + } + + if (method_exists($obj, 'isLazyObjectInitialized')) { return true; } @@ -36,8 +41,13 @@ public static function isLazyObject(object $obj): bool */ public static function isLazyObjectInitialized(object $obj, bool $partial = false): bool { - if ($obj instanceof LazyObjectInterface){ - return $obj->isLazyObjectInitialized($partial); + if (method_exists($obj, 'isLazyObjectInitialized')) { + $method = new \ReflectionMethod($obj, 'isLazyObjectInitialized'); + if ($method->getNumberOfParameters() >= 1) { + return $obj->isLazyObjectInitialized($partial); + } + + return $obj->isLazyObjectInitialized(); } if (PHP_VERSION_ID >= 80400) { @@ -47,4 +57,4 @@ public static function isLazyObjectInitialized(object $obj, bool $partial = fals //If we reach here, the object is not a lazy object, so it is considered initialized return true; } -} \ No newline at end of file +} diff --git a/tests/Proxy/ProxyFactoryTest.php b/tests/Proxy/ProxyFactoryTest.php index 655e8bb..31c6fd9 100644 --- a/tests/Proxy/ProxyFactoryTest.php +++ b/tests/Proxy/ProxyFactoryTest.php @@ -30,7 +30,6 @@ use Jbtronics\SettingsBundle\Proxy\SettingsProxyInterface; use Jbtronics\SettingsBundle\Tests\TestApplication\Settings\SimpleSettings; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\VarExporter\LazyObjectInterface; class ProxyFactoryTest extends KernelTestCase { @@ -64,7 +63,7 @@ public function testCreateProxy(): void $instance->setValue1('Initialized'); }; - /** @var LazyObjectInterface&SimpleSettings&SettingsProxyInterface $proxy */ + /** @var SimpleSettings&SettingsProxyInterface $proxy */ $proxy = $this->proxyFactory->createProxy(SimpleSettings::class, $initializer); $this->assertInstanceOf(SimpleSettings::class, $proxy); diff --git a/tests/TestApplication/config/packages/doctrine.php b/tests/TestApplication/config/packages/doctrine.php index a2e6866..e4800fd 100644 --- a/tests/TestApplication/config/packages/doctrine.php +++ b/tests/TestApplication/config/packages/doctrine.php @@ -26,24 +26,30 @@ declare(strict_types=1); +$ormConfig = [ + 'auto_generate_proxy_classes' => true, + 'naming_strategy' => 'doctrine.orm.naming_strategy.underscore_number_aware', + 'auto_mapping' => true, + 'mappings' => [ + 'TestEntities' => [ + 'is_bundle' => false, + 'type' => 'attribute', + 'dir' => '%kernel.project_dir%/src/Entity', + 'prefix' => 'Jbtronics\SettingsBundle\Tests\TestApplication\Entity', + 'alias' => 'app', + ], + ], +]; + +// Doctrine ORM supports native lazy objects on PHP 8.4+ via config. +if (PHP_VERSION_ID >= 80400) { + $ormConfig['enable_native_lazy_objects'] = true; +} + $container->loadFromExtension('doctrine', [ 'dbal' => [ 'driver' => 'pdo_sqlite', 'path' => '%kernel.cache_dir%/test_database.sqlite', ], - - 'orm' => [ - 'auto_generate_proxy_classes' => true, - 'naming_strategy' => 'doctrine.orm.naming_strategy.underscore_number_aware', - 'auto_mapping' => true, - 'mappings' => [ - 'TestEntities' => [ - 'is_bundle' => false, - 'type' => 'attribute', - 'dir' => '%kernel.project_dir%/src/Entity', - 'prefix' => 'Jbtronics\SettingsBundle\Tests\TestApplication\Entity', - 'alias' => 'app', - ], - ], - ], -]); \ No newline at end of file + 'orm' => $ormConfig, +]);