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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions src/Manager/SettingsCloner.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -218,4 +218,4 @@ private function cloneDataIfNeeded(mixed $data, ParameterMetadata $parameter): m

return $data;
}
}
}
7 changes: 4 additions & 3 deletions src/Manager/SettingsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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;
}

Expand All @@ -234,6 +234,7 @@ public function reset(): void
$this->settings_by_class = [];
}


public function isEnvVarOverwritten(
object|string $settings,
ParameterMetadata|string|\ReflectionProperty $property
Expand Down Expand Up @@ -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);
}
}
}
32 changes: 32 additions & 0 deletions src/Proxy/LegacyProxyHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);


namespace Jbtronics\SettingsBundle\Proxy;

/**
* Helper class providing utility methods for working with legacy proxies (pre-PHP 8.4).
* @internal
*/
final class LegacyProxyHelper
{

/**
* Checks if a legacy (pre-PHP 8.4) proxy is still uninitialized.
* If the object is not a legacy proxy, it returns false.
*/
public static function isLegacyProxyUninitialized(object $instance): bool
{
if (!method_exists($instance, 'isLazyObjectInitialized')) {
return false;
}

$method = new \ReflectionMethod($instance, 'isLazyObjectInitialized');
if ($method->getNumberOfParameters() >= 1) {
return !$instance->isLazyObjectInitialized(false);
}

return !$instance->isLazyObjectInitialized();
}
}
10 changes: 7 additions & 3 deletions src/Proxy/ProxyFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -221,4 +225,4 @@ public function getProxyClassName(string $class): string
{
return rtrim($this->proxyNamespace, '\\') . '\\' . SettingsProxyInterface::MARKER . '\\' . ltrim($class, '\\');
}
}
}
26 changes: 17 additions & 9 deletions src/Proxy/SettingsProxyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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__';
}
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__';
}
}
5 changes: 1 addition & 4 deletions tests/Manager/SettingsManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion tests/Migrations/EnvVarToSettingsMigratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
20 changes: 15 additions & 5 deletions tests/Proxy/LazyObjectTestHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Jbtronics\SettingsBundle\Tests\Proxy;

use ReflectionClass;
use Symfony\Component\VarExporter\LazyObjectInterface;

final class LazyObjectTestHelper
{
Expand All @@ -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;
}

Expand All @@ -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) {
Expand All @@ -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;
}
}
}
3 changes: 1 addition & 2 deletions tests/Proxy/ProxyFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);

Expand Down
38 changes: 22 additions & 16 deletions tests/TestApplication/config/packages/doctrine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
],
],
]);
'orm' => $ormConfig,
]);
Loading