diff --git a/CHANGELOG-6.2.md b/CHANGELOG-6.2.md
index 94acdbdcfc990..0870e46a6ab8f 100644
--- a/CHANGELOG-6.2.md
+++ b/CHANGELOG-6.2.md
@@ -7,6 +7,18 @@ in 6.2 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.2.0...v6.2.1
+* 6.2.0-RC1 (2022-11-25)
+
+ * bug #48312 [VarExporter] Improve partial-initialization API for ghost objects (nicolas-grekas)
+ * bug #48285 [Security] Support loading UserBadge directly from accessToken (Jeroeny)
+ * bug #48262 [Notifier] [SMSBiuras] `true`/`false` mismatch for `test_mode` option (StaffNowa)
+ * bug #48273 [HttpKernel] Fix message for unresovable arguments of invokable controllers (fancyweb)
+ * bug #48251 [PropertyInfo] ignore const expressions read by phpdocumentor (xabbuh)
+ * bug #48224 [DependencyInjection] Process bindings in `ServiceLocatorTagPass` (MatTheCat)
+ * bug #48271 [WebProfilerBundle] Fix form panel when there are no view vars (nicolas-grekas)
+ * bug #48274 Add more #[\SensitiveParameter] (fancyweb)
+ * bug #48179 [Console] Support completion for bash functions (Chi-teck)
+
* 6.2.0-BETA3 (2022-11-19)
* bug #48217 [Console] Improve error message when shell is not detected in completion command (GromNaN)
diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php
index 4f826f1c84b44..7ec22ccd85cb3 100644
--- a/src/Symfony/Bridge/PhpUnit/ClockMock.php
+++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php
@@ -97,7 +97,7 @@ public static function hrtime($asNumber = false)
if ($asNumber) {
$number = sprintf('%d%d', (int) self::$now, $ns);
- return PHP_INT_SIZE === 8 ? (int) $number : (float) $number;
+ return \PHP_INT_SIZE === 8 ? (int) $number : (float) $number;
}
return [(int) self::$now, (int) $ns];
diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php
index e8ce7d8f95eec..b6bb058b3f170 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php
@@ -30,7 +30,7 @@ class SodiumVault extends AbstractVault implements EnvVarLoaderInterface
* @param $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault
* or null to store generated keys in the provided $secretsDir
*/
- public function __construct(string $secretsDir, string|\Stringable $decryptionKey = null)
+ public function __construct(string $secretsDir, #[\SensitiveParameter] string|\Stringable $decryptionKey = null)
{
$this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.';
$this->decryptionKey = $decryptionKey;
diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php
index 1e38c58643638..b59f7974f1e87 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php
@@ -79,9 +79,9 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal
$container
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token'))
- ->replaceArgument(0, $userProvider)
- ->replaceArgument(1, new Reference($config['token_handler']))
- ->replaceArgument(2, new Reference($extractorId))
+ ->replaceArgument(0, new Reference($config['token_handler']))
+ ->replaceArgument(1, new Reference($extractorId))
+ ->replaceArgument(2, $userProvider)
->replaceArgument(3, $successHandler)
->replaceArgument(4, $failureHandler)
->replaceArgument(5, $config['realm'])
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php
index 17bc916c8d314..f1aea7cb2c3d1 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php
@@ -26,12 +26,12 @@
->set('security.authenticator.access_token', AccessTokenAuthenticator::class)
->abstract()
->args([
- abstract_arg('user provider'),
abstract_arg('access token handler'),
abstract_arg('access token extractor'),
null,
null,
null,
+ null,
])
->call('setTranslator', [service('translator')->ignoreOnInvalid()])
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php
index 0d1e9e0c0e7fc..4f94cc6936a05 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php
@@ -13,6 +13,7 @@
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class AccessTokenHandler implements AccessTokenHandlerInterface
{
@@ -20,10 +21,10 @@ public function __construct()
{
}
- public function getUserIdentifierFrom(string $accessToken): string
+ public function getUserBadgeFrom(string $accessToken): UserBadge
{
return match ($accessToken) {
- 'VALID_ACCESS_TOKEN' => 'dunglas',
+ 'VALID_ACCESS_TOKEN' => new UserBadge('dunglas'),
default => throw new BadCredentialsException('Invalid credentials.'),
};
}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
index 2f2f21a9f45c1..1dad6d8a8361e 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
@@ -714,7 +714,7 @@
- {% for variable, value in data.view_vars %}
+ {% for variable, value in data.view_vars ?? [] %}
{{ variable }} |
{{ profiler_dump(value) }} |
diff --git a/src/Symfony/Component/Cache/Traits/Redis5Proxy.php b/src/Symfony/Component/Cache/Traits/Redis5Proxy.php
index f9235b04df63f..bcfe8bc8d3288 100644
--- a/src/Symfony/Component/Cache/Traits/Redis5Proxy.php
+++ b/src/Symfony/Component/Cache/Traits/Redis5Proxy.php
@@ -29,9 +29,6 @@ class Redis5Proxy extends \Redis implements ResetInterface, LazyObjectInterface
resetLazyObject as reset;
}
- private int $lazyObjectId;
- private \Redis $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],
diff --git a/src/Symfony/Component/Cache/Traits/Redis6Proxy.php b/src/Symfony/Component/Cache/Traits/Redis6Proxy.php
index 2304bd878684f..f5373df72f3bf 100644
--- a/src/Symfony/Component/Cache/Traits/Redis6Proxy.php
+++ b/src/Symfony/Component/Cache/Traits/Redis6Proxy.php
@@ -29,9 +29,6 @@ class Redis6Proxy extends \Redis implements ResetInterface, LazyObjectInterface
resetLazyObject as reset;
}
- private int $lazyObjectId;
- private \Redis $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],
diff --git a/src/Symfony/Component/Cache/Traits/RedisCluster5Proxy.php b/src/Symfony/Component/Cache/Traits/RedisCluster5Proxy.php
index 58e5ad5cbff9f..d95aced028137 100644
--- a/src/Symfony/Component/Cache/Traits/RedisCluster5Proxy.php
+++ b/src/Symfony/Component/Cache/Traits/RedisCluster5Proxy.php
@@ -29,9 +29,6 @@ class RedisCluster5Proxy extends \RedisCluster implements ResetInterface, LazyOb
resetLazyObject as reset;
}
- private int $lazyObjectId;
- private \RedisCluster $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],
diff --git a/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php b/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php
index bf61a4c2c25f5..9cc6abf7fad94 100644
--- a/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php
+++ b/src/Symfony/Component/Cache/Traits/RedisCluster6Proxy.php
@@ -29,9 +29,6 @@ class RedisCluster6Proxy extends \RedisCluster implements ResetInterface, LazyOb
resetLazyObject as reset;
}
- private int $lazyObjectId;
- private \RedisCluster $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],
diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php
index 76cf77df5e195..c345b4af7747f 100644
--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php
@@ -430,7 +430,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $
$value = fgets($inputStream, 4096);
- if (4095 === strlen($value)) {
+ if (4095 === \strlen($value)) {
$errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
$errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
}
diff --git a/src/Symfony/Component/Console/Resources/completion.bash b/src/Symfony/Component/Console/Resources/completion.bash
index 4dd978e75b351..ad69eab0ff070 100644
--- a/src/Symfony/Component/Console/Resources/completion.bash
+++ b/src/Symfony/Component/Console/Resources/completion.bash
@@ -11,13 +11,14 @@ _sf_{{ COMMAND_NAME }}() {
local sf_cmd="${COMP_WORDS[0]}"
# for an alias, get the real script behind it
- if [[ $(type -t $sf_cmd) == "alias" ]]; then
+ sf_cmd_type=$(type -t $sf_cmd)
+ if [[ $sf_cmd_type == "alias" ]]; then
sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/")
- else
+ elif [[ $sf_cmd_type == "file" ]]; then
sf_cmd=$(type -p $sf_cmd)
fi
- if [ ! -x "$sf_cmd" ]; then
+ if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then
return 1
fi
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
index 7387a8a934f4d..67103a78522d3 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
@@ -40,6 +40,10 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
return self::register($this->container, $value->getValues());
}
+ if ($value instanceof Definition) {
+ $value->setBindings(parent::processValue($value->getBindings()));
+ }
+
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
return parent::processValue($value, $isRoot);
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php
index 644876e81f20a..4d248306a0b7d 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php
@@ -214,6 +214,18 @@ public function testDefinitionOrderIsTheSame()
static::assertSame(['service-2', 'service-1'], array_keys($factories));
}
+
+ public function testBindingsAreProcessed()
+ {
+ $container = new ContainerBuilder();
+
+ $definition = $container->register('foo')
+ ->setBindings(['foo' => new ServiceLocatorArgument()]);
+
+ (new ServiceLocatorTagPass())->process($container);
+
+ $this->assertInstanceOf(Reference::class, $definition->getBindings()['foo']->getValues()[0]);
+ }
}
class Locator
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php
index 32e5364ef4e35..a7972921fadba 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_dedup_lazy_ghost.php
@@ -75,8 +75,6 @@ class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExport
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
- private int $lazyObjectId;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt
index d04e886a810b2..a52a32a27dadd 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt
@@ -45,8 +45,6 @@ class FooLazyClass_f814e3a extends \Bar\FooLazyClass implements \Symfony\Compone
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
- private int $lazyObjectId;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php
index f0143cb590f3f..c304c9461fd1c 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_ghost.php
@@ -79,8 +79,6 @@ class stdClass_5a8a5eb extends \stdClass implements \Symfony\Component\VarExport
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
- private int $lazyObjectId;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [];
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php
index ec39e663e4ec7..5de8772d6746e 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_wither_lazy.php
@@ -75,9 +75,6 @@ class Wither_94fa281 extends \Symfony\Component\DependencyInjection\Tests\Compil
{
use \Symfony\Component\VarExporter\LazyProxyTrait;
- private int $lazyObjectId;
- private parent $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],
diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
index 00a757d637645..c7ab562f2ef75 100644
--- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
+++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
@@ -1768,7 +1768,7 @@ public function testDumpToProtectedDirectory()
$this->markTestSkipped('This test is specific to Windows.');
}
- if (($userProfilePath = getenv('USERPROFILE')) === false || !is_dir($userProfilePath)) {
+ if (false === ($userProfilePath = getenv('USERPROFILE')) || !is_dir($userProfilePath)) {
throw new \RuntimeException('Failed to retrieve user profile path.');
}
diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md
index 790767d728190..fdea3d67e1b19 100644
--- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md
+++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md
@@ -104,7 +104,7 @@ CHANGELOG
make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to
update your database.
* `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column,
- make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database
+ make sure to run `CREATE INDEX expiry ON sessions (sess_lifetime)` to update your database
to speed up garbage collection of expired sessions.
* added `SessionHandlerFactory` to create session handlers with a DSN
* added `IpUtils::anonymize()` to help with GDPR compliance.
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
index 9e26ac56e1249..302372627ddb5 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -207,7 +207,7 @@ public function createTable()
try {
$this->pdo->exec($sql);
- $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)");
+ $this->pdo->exec("CREATE INDEX expiry ON $this->table ($this->lifetimeCol)");
} catch (\PDOException $e) {
$this->rollback();
diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php
index 006ab29d46e04..26403612880fe 100644
--- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php
+++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php
@@ -72,8 +72,10 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
$controller = ltrim($controller, '\\');
}
- if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) {
- $controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
+ if (!$this->container->has($controller)) {
+ $controller = (false !== $i = strrpos($controller, ':'))
+ ? substr($controller, 0, $i).strtolower(substr($controller, $i))
+ : $controller.'::__invoke';
}
if ($this->container->has($controller)) {
diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php
index eb4ac7b06a217..edc30e1806c13 100644
--- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php
+++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php
@@ -42,8 +42,6 @@ public function supports(Request $request, ArgumentMetadata $argument): bool
return true;
}
- @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
-
$method = \get_class($this->inner).'::'.__FUNCTION__;
$this->stopwatch->start($method, 'controller.argument_value_resolver');
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 613356dd5af51..52a61b868d73c 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -75,12 +75,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.2.0-BETA3';
+ public const VERSION = '6.2.0-RC1';
public const VERSION_ID = 60200;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 2;
public const RELEASE_VERSION = 0;
- public const EXTRA_VERSION = 'BETA3';
+ public const EXTRA_VERSION = 'RC1';
public const END_OF_MAINTENANCE = '07/2023';
public const END_OF_LIFE = '07/2023';
diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php
index 25214afdfa48a..71b5543b04092 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php
@@ -108,6 +108,22 @@ public function testControllerNameIsAnArray()
$resolver->resolve($request, $argument);
}
+ /**
+ * In Symfony 7, keep this test case but remove the call to supports().
+ *
+ * @group legacy
+ */
+ public function testInvokableController()
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('Could not resolve argument $dummy of "App\Controller\Mine::__invoke()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?');
+ $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
+ $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
+ $request = $this->requestWithAttributes(['_controller' => 'App\Controller\Mine']);
+ $this->assertTrue($resolver->supports($request, $argument));
+ $resolver->resolve($request, $argument);
+ }
+
private function requestWithAttributes(array $attributes)
{
$request = Request::create('/');
diff --git a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php
index 10814f8fd18ca..bc6257422fe50 100644
--- a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php
+++ b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/SmsBiurasTransport.php
@@ -87,7 +87,7 @@ protected function doSend(MessageInterface $message): SentMessage
'apikey' => $this->apiKey,
'message' => $message->getSubject(),
'from' => $from,
- 'test' => $this->testMode ? 0 : 1,
+ 'test' => $this->testMode ? 1 : 0,
'to' => $message->getPhone(),
],
]);
diff --git a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php
index 28e96cc491877..76cb97a39cc76 100644
--- a/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php
+++ b/src/Symfony/Component/Notifier/Bridge/SmsBiuras/Tests/SmsBiurasTransportTest.php
@@ -11,12 +11,14 @@
namespace Symfony\Component\Notifier\Bridge\SmsBiuras\Tests;
+use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransport;
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Test\TransportTestCase;
use Symfony\Contracts\HttpClient\HttpClientInterface;
+use Symfony\Contracts\HttpClient\ResponseInterface;
final class SmsBiurasTransportTest extends TransportTestCase
{
@@ -40,4 +42,48 @@ public function unsupportedMessagesProvider(): iterable
yield [new ChatMessage('Hello!')];
yield [$this->createMock(MessageInterface::class)];
}
+
+ /**
+ * @dataProvider provideTestMode()
+ */
+ public function testTestMode(int $expected, bool $testMode)
+ {
+ $message = new SmsMessage('+37012345678', 'Hello World!');
+
+ $response = $this->createMock(ResponseInterface::class);
+ $response->expects($this->atLeast(1))
+ ->method('getStatusCode')
+ ->willReturn(200);
+ $response->expects($this->atLeast(1))
+ ->method('getContent')
+ ->willReturn('OK: 519545');
+
+ $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($response, $message, $expected): ResponseInterface {
+ $this->assertSame('GET', $method);
+ $this->assertSame(sprintf(
+ 'https://savitarna.smsbiuras.lt/api?uid=uid&apikey=api_key&message=%s&from=from&test=%s&to=%s',
+ rawurlencode($message->getSubject()),
+ $expected,
+ rawurlencode($message->getPhone())
+ ), $url);
+ $this->assertSame($expected, $options['query']['test']);
+
+ $this->assertSame(200, $response->getStatusCode());
+ $this->assertSame('OK: 519545', $response->getContent());
+
+ return $response;
+ });
+
+ $transport = new SmsBiurasTransport('uid', 'api_key', 'from', $testMode, $client);
+
+ $sentMessage = $transport->send($message);
+
+ $this->assertSame('519545', $sentMessage->getMessageId());
+ }
+
+ public static function provideTestMode(): iterable
+ {
+ yield [1, true];
+ yield [0, false];
+ }
}
diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
index a9c7c1b478f45..6cf083bb4ee86 100644
--- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
+++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\PropertyInfo\Util;
use phpDocumentor\Reflection\PseudoType;
+use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\Type as DocType;
use phpDocumentor\Reflection\Types\Array_;
@@ -42,6 +43,11 @@ final class PhpDocTypeHelper
*/
public function getTypes(DocType $varType): array
{
+ if ($varType instanceof ConstExpression) {
+ // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
+ return [];
+ }
+
$types = [];
$nullable = false;
@@ -67,6 +73,11 @@ public function getTypes(DocType $varType): array
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
$type = $varType->get($typeIndex);
+ if ($type instanceof ConstExpression) {
+ // It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
+ return [];
+ }
+
// If null is present, all types are nullable
if ($type instanceof Null_) {
$nullable = true;
diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php
index 7d6dbda5489f4..7cefe8b0a0572 100644
--- a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php
+++ b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php
@@ -79,7 +79,7 @@ public function getToken(string $tokenId): CsrfToken
return new CsrfToken($tokenId, $this->randomize($value));
}
- public function refreshToken(#[\SensitiveParameter] string $tokenId): CsrfToken
+ public function refreshToken(string $tokenId): CsrfToken
{
$namespacedId = $this->getNamespace().$tokenId;
$value = $this->generator->generateToken();
diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php
index 15bdaaf82046e..cf0ce03e9311d 100644
--- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php
+++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php
@@ -51,7 +51,7 @@ public function getToken(string $tokenId): string
return (string) $_SESSION[$this->namespace][$tokenId];
}
- public function setToken(string $tokenId, string $token)
+ public function setToken(string $tokenId, #[\SensitiveParameter] string $token)
{
if (!$this->sessionStarted) {
$this->startSession();
diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php
index bed96fa4c2960..fdbaf135d7654 100644
--- a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php
+++ b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php
@@ -56,7 +56,7 @@ public function getToken(string $tokenId): string
return (string) $session->get($this->namespace.'/'.$tokenId);
}
- public function setToken(string $tokenId, string $token)
+ public function setToken(string $tokenId, #[\SensitiveParameter] string $token)
{
$session = $this->getSession();
if (!$session->isStarted()) {
diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php
index a26439366e2ab..d119d6e977bcb 100644
--- a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php
+++ b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php
@@ -28,7 +28,7 @@ public function getToken(string $tokenId): string;
/**
* Stores a CSRF token.
*/
- public function setToken(string $tokenId, string $token);
+ public function setToken(string $tokenId, #[\SensitiveParameter] string $token);
/**
* Removes a CSRF token.
diff --git a/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php b/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php
index 33a0690d15cc5..5cbc857a1c6f0 100644
--- a/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php
+++ b/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\AccessToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
/**
* The token handler retrieves the user identifier from the token.
@@ -24,5 +25,5 @@ interface AccessTokenHandlerInterface
/**
* @throws AuthenticationException
*/
- public function getUserIdentifierFrom(string $accessToken): string;
+ public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge;
}
diff --git a/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php
index ae8f0a4ea3da2..d3ade9b0be73b 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php
@@ -21,7 +21,6 @@
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
-use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
@@ -38,9 +37,9 @@ class AccessTokenAuthenticator implements AuthenticatorInterface
private ?TranslatorInterface $translator = null;
public function __construct(
- private readonly UserProviderInterface $userProvider,
private readonly AccessTokenHandlerInterface $accessTokenHandler,
private readonly AccessTokenExtractorInterface $accessTokenExtractor,
+ private readonly ?UserProviderInterface $userProvider = null,
private readonly ?AuthenticationSuccessHandlerInterface $successHandler = null,
private readonly ?AuthenticationFailureHandlerInterface $failureHandler = null,
private readonly ?string $realm = null,
@@ -58,11 +57,13 @@ public function authenticate(Request $request): Passport
if (!$accessToken) {
throw new BadCredentialsException('Invalid credentials.');
}
- $userIdentifier = $this->accessTokenHandler->getUserIdentifierFrom($accessToken);
- return new SelfValidatingPassport(
- new UserBadge($userIdentifier, $this->userProvider->loadUserByIdentifier(...))
- );
+ $userBadge = $this->accessTokenHandler->getUserBadgeFrom($accessToken);
+ if (null === $userBadge->getUserLoader() && $this->userProvider) {
+ $userBadge->setUserLoader($this->userProvider->loadUserByIdentifier(...));
+ }
+
+ return new SelfValidatingPassport($userBadge);
}
public function createToken(Passport $passport, string $firewallName): TokenInterface
diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php
index b24e2c75f548c..52aeafe858eb1 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/CsrfTokenBadge.php
@@ -33,7 +33,7 @@ class CsrfTokenBadge implements BadgeInterface
* Using a different string for each authenticator improves its security.
* @param string|null $csrfToken The CSRF token presented in the request, if any
*/
- public function __construct(string $csrfTokenId, ?string $csrfToken)
+ public function __construct(string $csrfTokenId, #[\SensitiveParameter] ?string $csrfToken)
{
$this->csrfTokenId = $csrfTokenId;
$this->csrfToken = $csrfToken;
diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php
index 992b50d0811c1..9cbf6d6e69343 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php
@@ -32,7 +32,7 @@ class PasswordUpgradeBadge implements BadgeInterface
* @param string $plaintextPassword The presented password, used in the rehash
* @param PasswordUpgraderInterface|null $passwordUpgrader The password upgrader, defaults to the UserProvider if null
*/
- public function __construct(string $plaintextPassword, PasswordUpgraderInterface $passwordUpgrader = null)
+ public function __construct(#[\SensitiveParameter] string $plaintextPassword, PasswordUpgraderInterface $passwordUpgrader = null)
{
$this->plaintextPassword = $plaintextPassword;
$this->passwordUpgrader = $passwordUpgrader;
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php
index e59ce918b0651..8dd3188eb9587 100644
--- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php
@@ -22,6 +22,7 @@
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor;
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler;
@@ -40,7 +41,7 @@ protected function setUp(): void
/**
* @dataProvider provideSupportData
*/
- public function testSupport($request): void
+ public function testSupport($request)
{
$this->setUpAuthenticator();
@@ -53,9 +54,9 @@ public function provideSupportData(): iterable
yield [new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer INVALID_ACCESS_TOKEN'])];
}
- public function testAuthenticate(): void
+ public function testAuthenticate()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator();
$request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']);
@@ -66,7 +67,7 @@ public function testAuthenticate(): void
/**
* @dataProvider provideInvalidAuthenticateData
*/
- public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void
+ public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class)
{
$this->expectException($exceptionType);
$this->expectExceptionMessage($errorMessage);
@@ -100,13 +101,13 @@ public function provideInvalidAuthenticateData(): iterable
private function setUpAuthenticator(): void
{
$this->authenticator = new AccessTokenAuthenticator(
- $this->userProvider,
$this->accessTokenHandler,
new ChainAccessTokenExtractor([
new FormEncodedBodyExtractor(),
new QueryAccessTokenExtractor(),
new HeaderAccessTokenExtractor(),
- ])
+ ]),
+ $this->userProvider
);
}
}
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php
index 5f251bb71f197..b915a5d10631c 100644
--- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php
@@ -19,6 +19,7 @@
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor;
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler;
@@ -34,7 +35,7 @@ protected function setUp(): void
$this->accessTokenHandler = new InMemoryAccessTokenHandler();
}
- public function testSupport(): void
+ public function testSupport()
{
$this->setUpAuthenticator();
$request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
@@ -44,7 +45,7 @@ public function testSupport(): void
$this->assertNull($this->authenticator->supports($request));
}
- public function testSupportsWithCustomParameter(): void
+ public function testSupportsWithCustomParameter()
{
$this->setUpAuthenticator('protection-token');
$request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
@@ -54,9 +55,9 @@ public function testSupportsWithCustomParameter(): void
$this->assertNull($this->authenticator->supports($request));
}
- public function testAuthenticate(): void
+ public function testAuthenticate()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator();
$request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'], 'access_token=VALID_ACCESS_TOKEN');
$request->request->set('access_token', 'VALID_ACCESS_TOKEN');
@@ -66,9 +67,9 @@ public function testAuthenticate(): void
$this->assertInstanceOf(SelfValidatingPassport::class, $passport);
}
- public function testAuthenticateWithCustomParameter(): void
+ public function testAuthenticateWithCustomParameter()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator('protection-token');
$request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']);
$request->request->set('protection-token', 'VALID_ACCESS_TOKEN');
@@ -81,7 +82,7 @@ public function testAuthenticateWithCustomParameter(): void
/**
* @dataProvider provideInvalidAuthenticateData
*/
- public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void
+ public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class)
{
$this->expectException($exceptionType);
$this->expectExceptionMessage($errorMessage);
@@ -119,9 +120,9 @@ public function provideInvalidAuthenticateData(): iterable
private function setUpAuthenticator(string $parameter = 'access_token'): void
{
$this->authenticator = new AccessTokenAuthenticator(
- $this->userProvider,
$this->accessTokenHandler,
- new FormEncodedBodyExtractor($parameter)
+ new FormEncodedBodyExtractor($parameter),
+ $this->userProvider
);
}
}
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php
index 89e91e34feecf..a4e7758bffed5 100644
--- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php
@@ -19,6 +19,7 @@
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler;
@@ -37,7 +38,7 @@ protected function setUp(): void
/**
* @dataProvider provideSupportData
*/
- public function testSupport($request): void
+ public function testSupport($request)
{
$this->setUpAuthenticator();
@@ -53,7 +54,7 @@ public function provideSupportData(): iterable
/**
* @dataProvider provideSupportsWithCustomTokenTypeData
*/
- public function testSupportsWithCustomTokenType($request, $result): void
+ public function testSupportsWithCustomTokenType($request, $result)
{
$this->setUpAuthenticator('Authorization', 'JWT');
@@ -71,7 +72,7 @@ public function provideSupportsWithCustomTokenTypeData(): iterable
/**
* @dataProvider provideSupportsWithCustomHeaderParameter
*/
- public function testSupportsWithCustomHeaderParameter($request, $result): void
+ public function testSupportsWithCustomHeaderParameter($request, $result)
{
$this->setUpAuthenticator('X-FOO');
@@ -86,9 +87,9 @@ public function provideSupportsWithCustomHeaderParameter(): iterable
yield [new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer INVALID_ACCESS_TOKEN']), false];
}
- public function testAuthenticate(): void
+ public function testAuthenticate()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator();
$request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']);
@@ -96,9 +97,9 @@ public function testAuthenticate(): void
$this->assertInstanceOf(SelfValidatingPassport::class, $passport);
}
- public function testAuthenticateWithCustomTokenType(): void
+ public function testAuthenticateWithCustomTokenType()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator('Authorization', 'JWT');
$request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'JWT VALID_ACCESS_TOKEN']);
@@ -109,7 +110,7 @@ public function testAuthenticateWithCustomTokenType(): void
/**
* @dataProvider provideInvalidAuthenticateData
*/
- public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void
+ public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class)
{
$this->expectException($exceptionType);
$this->expectExceptionMessage($errorMessage);
@@ -143,9 +144,9 @@ public function provideInvalidAuthenticateData(): iterable
private function setUpAuthenticator(string $headerParameter = 'Authorization', string $tokenType = 'Bearer'): void
{
$this->authenticator = new AccessTokenAuthenticator(
- $this->userProvider,
$this->accessTokenHandler,
- new HeaderAccessTokenExtractor($headerParameter, $tokenType)
+ new HeaderAccessTokenExtractor($headerParameter, $tokenType),
+ $this->userProvider
);
}
}
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php
index c1a8206115452..73f8392a9fa94 100644
--- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php
@@ -19,6 +19,7 @@
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor;
use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler;
@@ -34,7 +35,7 @@ protected function setUp(): void
$this->accessTokenHandler = new InMemoryAccessTokenHandler();
}
- public function testSupport(): void
+ public function testSupport()
{
$this->setUpAuthenticator();
$request = new Request();
@@ -43,7 +44,7 @@ public function testSupport(): void
$this->assertNull($this->authenticator->supports($request));
}
- public function testSupportsWithCustomParameter(): void
+ public function testSupportsWithCustomParameter()
{
$this->setUpAuthenticator('protection-token');
$request = new Request();
@@ -52,9 +53,9 @@ public function testSupportsWithCustomParameter(): void
$this->assertNull($this->authenticator->supports($request));
}
- public function testAuthenticate(): void
+ public function testAuthenticate()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator();
$request = new Request();
$request->query->set('access_token', 'VALID_ACCESS_TOKEN');
@@ -63,9 +64,9 @@ public function testAuthenticate(): void
$this->assertInstanceOf(SelfValidatingPassport::class, $passport);
}
- public function testAuthenticateWithCustomParameter(): void
+ public function testAuthenticateWithCustomParameter()
{
- $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo');
+ $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo'));
$this->setUpAuthenticator('protection-token');
$request = new Request();
$request->query->set('protection-token', 'VALID_ACCESS_TOKEN');
@@ -77,7 +78,7 @@ public function testAuthenticateWithCustomParameter(): void
/**
* @dataProvider provideInvalidAuthenticateData
*/
- public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void
+ public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class)
{
$this->expectException($exceptionType);
$this->expectExceptionMessage($errorMessage);
@@ -111,9 +112,9 @@ public function provideInvalidAuthenticateData(): iterable
private function setUpAuthenticator(string $parameter = 'access_token'): void
{
$this->authenticator = new AccessTokenAuthenticator(
- $this->userProvider,
$this->accessTokenHandler,
- new QueryAccessTokenExtractor($parameter)
+ new QueryAccessTokenExtractor($parameter),
+ $this->userProvider
);
}
}
diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php
index 9ace3ba8324a7..3fe4c850736ce 100644
--- a/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php
+++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php
@@ -13,15 +13,16 @@
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class InMemoryAccessTokenHandler implements AccessTokenHandlerInterface
{
/**
- * @var array
+ * @var array
*/
private $accessTokens = [];
- public function getUserIdentifierFrom(string $accessToken): string
+ public function getUserBadgeFrom(string $accessToken): UserBadge
{
if (!\array_key_exists($accessToken, $this->accessTokens)) {
throw new BadCredentialsException('Invalid access token or invalid user.');
@@ -37,7 +38,7 @@ public function remove(string $accessToken): self
return $this;
}
- public function add(string $accessToken, string $user): self
+ public function add(string $accessToken, UserBadge $user): self
{
$this->accessTokens[$accessToken] = $user;
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php
index bfd4336de6e33..f44e606a49f12 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php
@@ -14,6 +14,7 @@
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Validator\Constraints\ExpressionLanguageSyntax;
use Symfony\Component\Validator\Constraints\ExpressionLanguageSyntaxValidator;
+use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
/**
@@ -21,7 +22,7 @@
*/
class ExpressionLanguageSyntaxValidatorTest extends ConstraintValidatorTestCase
{
- protected function createValidator(): ExpressionLanguageSyntaxValidator
+ protected function createValidator(): ConstraintValidatorInterface
{
return new ExpressionLanguageSyntaxValidator(new ExpressionLanguage());
}
diff --git a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php
index 605f1fdd52831..99f721df2605c 100644
--- a/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php
+++ b/src/Symfony/Component/VarExporter/Internal/LazyObjectState.php
@@ -55,16 +55,34 @@ public function initialize($instance, $propertyName, $propertyScope)
$propertyScopes = Hydrator::$propertyScopes[$class];
$propertyScopes[$k = "\0$propertyScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName;
- if (!$initializer = $this->initializer[$k] ?? null) {
- return self::STATUS_UNINITIALIZED_PARTIAL;
- }
+ if ($initializer = $this->initializer[$k] ?? null) {
+ $value = $initializer(...[$instance, $propertyName, $propertyScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]);
+ $accessor = LazyObjectRegistry::$classAccessors[$propertyScope] ??= LazyObjectRegistry::getClassAccessors($propertyScope);
+ $accessor['set']($instance, $propertyName, $value);
- $value = $initializer(...[$instance, $propertyName, $propertyScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]);
+ return $this->status = self::STATUS_INITIALIZED_PARTIAL;
+ }
- $accessor = LazyObjectRegistry::$classAccessors[$propertyScope] ??= LazyObjectRegistry::getClassAccessors($propertyScope);
- $accessor['set']($instance, $propertyName, $value);
+ $status = self::STATUS_UNINITIALIZED_PARTIAL;
+
+ if ($initializer = $this->initializer["\0"] ?? null) {
+ if (!\is_array($values = $initializer($instance, LazyObjectRegistry::$defaultProperties[$class]))) {
+ throw new \TypeError(sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values)));
+ }
+ $properties = (array) $instance;
+ foreach ($values as $key => $value) {
+ if ($k === $key) {
+ $status = self::STATUS_INITIALIZED_PARTIAL;
+ }
+ if (!\array_key_exists($key, $properties) && [$scope, $name, $readonlyScope] = $propertyScopes[$key] ?? null) {
+ $scope = $readonlyScope ?? ('*' !== $scope ? $scope : $class);
+ $accessor = LazyObjectRegistry::$classAccessors[$scope] ??= LazyObjectRegistry::getClassAccessors($scope);
+ $accessor['set']($instance, $name, $value);
+ }
+ }
+ }
- return $this->status = self::STATUS_INITIALIZED_PARTIAL;
+ return $status;
}
$this->status = self::STATUS_INITIALIZED_FULL;
diff --git a/src/Symfony/Component/VarExporter/LazyGhostTrait.php b/src/Symfony/Component/VarExporter/LazyGhostTrait.php
index 16e40f7234567..df40c267fd027 100644
--- a/src/Symfony/Component/VarExporter/LazyGhostTrait.php
+++ b/src/Symfony/Component/VarExporter/LazyGhostTrait.php
@@ -15,11 +15,10 @@
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
use Symfony\Component\VarExporter\Internal\LazyObjectState;
-/**
- * @property int $lazyObjectId This property must be declared as private in classes using this trait
- */
trait LazyGhostTrait
{
+ private int $lazyObjectId;
+
/**
* Creates a lazy-loading ghost instance.
*
@@ -30,13 +29,23 @@ trait LazyGhostTrait
* properties and closures should accept 4 arguments: the instance to
* initialize, the property to initialize, its write-scope, and its default
* value. Each closure should return the value of the corresponding property.
+ * The special "\0" key can be used to define a closure that returns all
+ * properties at once when full-initialization is needed; it takes the
+ * instance and its default properties as arguments.
+ *
+ * Properties should be indexed by their array-cast name, see
+ * https://php.net/manual/language.types.array#language.types.array.casting
*
- * @param \Closure(static):void|array $initializer
- * @param array $skippedProperties An array indexed by the properties to skip, aka the ones
- * that the initializer doesn't set when its a closure
+ * @param (\Closure(static):void
+ * |array
+ * |array{"\0": \Closure(static, array):array}) $initializer
+ * @param array|null $skippedProperties An array indexed by the properties to skip, aka the ones
+ * that the initializer doesn't set when its a closure
*/
- public static function createLazyGhost(\Closure|array $initializer, array $skippedProperties = [], self $instance = null): static
+ public static function createLazyGhost(\Closure|array $initializer, array $skippedProperties = null, self $instance = null): static
{
+ $onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null;
+
if (self::class !== $class = $instance ? $instance::class : static::class) {
$skippedProperties["\0".self::class."\0lazyObjectId"] = true;
} elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
@@ -46,8 +55,7 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
$instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
Registry::$defaultProperties[$class] ??= (array) $instance;
$instance->lazyObjectId = $id = spl_object_id($instance);
- Registry::$states[$id] = new LazyObjectState($initializer, $skippedProperties);
- $onlyProperties = \is_array($initializer) ? $initializer : null;
+ Registry::$states[$id] = new LazyObjectState($initializer, $skippedProperties ??= []);
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
$reset($instance, $skippedProperties, $onlyProperties);
@@ -58,8 +66,10 @@ public static function createLazyGhost(\Closure|array $initializer, array $skipp
/**
* Returns whether the object is initialized.
+ *
+ * @param $partial Whether partially initialized objects should be considered as initialized
*/
- public function isLazyObjectInitialized(): bool
+ public function isLazyObjectInitialized(bool $partial = false): bool
{
if (!$state = Registry::$states[$this->lazyObjectId ?? ''] ?? null) {
return true;
@@ -71,6 +81,11 @@ public function isLazyObjectInitialized(): bool
$class = $this::class;
$properties = (array) $this;
+
+ if ($partial) {
+ return (bool) array_intersect_key($state->initializer, $properties);
+ }
+
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
foreach ($state->initializer as $key => $initializer) {
if (!\array_key_exists($key, $properties) && isset($propertyScopes[$key])) {
@@ -98,6 +113,8 @@ public function initializeLazyObject(): static
return $this;
}
+ $values = isset($state->initializer["\0"]) ? null : [];
+
$class = $this::class;
$properties = (array) $this;
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
@@ -105,9 +122,25 @@ public function initializeLazyObject(): static
if (\array_key_exists($key, $properties) || ![$scope, $name, $readonlyScope] = $propertyScopes[$key] ?? null) {
continue;
}
+ $scope = $readonlyScope ?? ('*' !== $scope ? $scope : $class);
- $state->initialize($this, $name, $readonlyScope ?? ('*' !== $scope ? $scope : null));
- $properties = (array) $this;
+ if (null === $values) {
+ if (!\is_array($values = ($state->initializer["\0"])($this, Registry::$defaultProperties[$class]))) {
+ throw new \TypeError(sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values)));
+ }
+
+ if (\array_key_exists($key, $properties = (array) $this)) {
+ continue;
+ }
+ }
+
+ if (\array_key_exists($key, $values)) {
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+ $accessor['set']($this, $name, $properties[$key] = $values[$key]);
+ } else {
+ $state->initialize($this, $name, $scope);
+ $properties = (array) $this;
+ }
}
return $this;
diff --git a/src/Symfony/Component/VarExporter/LazyObjectInterface.php b/src/Symfony/Component/VarExporter/LazyObjectInterface.php
index 314ba85e37039..36708845912ca 100644
--- a/src/Symfony/Component/VarExporter/LazyObjectInterface.php
+++ b/src/Symfony/Component/VarExporter/LazyObjectInterface.php
@@ -15,8 +15,10 @@ interface LazyObjectInterface
{
/**
* Returns whether the object is initialized.
+ *
+ * @param $partial Whether partially initialized objects should be considered as initialized
*/
- public function isLazyObjectInitialized(): bool;
+ public function isLazyObjectInitialized(bool $partial = false): bool;
/**
* Forces initialization of a lazy object and returns it.
diff --git a/src/Symfony/Component/VarExporter/LazyProxyTrait.php b/src/Symfony/Component/VarExporter/LazyProxyTrait.php
index 4b58b7a388160..97509eb35321b 100644
--- a/src/Symfony/Component/VarExporter/LazyProxyTrait.php
+++ b/src/Symfony/Component/VarExporter/LazyProxyTrait.php
@@ -16,13 +16,11 @@
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
use Symfony\Component\VarExporter\Internal\LazyObjectState;
-/**
- * @property int $lazyObjectId This property must be declared as private in classes using this trait
- * @property parent $lazyObjectReal This property must be declared as private in classes using this trait;
- * its type should match the type of the proxied object
- */
trait LazyProxyTrait
{
+ private int $lazyObjectId;
+ private object $lazyObjectReal;
+
/**
* Creates a lazy-loading virtual proxy.
*
@@ -49,8 +47,10 @@ public static function createLazyProxy(\Closure $initializer, self $instance = n
/**
* Returns whether the object is initialized.
+ *
+ * @param $partial Whether partially initialized objects should be considered as initialized
*/
- public function isLazyObjectInitialized(): bool
+ public function isLazyObjectInitialized(bool $partial = false): bool
{
if (0 >= ($this->lazyObjectId ?? 0)) {
return true;
diff --git a/src/Symfony/Component/VarExporter/ProxyHelper.php b/src/Symfony/Component/VarExporter/ProxyHelper.php
index 3df1dd14a3a4a..ba24c4afb08a0 100644
--- a/src/Symfony/Component/VarExporter/ProxyHelper.php
+++ b/src/Symfony/Component/VarExporter/ProxyHelper.php
@@ -27,6 +27,9 @@ final class ProxyHelper
*/
public static function generateLazyGhost(\ReflectionClass $class): string
{
+ if (\PHP_VERSION_ID >= 80200 && $class->isReadOnly()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is read-only.', $class->name));
+ }
if ($class->isFinal()) {
throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name));
}
@@ -56,15 +59,12 @@ public static function generateLazyGhost(\ReflectionClass $class): string
}
}
$propertyScopes = self::exportPropertyScopes($class->name);
- $readonly = \PHP_VERSION_ID >= 80200 && $class->isReadOnly() ? 'readonly ' : '';
return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
- private {$readonly}int \$lazyObjectId;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
}
@@ -91,6 +91,9 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
if ($class?->isFinal()) {
throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name));
}
+ if (\PHP_VERSION_ID >= 80200 && $class?->isReadOnly()) {
+ throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is read-only.', $class->name));
+ }
$methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []];
foreach ($interfaces as $interface) {
@@ -176,7 +179,6 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
$methods[$lcName] = " {$signature}\n {\n{$body}\n }";
}
- $readonly = \PHP_VERSION_ID >= 80200 && $class?->isReadOnly() ? 'readonly ' : '';
$types = $interfaces = array_unique(array_column($interfaces, 'name'));
$interfaces[] = LazyObjectInterface::class;
$interfaces = implode(', \\', $interfaces);
@@ -198,9 +200,6 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
{
use \Symfony\Component\VarExporter\LazyProxyTrait;
- private {$readonly}int \$lazyObjectId;
- private {$readonly}{$type} \$lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\\0".self::class."\\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],{$propertyScopes}
diff --git a/src/Symfony/Component/VarExporter/README.md b/src/Symfony/Component/VarExporter/README.md
index 0107f99cae249..7c7a58e298357 100644
--- a/src/Symfony/Component/VarExporter/README.md
+++ b/src/Symfony/Component/VarExporter/README.md
@@ -92,8 +92,6 @@ when accessing a property.
class FooLazyGhost extends Foo
{
use LazyGhostTrait;
-
- private int $lazyObjectId;
}
$foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void {
diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php
index a7dc5d3d73fb4..6cac9ffc03d01 100644
--- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php
+++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildMagicClass.php
@@ -18,6 +18,5 @@ class ChildMagicClass extends MagicClass implements LazyObjectInterface
{
use LazyGhostTrait;
- private int $lazyObjectId;
private int $data = 123;
}
diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php
index 0a131d9bfb5c1..265fb1d318c38 100644
--- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php
+++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/ChildStdClass.php
@@ -17,6 +17,4 @@
class ChildStdClass extends \stdClass implements LazyObjectInterface
{
use LazyGhostTrait;
-
- private int $lazyObjectId;
}
diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php
index 710b0571b1755..a3ce88d14e942 100644
--- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php
+++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/LazyClass.php
@@ -19,7 +19,6 @@ class LazyClass
createLazyGhost as private;
}
- private int $lazyObjectId;
public int $public;
public function __construct(\Closure $initializer)
diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php
index 1c1127d546399..f755235831b1e 100644
--- a/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php
+++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/LazyGhost/TestClass.php
@@ -17,7 +17,6 @@ class TestClass extends NoMagicClass
{
use LazyGhostTrait;
- private int $lazyObjectId;
public int $public = 1;
protected int $protected = 2;
protected readonly int $protectedReadonly;
diff --git a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php
index a64747490bd0c..3663217435268 100644
--- a/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php
+++ b/src/Symfony/Component/VarExporter/Tests/LazyGhostTraitTest.php
@@ -248,7 +248,8 @@ public function testPartialInitialization()
$this->assertFalse($instance->isLazyObjectInitialized());
$this->assertSame(123, $instance->public);
$this->assertFalse($instance->isLazyObjectInitialized());
- $this->assertSame(["\0".TestClass::class."\0lazyObjectId", 'public'], array_keys((array) $instance));
+ $this->assertTrue($instance->isLazyObjectInitialized(true));
+ $this->assertSame(['public', "\0".TestClass::class."\0lazyObjectId"], array_keys((array) $instance));
$this->assertSame(1, $counter);
$instance->initializeLazyObject();
@@ -330,4 +331,89 @@ public function testReflectionPropertyGetValue()
$this->assertSame(-3, $r->getValue($obj));
}
+
+ public function testFullPartialInitialization()
+ {
+ $counter = 0;
+ $initializer = static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) {
+ return 234;
+ };
+ $instance = ChildTestClass::createLazyGhost([
+ 'public' => $initializer,
+ 'publicReadonly' => $initializer,
+ "\0*\0protected" => $initializer,
+ "\0" => function ($obj, $defaults) use (&$instance, &$counter) {
+ $counter += 1000;
+ $this->assertSame($instance, $obj);
+
+ return [
+ 'public' => 345,
+ 'publicReadonly' => 456,
+ "\0*\0protected" => 567,
+ ] + $defaults;
+ },
+ ]);
+
+ $this->assertSame($instance, $instance->initializeLazyObject());
+ $this->assertSame(345, $instance->public);
+ $this->assertSame(456, $instance->publicReadonly);
+ $this->assertSame(6, ((array) $instance)["\0".ChildTestClass::class."\0private"]);
+ $this->assertSame(3, ((array) $instance)["\0".TestClass::class."\0private"]);
+ $this->assertSame(1000, $counter);
+ }
+
+ public function testPartialInitializationFallback()
+ {
+ $counter = 0;
+ $instance = ChildTestClass::createLazyGhost([
+ "\0" => function ($obj) use (&$instance, &$counter) {
+ $counter += 1000;
+ $this->assertSame($instance, $obj);
+
+ return [
+ 'public' => 345,
+ 'publicReadonly' => 456,
+ "\0*\0protected" => 567,
+ ];
+ },
+ ], []);
+
+ $this->assertSame(345, $instance->public);
+ $this->assertSame(456, $instance->publicReadonly);
+ $this->assertSame(567, ((array) $instance)["\0*\0protected"]);
+ $this->assertSame(1000, $counter);
+ }
+
+ public function testFullInitializationAfterPartialInitialization()
+ {
+ $counter = 0;
+ $initializer = static function (ChildTestClass $instance, string $property, ?string $scope, mixed $default) use (&$counter) {
+ ++$counter;
+
+ return 234;
+ };
+ $instance = ChildTestClass::createLazyGhost([
+ 'public' => $initializer,
+ 'publicReadonly' => $initializer,
+ "\0*\0protected" => $initializer,
+ "\0" => function ($obj, $defaults) use (&$instance, &$counter) {
+ $counter += 1000;
+ $this->assertSame($instance, $obj);
+
+ return [
+ 'public' => 345,
+ 'publicReadonly' => 456,
+ "\0*\0protected" => 567,
+ ] + $defaults;
+ },
+ ]);
+
+ $this->assertSame(234, $instance->public);
+ $this->assertSame($instance, $instance->initializeLazyObject());
+ $this->assertSame(234, $instance->public);
+ $this->assertSame(456, $instance->publicReadonly);
+ $this->assertSame(6, ((array) $instance)["\0".ChildTestClass::class."\0private"]);
+ $this->assertSame(3, ((array) $instance)["\0".TestClass::class."\0private"]);
+ $this->assertSame(1001, $counter);
+ }
}
diff --git a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php
index c44c3b9cac6be..04497f02b34ae 100644
--- a/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php
+++ b/src/Symfony/Component/VarExporter/Tests/LazyProxyTraitTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\VarExporter\Tests;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\VarExporter\Exception\LogicException;
use Symfony\Component\VarExporter\LazyProxyTrait;
use Symfony\Component\VarExporter\ProxyHelper;
use Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\FinalPublicClass;
@@ -237,9 +238,9 @@ public function setFoo($foo): static
*/
public function testReadOnlyClass()
{
- $proxy = $this->createLazyProxy(ReadOnlyClass::class, fn () => new ReadOnlyClass(123));
-
- $this->assertSame(123, $proxy->foo);
+ $this->expectException(LogicException::class);
+ $this->expectExceptionMessage('Cannot generate lazy proxy: class "Symfony\Component\VarExporter\Tests\Fixtures\LazyProxy\ReadOnlyClass" is read-only.');
+ $this->createLazyProxy(ReadOnlyClass::class, fn () => new ReadOnlyClass(123));
}
public function testLazyDecoratorClass()
@@ -249,9 +250,6 @@ public function testLazyDecoratorClass()
createLazyProxy as private;
}
- private int $lazyObjectId;
- private parent $lazyObjectReal;
-
public function __construct()
{
self::createLazyProxy(fn () => new TestClass((object) ['foo' => 123]), $this);
diff --git a/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php b/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php
index 455a7b974ec55..260980d94f870 100644
--- a/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php
+++ b/src/Symfony/Component/VarExporter/Tests/ProxyHelperTest.php
@@ -66,9 +66,6 @@ public function testGenerateLazyProxy()
{
use \Symfony\Component\VarExporter\LazyProxyTrait;
- private int $lazyObjectId;
- private parent $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],
@@ -119,9 +116,6 @@ public function testGenerateLazyProxyForInterfaces()
{
use \Symfony\Component\VarExporter\LazyProxyTrait;
- private int $lazyObjectId;
- private \Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface1&\Symfony\Component\VarExporter\Tests\TestForProxyHelperInterface2 $lazyObjectReal;
-
private const LAZY_OBJECT_PROPERTY_SCOPES = [
'lazyObjectReal' => [self::class, 'lazyObjectReal', null],
"\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null],