diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index c392a1a48ab79..2b2960982140f 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -32,7 +32,7 @@ jobs:
mode: low-deps
- php: '8.4'
- php: '8.5'
- #mode: experimental
+ - php: '8.6'
fail-fast: false
runs-on: ubuntu-24.04
@@ -179,7 +179,7 @@ jobs:
echo -e "\n\\e[32mOK\\e[0m $title\\n\\n::endgroup::"
fi
- [[ "${{ matrix.mode }}" = experimental ]] || (exit $ok)
+ exit $ok
}
export -f _run_tests
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 26cdf7f670422..616d1a71958ce 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -34,7 +34,7 @@
'@PHPUnit9x1Migration:risky' => true, // take version from src/Symfony/Bridge/PhpUnit/phpunit.xml.dist#L4
'@Symfony' => true,
'@Symfony:risky' => true,
- 'protected_to_private' => false,
+ 'protected_to_private' => true,
'header_comment' => [
'header' => implode('', $fileHeaderParts),
'validator' => implode('', [
diff --git a/UPGRADE-8.1.md b/UPGRADE-8.1.md
new file mode 100644
index 0000000000000..4aefd06c644b8
--- /dev/null
+++ b/UPGRADE-8.1.md
@@ -0,0 +1,12 @@
+UPGRADE FROM 8.0 to 8.1
+=======================
+
+DependencyInjection
+-------------------
+
+ * Deprecate configuring options `alias`, `parent`, `synthetic`, `file`, `arguments`, `properties`, `configurator` or `calls` when using `from_callable`
+
+FrameworkBundle
+---------------
+
+ * Deprecate setting the `framework.profiler.collect_serializer_data` config option
diff --git a/psalm.xml b/psalm.xml
index a3dd6b8d5e191..0b544dd2abdf2 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -27,24 +27,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/splitsh.json b/splitsh.json
new file mode 100644
index 0000000000000..232189f349bc0
--- /dev/null
+++ b/splitsh.json
@@ -0,0 +1,198 @@
+{
+ "subtrees": {
+ "doctrine-bridge": "src/Symfony/Bridge/Doctrine",
+ "monolog-bridge": "src/Symfony/Bridge/Monolog",
+ "phpunit-bridge": "src/Symfony/Bridge/PhpUnit",
+ "psr-http-message-bridge": "src/Symfony/Bridge/PsrHttpMessage",
+ "twig-bridge": "src/Symfony/Bridge/Twig",
+ "debug-bundle": "src/Symfony/Bundle/DebugBundle",
+ "framework-bundle": "src/Symfony/Bundle/FrameworkBundle",
+ "security-bundle": "src/Symfony/Bundle/SecurityBundle",
+ "twig-bundle": "src/Symfony/Bundle/TwigBundle",
+ "web-profiler-bundle": "src/Symfony/Bundle/WebProfilerBundle",
+ "asset": "src/Symfony/Component/Asset",
+ "asset-mapper": "src/Symfony/Component/AssetMapper",
+ "browser-kit": "src/Symfony/Component/BrowserKit",
+ "cache": "src/Symfony/Component/Cache",
+ "clock": "src/Symfony/Component/Clock",
+ "config": "src/Symfony/Component/Config",
+ "console": "src/Symfony/Component/Console",
+ "css-selector": "src/Symfony/Component/CssSelector",
+ "dependency-injection": "src/Symfony/Component/DependencyInjection",
+ "dom-crawler": "src/Symfony/Component/DomCrawler",
+ "dotenv": "src/Symfony/Component/Dotenv",
+ "emoji": "src/Symfony/Component/Emoji",
+ "error-handler": "src/Symfony/Component/ErrorHandler",
+ "event-dispatcher": "src/Symfony/Component/EventDispatcher",
+ "expression-language": "src/Symfony/Component/ExpressionLanguage",
+ "filesystem": "src/Symfony/Component/Filesystem",
+ "finder": "src/Symfony/Component/Finder",
+ "form": "src/Symfony/Component/Form",
+ "html-sanitizer": "src/Symfony/Component/HtmlSanitizer",
+ "http-client": "src/Symfony/Component/HttpClient",
+ "http-foundation": "src/Symfony/Component/HttpFoundation",
+ "http-kernel": "src/Symfony/Component/HttpKernel",
+ "intl": "src/Symfony/Component/Intl",
+ "json-path": "src/Symfony/Component/JsonPath",
+ "json-streamer": "src/Symfony/Component/JsonStreamer",
+ "ldap": "src/Symfony/Component/Ldap",
+ "lock": {
+ "prefixes": [{ "from": "src/Symfony/Component/Lock", "to": "", "excludes": ["Bridge"] }]
+ },
+ "dynamo-db-lock": "src/Symfony/Component/Lock/Bridge/DynamoDb",
+ "mailer": {
+ "prefixes": [{ "from": "src/Symfony/Component/Mailer", "to": "", "excludes": ["Bridge"] }]
+ },
+ "aha-send-mailer": "src/Symfony/Component/Mailer/Bridge/AhaSend",
+ "amazon-mailer": "src/Symfony/Component/Mailer/Bridge/Amazon",
+ "azure-mailer": "src/Symfony/Component/Mailer/Bridge/Azure",
+ "brevo-mailer": "src/Symfony/Component/Mailer/Bridge/Brevo",
+ "google-mailer": "src/Symfony/Component/Mailer/Bridge/Google",
+ "infobip-mailer": "src/Symfony/Component/Mailer/Bridge/Infobip",
+ "mail-pace-mailer": "src/Symfony/Component/Mailer/Bridge/MailPace",
+ "mailchimp-mailer": "src/Symfony/Component/Mailer/Bridge/Mailchimp",
+ "mailer-send-mailer": "src/Symfony/Component/Mailer/Bridge/MailerSend",
+ "mailgun-mailer": "src/Symfony/Component/Mailer/Bridge/Mailgun",
+ "mailjet-mailer": "src/Symfony/Component/Mailer/Bridge/Mailjet",
+ "mailomat-mailer": "src/Symfony/Component/Mailer/Bridge/Mailomat",
+ "mailtrap-mailer": "src/Symfony/Component/Mailer/Bridge/Mailtrap",
+ "microsoft-graph-mailer": "src/Symfony/Component/Mailer/Bridge/MicrosoftGraph",
+ "postal-mailer": "src/Symfony/Component/Mailer/Bridge/Postal",
+ "postmark-mailer": "src/Symfony/Component/Mailer/Bridge/Postmark",
+ "resend-mailer": "src/Symfony/Component/Mailer/Bridge/Resend",
+ "scaleway-mailer": "src/Symfony/Component/Mailer/Bridge/Scaleway",
+ "sendgrid-mailer": "src/Symfony/Component/Mailer/Bridge/Sendgrid",
+ "sweego-mailer": "src/Symfony/Component/Mailer/Bridge/Sweego",
+ "messenger": {
+ "prefixes": [{ "from": "src/Symfony/Component/Messenger", "to": "", "excludes": ["Bridge"] }]
+ },
+ "amazon-sqs-messenger": "src/Symfony/Component/Messenger/Bridge/AmazonSqs",
+ "amqp-messenger": "src/Symfony/Component/Messenger/Bridge/Amqp",
+ "beanstalkd-messenger": "src/Symfony/Component/Messenger/Bridge/Beanstalkd",
+ "doctrine-messenger": "src/Symfony/Component/Messenger/Bridge/Doctrine",
+ "redis-messenger": "src/Symfony/Component/Messenger/Bridge/Redis",
+ "mime": "src/Symfony/Component/Mime",
+ "notifier": {
+ "prefixes": [{ "from": "src/Symfony/Component/Notifier", "to": "", "excludes": ["Bridge"] }]
+ },
+ "all-my-sms-notifier": "src/Symfony/Component/Notifier/Bridge/AllMySms",
+ "amazon-sns-notifier": "src/Symfony/Component/Notifier/Bridge/AmazonSns",
+ "bandwidth-notifier": "src/Symfony/Component/Notifier/Bridge/Bandwidth",
+ "bluesky-notifier": "src/Symfony/Component/Notifier/Bridge/Bluesky",
+ "brevo-notifier": "src/Symfony/Component/Notifier/Bridge/Brevo",
+ "chatwork-notifier": "src/Symfony/Component/Notifier/Bridge/Chatwork",
+ "click-send-notifier": "src/Symfony/Component/Notifier/Bridge/ClickSend",
+ "clickatell-notifier": "src/Symfony/Component/Notifier/Bridge/Clickatell",
+ "contact-everyone-notifier": "src/Symfony/Component/Notifier/Bridge/ContactEveryone",
+ "discord-notifier": "src/Symfony/Component/Notifier/Bridge/Discord",
+ "engagespot-notifier": "src/Symfony/Component/Notifier/Bridge/Engagespot",
+ "esendex-notifier": "src/Symfony/Component/Notifier/Bridge/Esendex",
+ "expo-notifier": "src/Symfony/Component/Notifier/Bridge/Expo",
+ "fake-chat-notifier": "src/Symfony/Component/Notifier/Bridge/FakeChat",
+ "fake-sms-notifier": "src/Symfony/Component/Notifier/Bridge/FakeSms",
+ "firebase-notifier": "src/Symfony/Component/Notifier/Bridge/Firebase",
+ "forty-six-elks-notifier": "src/Symfony/Component/Notifier/Bridge/FortySixElks",
+ "free-mobile-notifier": "src/Symfony/Component/Notifier/Bridge/FreeMobile",
+ "gateway-api-notifier": "src/Symfony/Component/Notifier/Bridge/GatewayApi",
+ "go-ip-notifier": "src/Symfony/Component/Notifier/Bridge/GoIp",
+ "google-chat-notifier": "src/Symfony/Component/Notifier/Bridge/GoogleChat",
+ "infobip-notifier": "src/Symfony/Component/Notifier/Bridge/Infobip",
+ "iqsms-notifier": "src/Symfony/Component/Notifier/Bridge/Iqsms",
+ "isendpro-notifier": "src/Symfony/Component/Notifier/Bridge/Isendpro",
+ "joli-notif-notifier": "src/Symfony/Component/Notifier/Bridge/JoliNotif",
+ "kaz-info-teh-notifier": "src/Symfony/Component/Notifier/Bridge/KazInfoTeh",
+ "light-sms-notifier": "src/Symfony/Component/Notifier/Bridge/LightSms",
+ "line-bot-notifier": "src/Symfony/Component/Notifier/Bridge/LineBot",
+ "line-notify-notifier": "src/Symfony/Component/Notifier/Bridge/LineNotify",
+ "linked-in-notifier": "src/Symfony/Component/Notifier/Bridge/LinkedIn",
+ "lox24-notifier": "src/Symfony/Component/Notifier/Bridge/Lox24",
+ "mailjet-notifier": "src/Symfony/Component/Notifier/Bridge/Mailjet",
+ "mastodon-notifier": "src/Symfony/Component/Notifier/Bridge/Mastodon",
+ "matrix-notifier": "src/Symfony/Component/Notifier/Bridge/Matrix",
+ "mattermost-notifier": "src/Symfony/Component/Notifier/Bridge/Mattermost",
+ "mercure-notifier": "src/Symfony/Component/Notifier/Bridge/Mercure",
+ "message-bird-notifier": "src/Symfony/Component/Notifier/Bridge/MessageBird",
+ "message-media-notifier": "src/Symfony/Component/Notifier/Bridge/MessageMedia",
+ "microsoft-teams-notifier": "src/Symfony/Component/Notifier/Bridge/MicrosoftTeams",
+ "mobyt-notifier": "src/Symfony/Component/Notifier/Bridge/Mobyt",
+ "novu-notifier": "src/Symfony/Component/Notifier/Bridge/Novu",
+ "ntfy-notifier": "src/Symfony/Component/Notifier/Bridge/Ntfy",
+ "octopush-notifier": "src/Symfony/Component/Notifier/Bridge/Octopush",
+ "one-signal-notifier": "src/Symfony/Component/Notifier/Bridge/OneSignal",
+ "orange-sms-notifier": "src/Symfony/Component/Notifier/Bridge/OrangeSms",
+ "ovh-cloud-notifier": "src/Symfony/Component/Notifier/Bridge/OvhCloud",
+ "pager-duty-notifier": "src/Symfony/Component/Notifier/Bridge/PagerDuty",
+ "plivo-notifier": "src/Symfony/Component/Notifier/Bridge/Plivo",
+ "primotexto-notifier": "src/Symfony/Component/Notifier/Bridge/Primotexto",
+ "pushover-notifier": "src/Symfony/Component/Notifier/Bridge/Pushover",
+ "pushy-notifier": "src/Symfony/Component/Notifier/Bridge/Pushy",
+ "redlink-notifier": "src/Symfony/Component/Notifier/Bridge/Redlink",
+ "ring-central-notifier": "src/Symfony/Component/Notifier/Bridge/RingCentral",
+ "rocket-chat-notifier": "src/Symfony/Component/Notifier/Bridge/RocketChat",
+ "sendberry-notifier": "src/Symfony/Component/Notifier/Bridge/Sendberry",
+ "sevenio-notifier": "src/Symfony/Component/Notifier/Bridge/Sevenio",
+ "simple-textin-notifier": "src/Symfony/Component/Notifier/Bridge/SimpleTextin",
+ "sinch-notifier": "src/Symfony/Component/Notifier/Bridge/Sinch",
+ "sipgate-notifier": "src/Symfony/Component/Notifier/Bridge/Sipgate",
+ "slack-notifier": "src/Symfony/Component/Notifier/Bridge/Slack",
+ "sms-biuras-notifier": "src/Symfony/Component/Notifier/Bridge/SmsBiuras",
+ "sms-factor-notifier": "src/Symfony/Component/Notifier/Bridge/SmsFactor",
+ "sms-sluzba-notifier": "src/Symfony/Component/Notifier/Bridge/SmsSluzba",
+ "smsapi-notifier": "src/Symfony/Component/Notifier/Bridge/Smsapi",
+ "smsbox-notifier": "src/Symfony/Component/Notifier/Bridge/Smsbox",
+ "smsc-notifier": "src/Symfony/Component/Notifier/Bridge/Smsc",
+ "smsense-notifier": "src/Symfony/Component/Notifier/Bridge/Smsense",
+ "smsmode-notifier": "src/Symfony/Component/Notifier/Bridge/Smsmode",
+ "spot-hit-notifier": "src/Symfony/Component/Notifier/Bridge/SpotHit",
+ "sweego-notifier": "src/Symfony/Component/Notifier/Bridge/Sweego",
+ "telegram-notifier": "src/Symfony/Component/Notifier/Bridge/Telegram",
+ "telnyx-notifier": "src/Symfony/Component/Notifier/Bridge/Telnyx",
+ "termii-notifier": "src/Symfony/Component/Notifier/Bridge/Termii",
+ "turbo-sms-notifier": "src/Symfony/Component/Notifier/Bridge/TurboSms",
+ "twilio-notifier": "src/Symfony/Component/Notifier/Bridge/Twilio",
+ "twitter-notifier": "src/Symfony/Component/Notifier/Bridge/Twitter",
+ "unifonic-notifier": "src/Symfony/Component/Notifier/Bridge/Unifonic",
+ "vonage-notifier": "src/Symfony/Component/Notifier/Bridge/Vonage",
+ "yunpian-notifier": "src/Symfony/Component/Notifier/Bridge/Yunpian",
+ "zendesk-notifier": "src/Symfony/Component/Notifier/Bridge/Zendesk",
+ "zulip-notifier": "src/Symfony/Component/Notifier/Bridge/Zulip",
+ "object-mapper": "src/Symfony/Component/ObjectMapper",
+ "options-resolver": "src/Symfony/Component/OptionsResolver",
+ "password-hasher": "src/Symfony/Component/PasswordHasher",
+ "process": "src/Symfony/Component/Process",
+ "property-access": "src/Symfony/Component/PropertyAccess",
+ "property-info": "src/Symfony/Component/PropertyInfo",
+ "rate-limiter": "src/Symfony/Component/RateLimiter",
+ "remote-event": "src/Symfony/Component/RemoteEvent",
+ "routing": "src/Symfony/Component/Routing",
+ "runtime": "src/Symfony/Component/Runtime",
+ "scheduler": "src/Symfony/Component/Scheduler",
+ "security-core": "src/Symfony/Component/Security/Core",
+ "security-csrf": "src/Symfony/Component/Security/Csrf",
+ "security-http": "src/Symfony/Component/Security/Http",
+ "semaphore": "src/Symfony/Component/Semaphore",
+ "serializer": "src/Symfony/Component/Serializer",
+ "stopwatch": "src/Symfony/Component/Stopwatch",
+ "string": "src/Symfony/Component/String",
+ "translation": {
+ "prefixes": [{ "from": "src/Symfony/Component/Translation", "to": "", "excludes": ["Bridge"] }]
+ },
+ "crowdin-translation-provider": "src/Symfony/Component/Translation/Bridge/Crowdin",
+ "loco-translation-provider": "src/Symfony/Component/Translation/Bridge/Loco",
+ "lokalise-translation-provider": "src/Symfony/Component/Translation/Bridge/Lokalise",
+ "phrase-translation-provider": "src/Symfony/Component/Translation/Bridge/Phrase",
+ "type-info": "src/Symfony/Component/TypeInfo",
+ "uid": "src/Symfony/Component/Uid",
+ "validator": "src/Symfony/Component/Validator",
+ "var-dumper": "src/Symfony/Component/VarDumper",
+ "var-exporter": "src/Symfony/Component/VarExporter",
+ "web-link": "src/Symfony/Component/WebLink",
+ "webhook": "src/Symfony/Component/Webhook",
+ "workflow": "src/Symfony/Component/Workflow",
+ "yaml": "src/Symfony/Component/Yaml",
+ "contracts": "src/Symfony/Contracts"
+ },
+ "defaults": {
+ "git_constraint": "<1.8.2"
+ }
+}
diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
index f62628667798b..76972606a465b 100644
--- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
+++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php
@@ -276,6 +276,7 @@
if ($PHPUNIT_REMOVE_RETURN_TYPEHINT) {
$alteredCode = preg_replace('/^ ((?:protected|public)(?: static)? function \w+\(\)): void/m', ' $1', $alteredCode);
}
+ file_put_contents($alteredFile, $alteredCode);
// Mutate Assert code
$alteredCode = file_get_contents($alteredFile = './src/Framework/Assert.php');
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
index f1856f066e25f..4e2def28cb1ab 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
@@ -285,7 +285,7 @@
'%name%': name,
'%id%': id,
}) %}
- {%- else -%}
+ {%- elseif 'button' not in block_prefixes or label is not same as(false) -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
diff --git a/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php b/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php
index bd8123a3e0d8a..c9e9d940d0e1e 100644
--- a/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php
+++ b/src/Symfony/Bridge/Twig/Test/FormLayoutTestCase.php
@@ -17,7 +17,7 @@
use Symfony\Component\Form\FormRendererInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Test\FormIntegrationTestCase;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Twig\Environment;
use Twig\Extension\ExtensionInterface;
use Twig\Loader\FilesystemLoader;
@@ -45,7 +45,7 @@ protected function setUp(): void
}
$rendererEngine = new TwigRendererEngine($this->getThemes(), $environment);
- $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class));
+ $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
}
diff --git a/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php b/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php
index 6d8943a9605d4..bd5ad49173999 100644
--- a/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php
+++ b/src/Symfony/Bridge/Twig/Test/Traits/RuntimeLoaderProvider.php
@@ -11,18 +11,17 @@
namespace Symfony\Bridge\Twig\Test\Traits;
+use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Form\FormRenderer;
use Twig\Environment;
-use Twig\RuntimeLoader\RuntimeLoaderInterface;
+use Twig\RuntimeLoader\ContainerRuntimeLoader;
trait RuntimeLoaderProvider
{
protected function registerTwigRuntimeLoader(Environment $environment, FormRenderer $renderer): void
{
- $loader = $this->createMock(RuntimeLoaderInterface::class);
- $loader->expects($this->any())->method('load')->willReturnMap([
- ['Symfony\Component\Form\FormRenderer', $renderer],
- ]);
- $environment->addRuntimeLoader($loader);
+ $environment->addRuntimeLoader(new ContainerRuntimeLoader(new ServiceLocator([
+ FormRenderer::class => fn () => $renderer,
+ ])));
}
}
diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php
index 697acffa106ca..771c0af83541c 100644
--- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php
@@ -17,11 +17,12 @@
use Symfony\Bridge\Twig\AppVariable;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
-use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\HttpFoundation\Session\Session;
-use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
-use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
-use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
+use Symfony\Component\Security\Core\Authentication\Token\NullToken;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Translation\LocaleSwitcher;
class AppVariableTest extends TestCase
@@ -59,9 +60,9 @@ public function testEnvironment()
#[RunInSeparateProcess]
public function testGetSession()
{
- $request = $this->createMock(Request::class);
- $request->method('hasSession')->willReturn(true);
- $request->method('getSession')->willReturn($session = new Session());
+ $session = new Session();
+ $request = new Request();
+ $request->setSession($session);
$this->setRequestStack($request);
@@ -84,28 +85,25 @@ public function testGetRequest()
public function testGetToken()
{
- $tokenStorage = $this->createMock(TokenStorageInterface::class);
+ $tokenStorage = new TokenStorage();
$this->appVariable->setTokenStorage($tokenStorage);
- $token = $this->createMock(TokenInterface::class);
- $tokenStorage->method('getToken')->willReturn($token);
+ $token = new NullToken();
+ $tokenStorage->setToken($token);
$this->assertEquals($token, $this->appVariable->getToken());
}
public function testGetUser()
{
- $this->setTokenStorage($user = $this->createMock(UserInterface::class));
+ $this->setTokenStorage($user = new InMemoryUser('john', 'password'));
$this->assertEquals($user, $this->appVariable->getUser());
}
public function testGetLocale()
{
- $localeSwitcher = $this->createMock(LocaleSwitcher::class);
- $this->appVariable->setLocaleSwitcher($localeSwitcher);
-
- $localeSwitcher->method('getLocale')->willReturn('fr');
+ $this->appVariable->setLocaleSwitcher(new LocaleSwitcher('fr', []));
self::assertEquals('fr', $this->appVariable->getLocale());
}
@@ -119,16 +117,14 @@ public function testGetEnabledLocales()
public function testGetTokenWithNoToken()
{
- $tokenStorage = $this->createMock(TokenStorageInterface::class);
- $this->appVariable->setTokenStorage($tokenStorage);
+ $this->appVariable->setTokenStorage(new TokenStorage());
$this->assertNull($this->appVariable->getToken());
}
public function testGetUserWithNoToken()
{
- $tokenStorage = $this->createMock(TokenStorageInterface::class);
- $this->appVariable->setTokenStorage($tokenStorage);
+ $this->appVariable->setTokenStorage(new TokenStorage());
$this->assertNull($this->appVariable->getUser());
}
@@ -298,13 +294,11 @@ protected function setRequestStack(?Request $request)
protected function setTokenStorage($user)
{
- $tokenStorage = $this->createMock(TokenStorageInterface::class);
+ $tokenStorage = new TokenStorage();
$this->appVariable->setTokenStorage($tokenStorage);
- $token = $this->createMock(TokenInterface::class);
- $tokenStorage->method('getToken')->willReturn($token);
-
- $token->method('getUser')->willReturn($user);
+ $token = new UsernamePasswordToken($user, 'main');
+ $tokenStorage->setToken($token);
}
private function setFlashMessages($sessionHasStarted = true)
@@ -314,16 +308,19 @@ private function setFlashMessages($sessionHasStarted = true)
'warning' => ['Warning #1 message'],
'error' => ['Error #1 message', 'Error #2 message'],
];
- $flashBag = new FlashBag();
- $flashBag->initialize($flashMessages);
- $session = $this->createMock(Session::class);
- $session->method('isStarted')->willReturn($sessionHasStarted);
- $session->method('getFlashBag')->willReturn($flashBag);
+ $storage = new MockArraySessionStorage();
+ $storage->setSessionData([
+ '_symfony_flashes' => $flashMessages,
+ ]);
+ $session = new Session($storage);
+
+ if ($sessionHasStarted) {
+ $session->start();
+ }
- $request = $this->createMock(Request::class);
- $request->method('hasSession')->willReturn(true);
- $request->method('getSession')->willReturn($session);
+ $request = new Request();
+ $request->setSession($session);
$this->setRequestStack($request);
return $flashMessages;
diff --git a/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php
index 9febc61e61887..6837a84ca819a 100644
--- a/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php
@@ -25,7 +25,6 @@ public function testFallbackToNativeRendererIfDebugOn()
{
$exception = new \Exception();
- $twig = $this->createMock(Environment::class);
$nativeRenderer = $this->createMock(HtmlErrorRenderer::class);
$nativeRenderer
->expects($this->once())
@@ -33,7 +32,7 @@ public function testFallbackToNativeRendererIfDebugOn()
->with($exception)
;
- (new TwigErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception());
+ (new TwigErrorRenderer(new Environment(new ArrayLoader()), $nativeRenderer, true))->render(new \Exception());
}
public function testFallbackToNativeRendererIfCustomTemplateNotFound()
diff --git a/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php b/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php
index 478f285eba5e6..2b5bda9533fcb 100644
--- a/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/EventListener/TemplateAttributeListenerTest.php
@@ -45,7 +45,7 @@ public function testAttribute()
;
$request = new Request();
- $kernel = $this->createMock(HttpKernelInterface::class);
+ $kernel = $this->createStub(HttpKernelInterface::class);
$controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], ['Bar'], $request, null);
$listener = new TemplateAttributeListener($twig);
@@ -97,9 +97,11 @@ public function testAttributeWithBlock()
public function testForm()
{
$request = new Request();
- $kernel = $this->createMock(HttpKernelInterface::class);
+ $kernel = $this->createStub(HttpKernelInterface::class);
$controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], [], $request, null);
- $listener = new TemplateAttributeListener($this->createMock(Environment::class));
+ $listener = new TemplateAttributeListener(new Environment(new ArrayLoader([
+ 'templates/foo.html.twig' => '',
+ ])));
$form = $this->createMock(FormInterface::class);
$form->expects($this->once())->method('createView');
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php
index 171d13effbd65..b941d1e1d3fad 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractDivLayoutTestCase.php
@@ -472,7 +472,7 @@ public function testNestedFormError()
public function testCsrf()
{
- $this->csrfTokenManager->expects($this->any())
+ $this->csrfTokenManager
->method('getToken')
->willReturn(new CsrfToken('token_id', 'foo&bar'));
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php
index c7e30f24ae974..95e1fb01823e8 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractLayoutTestCase.php
@@ -12,7 +12,7 @@
namespace Symfony\Bridge\Twig\Tests\Extension;
use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
use Symfony\Bridge\Twig\Test\FormLayoutTestCase;
use Symfony\Component\Form\Extension\Core\Type\PercentType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
@@ -27,7 +27,7 @@
abstract class AbstractLayoutTestCase extends FormLayoutTestCase
{
- protected MockObject&CsrfTokenManagerInterface $csrfTokenManager;
+ protected Stub&CsrfTokenManagerInterface $csrfTokenManager;
protected array $testableFeatures = [];
private string $defaultLocale;
@@ -41,7 +41,7 @@ protected function setUp(): void
$this->defaultLocale = \Locale::getDefault();
\Locale::setDefault('en');
- $this->csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class);
+ $this->csrfTokenManager = $this->createStub(CsrfTokenManagerInterface::class);
parent::setUp();
}
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php
index c3cdb08e547e8..4147a9e3b6cf4 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractTableLayoutTestCase.php
@@ -337,7 +337,7 @@ public function testNestedFormError()
public function testCsrf()
{
- $this->csrfTokenManager->expects($this->any())
+ $this->csrfTokenManager
->method('getToken')
->willReturn(new CsrfToken('token_id', 'foo&bar'));
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php
index b182b59f67aa6..46677e1c8d28a 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php
@@ -19,7 +19,6 @@
use Symfony\Component\VarDumper\VarDumper;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
-use Twig\Loader\LoaderInterface;
class DumpExtensionTest extends TestCase
{
@@ -65,7 +64,7 @@ public static function getDumpTags()
public function testDump($context, $args, $expectedOutput, $debug = true)
{
$extension = new DumpExtension(new VarCloner());
- $twig = new Environment($this->createMock(LoaderInterface::class), [
+ $twig = new Environment(new ArrayLoader(), [
'debug' => $debug,
'cache' => false,
'optimizations' => 0,
@@ -122,7 +121,7 @@ public function testCustomDumper()
''
);
$extension = new DumpExtension(new VarCloner(), $dumper);
- $twig = new Environment($this->createMock(LoaderInterface::class), [
+ $twig = new Environment(new ArrayLoader(), [
'debug' => true,
'cache' => false,
'optimizations' => 0,
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php
index b08d3b31ddcea..148d946914d6f 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php
@@ -16,7 +16,7 @@
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Component\Form\FormRenderer;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
@@ -60,7 +60,7 @@ public function testMoneyWidgetInIso()
'bootstrap_3_layout.html.twig',
'custom_widgets.html.twig',
], $environment);
- $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class));
+ $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
$view = $this->factory
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php
index 4aabc11ee0241..19fb232efc016 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php
@@ -16,7 +16,7 @@
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator;
use Symfony\Component\Form\FormRenderer;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
@@ -65,7 +65,7 @@ public function testMoneyWidgetInIso()
'bootstrap_4_layout.html.twig',
'custom_widgets.html.twig',
], $environment);
- $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class));
+ $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
$view = $this->factory
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php
index 8f1f746fc4594..0b8ddeb114fbb 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap5LayoutTest.php
@@ -18,7 +18,7 @@
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\FormRenderer;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
@@ -67,7 +67,7 @@ public function testMoneyWidgetInIso()
'bootstrap_5_layout.html.twig',
'custom_widgets.html.twig',
], $environment);
- $this->renderer = new FormRenderer($rendererEngine, $this->getMockBuilder(CsrfTokenManagerInterface::class)->getMock());
+ $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
$view = $this->factory
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php
index a334b2e02e057..adc926a281751 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php
@@ -19,7 +19,7 @@
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\FormView;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
@@ -145,7 +145,7 @@ public function testMoneyWidgetInIso()
'form_div_layout.html.twig',
'custom_widgets.html.twig',
], $environment);
- $this->renderer = new FormRenderer($rendererEngine, $this->createMock(CsrfTokenManagerInterface::class));
+ $this->renderer = new FormRenderer($rendererEngine, new CsrfTokenManager());
$this->registerTwigRuntimeLoader($environment, $this->renderer);
$view = $this->factory
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
index 3e17a28ca05f7..dd20162753acc 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php
@@ -15,6 +15,7 @@
use Symfony\Bridge\Twig\Extension\HttpKernelExtension;
use Symfony\Bridge\Twig\Extension\HttpKernelRuntime;
use Symfony\Bundle\FrameworkBundle\Controller\TemplateController;
+use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
@@ -25,7 +26,7 @@
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Loader\ArrayLoader;
-use Twig\RuntimeLoader\RuntimeLoaderInterface;
+use Twig\RuntimeLoader\ContainerRuntimeLoader;
class HttpKernelExtensionTest extends TestCase
{
@@ -77,10 +78,9 @@ public function testGenerateFragmentUri()
$twig = new Environment($loader, ['debug' => true, 'cache' => false]);
$twig->addExtension(new HttpKernelExtension());
- $loader = $this->createMock(RuntimeLoaderInterface::class);
- $loader->expects($this->any())->method('load')->willReturnMap([
- [HttpKernelRuntime::class, $kernelRuntime],
- ]);
+ $loader = new ContainerRuntimeLoader(new ServiceLocator([
+ HttpKernelRuntime::class => fn () => $kernelRuntime,
+ ]));
$twig->addRuntimeLoader($loader);
$this->assertMatchesRegularExpression('#/_fragment\?_hash=.+&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CController%255CTemplateController%253A%253AtemplateAction$#', $twig->render('index'));
@@ -111,10 +111,9 @@ protected function renderTemplate(FragmentHandler $renderer, $template = '{{ ren
$twig = new Environment($loader, ['debug' => true, 'cache' => false]);
$twig->addExtension(new HttpKernelExtension());
- $loader = $this->createMock(RuntimeLoaderInterface::class);
- $loader->expects($this->any())->method('load')->willReturnMap([
- ['Symfony\Bridge\Twig\Extension\HttpKernelRuntime', new HttpKernelRuntime($renderer)],
- ]);
+ $loader = new ContainerRuntimeLoader(new ServiceLocator([
+ HttpKernelRuntime::class => fn () => new HttpKernelRuntime($renderer),
+ ]));
$twig->addRuntimeLoader($loader);
return $twig->render('index');
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php
index 26a572e1954f5..61a292dc55c27 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/ImportMapExtensionTest.php
@@ -9,15 +9,16 @@
* file that was distributed with this source code.
*/
-namespace Extension;
+namespace Symfony\Bridge\Twig\Tests\Extension;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\Extension\ImportMapExtension;
use Symfony\Bridge\Twig\Extension\ImportMapRuntime;
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
+use Symfony\Component\DependencyInjection\ServiceLocator;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
-use Twig\RuntimeLoader\RuntimeLoaderInterface;
+use Twig\RuntimeLoader\ContainerRuntimeLoader;
class ImportMapExtensionTest extends TestCase
{
@@ -35,14 +36,10 @@ public function testItRendersTheImportmap()
->willReturn($expected);
$runtime = new ImportMapRuntime($importMapRenderer);
- $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class);
- $mockRuntimeLoader
- ->method('load')
- ->willReturnMap([
- [ImportMapRuntime::class, $runtime],
- ])
- ;
- $twig->addRuntimeLoader($mockRuntimeLoader);
+ $runtimeLoader = new ContainerRuntimeLoader(new ServiceLocator([
+ ImportMapRuntime::class => fn () => $runtime,
+ ]));
+ $twig->addRuntimeLoader($runtimeLoader);
$this->assertSame($expected, $twig->render('template'));
}
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php
index def7085ce71fe..13883678886fa 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/RoutingExtensionTest.php
@@ -16,7 +16,7 @@
use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\FilterExpression;
use Twig\Source;
@@ -25,8 +25,8 @@ class RoutingExtensionTest extends TestCase
#[DataProvider('getEscapingTemplates')]
public function testEscaping($template, $mustBeEscaped)
{
- $twig = new Environment($this->createMock(LoaderInterface::class), ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]);
- $twig->addExtension(new RoutingExtension($this->createMock(UrlGeneratorInterface::class)));
+ $twig = new Environment(new ArrayLoader(), ['debug' => true, 'cache' => false, 'autoescape' => 'html', 'optimizations' => 0]);
+ $twig->addExtension(new RoutingExtension($this->createStub(UrlGeneratorInterface::class)));
$nodes = $twig->parse($twig->tokenize(new Source($template, '')));
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php
index b6e1a030dc4c0..7b3d5bf5fdd08 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php
@@ -16,6 +16,7 @@
use Symfony\Bridge\Twig\Extension\SerializerExtension;
use Symfony\Bridge\Twig\Extension\SerializerRuntime;
use Symfony\Bridge\Twig\Tests\Extension\Fixtures\SerializerModelFixture;
+use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\YamlEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
@@ -24,7 +25,7 @@
use Symfony\Component\Serializer\Serializer;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
-use Twig\RuntimeLoader\RuntimeLoaderInterface;
+use Twig\RuntimeLoader\ContainerRuntimeLoader;
/**
* @author Jesse Rushlow
@@ -51,17 +52,13 @@ private function getTwig(string $template): Environment
$meta = new ClassMetadataFactory(new AttributeLoader());
$runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()]));
- $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class);
- $mockRuntimeLoader
- ->method('load')
- ->willReturnMap([
- ['Symfony\Bridge\Twig\Extension\SerializerRuntime', $runtime],
- ])
- ;
+ $runtimeLoader = new ContainerRuntimeLoader(new ServiceLocator([
+ SerializerRuntime::class => fn () => $runtime,
+ ]));
$twig = new Environment(new ArrayLoader(['template' => $template]));
$twig->addExtension(new SerializerExtension());
- $twig->addRuntimeLoader($mockRuntimeLoader);
+ $twig->addRuntimeLoader($runtimeLoader);
return $twig;
}
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php
index 3d3785342b936..c8ee5e07f9d79 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/StopwatchExtensionTest.php
@@ -77,7 +77,7 @@ protected function getStopwatch($events = [])
$expectedName->evaluate($name);
$this->assertSame($expectedCategory, $category);
- return $this->createMock(StopwatchEvent::class);
+ return new StopwatchEvent('1.0');
})
;
@@ -88,7 +88,7 @@ protected function getStopwatch($events = [])
[$expectedName] = array_shift($expectedStopCalls);
$expectedName->evaluate($name);
- return $this->createMock(StopwatchEvent::class);
+ return new StopwatchEvent('1.0');
})
;
diff --git a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php
index 620789bfbca5a..53656241cd18e 100644
--- a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php
@@ -15,7 +15,7 @@
use Symfony\Bridge\Twig\Node\DumpNode;
use Twig\Compiler;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\Variable\ContextVariable;
use Twig\Node\Nodes;
@@ -25,7 +25,7 @@ public function testNoVar()
{
$node = new DumpNode('bar', null, 7);
- $env = new Environment($this->createMock(LoaderInterface::class));
+ $env = new Environment(new ArrayLoader());
$compiler = new Compiler($env);
$expected = <<<'EOTXT'
@@ -49,7 +49,7 @@ public function testIndented()
{
$node = new DumpNode('bar', null, 7);
- $env = new Environment($this->createMock(LoaderInterface::class));
+ $env = new Environment(new ArrayLoader());
$compiler = new Compiler($env);
$expected = <<<'EOTXT'
@@ -77,7 +77,7 @@ public function testOneVar()
$node = new DumpNode('bar', $vars, 7);
- $env = new Environment($this->createMock(LoaderInterface::class));
+ $env = new Environment(new ArrayLoader());
$compiler = new Compiler($env);
$expected = <<<'EOTXT'
@@ -101,7 +101,7 @@ public function testMultiVars()
]);
$node = new DumpNode('bar', $vars, 7);
- $env = new Environment($this->createMock(LoaderInterface::class));
+ $env = new Environment(new ArrayLoader());
$compiler = new Compiler($env);
$expected = <<<'EOTXT'
diff --git a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php
index e581ff284938e..3bbb272f0cb12 100644
--- a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php
@@ -18,7 +18,7 @@
use Symfony\Component\Form\FormRendererEngineInterface;
use Twig\Compiler;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\Variable\ContextVariable;
@@ -55,8 +55,8 @@ public function testCompile()
$node = new FormThemeNode($form, $resources, 0);
- $environment = new Environment($this->createMock(LoaderInterface::class));
- $formRenderer = new FormRenderer($this->createMock(FormRendererEngineInterface::class));
+ $environment = new Environment(new ArrayLoader());
+ $formRenderer = new FormRenderer($this->createStub(FormRendererEngineInterface::class));
$this->registerTwigRuntimeLoader($environment, $formRenderer);
$compiler = new Compiler($environment);
diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php
index 71e5c0c5264f5..6bd209e042938 100644
--- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php
@@ -15,7 +15,7 @@
use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode;
use Twig\Compiler;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\Ternary\ConditionalTernary;
@@ -33,7 +33,7 @@ public function testCompileWidget()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
$this->assertEquals(
\sprintf(
@@ -56,7 +56,7 @@ public function testCompileWidgetWithVariables()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
$this->assertEquals(
\sprintf(
@@ -76,7 +76,7 @@ public function testCompileLabelWithLabel()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
$this->assertEquals(
\sprintf(
@@ -96,7 +96,7 @@ public function testCompileLabelWithNullLabel()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
@@ -118,7 +118,7 @@ public function testCompileLabelWithEmptyStringLabel()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
@@ -139,7 +139,7 @@ public function testCompileLabelWithDefaultLabel()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
$this->assertEquals(
\sprintf(
@@ -163,7 +163,7 @@ public function testCompileLabelWithAttributes()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
@@ -192,7 +192,7 @@ public function testCompileLabelWithLabelAndAttributes()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
$this->assertEquals(
\sprintf(
@@ -219,7 +219,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNull()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
@@ -258,7 +258,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes()
$node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0);
- $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class)));
+ $compiler = new Compiler(new Environment(new ArrayLoader()));
// "label" => null must not be included in the output!
// Otherwise the default label is overwritten with null.
diff --git a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php
index 5a55a0c846bb8..cb82aef864549 100644
--- a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php
@@ -15,7 +15,7 @@
use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Compiler;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\Variable\ContextVariable;
use Twig\Node\TextNode;
@@ -30,7 +30,7 @@ public function testCompileStrict()
$vars = new ContextVariable('foo', 0);
$node = new TransNode($body, null, null, $vars);
- $env = new Environment($this->createMock(LoaderInterface::class), ['strict_variables' => true]);
+ $env = new Environment(new ArrayLoader(), ['strict_variables' => true]);
$compiler = new Compiler($env);
$this->assertEquals(
diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php
index 3d3fd6ec25f57..af08ba3edebe8 100644
--- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationDefaultDomainNodeVisitorTest.php
@@ -16,7 +16,7 @@
use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor;
use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Node;
@@ -28,7 +28,7 @@ class TranslationDefaultDomainNodeVisitorTest extends TestCase
#[DataProvider('getDefaultDomainAssignmentTestData')]
public function testDefaultDomainAssignment(Node $node)
{
- $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
+ $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
$visitor = new TranslationDefaultDomainNodeVisitor();
// visit trans_default_domain tag
@@ -54,7 +54,7 @@ public function testDefaultDomainAssignment(Node $node)
#[DataProvider('getDefaultDomainAssignmentTestData')]
public function testNewModuleWithoutDefaultDomainTag(Node $node)
{
- $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
+ $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
$visitor = new TranslationDefaultDomainNodeVisitor();
// visit trans_default_domain tag
diff --git a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php
index 49a00a539bd0a..c4530c82e5a95 100644
--- a/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/NodeVisitor/TranslationNodeVisitorTest.php
@@ -15,7 +15,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
@@ -29,7 +29,7 @@ class TranslationNodeVisitorTest extends TestCase
#[DataProvider('getMessagesExtractionTestData')]
public function testMessagesExtraction(Node $node, array $expectedMessages)
{
- $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
+ $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
$visitor = new TranslationNodeVisitor();
$visitor->enable();
$visitor->enterNode($node, $env);
diff --git a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php
index f4d79ffbab5b8..0634b6883e713 100644
--- a/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/TokenParser/FormThemeTokenParserTest.php
@@ -16,7 +16,7 @@
use Symfony\Bridge\Twig\Node\FormThemeNode;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Twig\Environment;
-use Twig\Loader\LoaderInterface;
+use Twig\Loader\ArrayLoader;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\Variable\ContextVariable;
@@ -28,7 +28,7 @@ class FormThemeTokenParserTest extends TestCase
#[DataProvider('getTestsForFormTheme')]
public function testCompile($source, $expected)
{
- $env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
+ $env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
$env->addTokenParser(new FormThemeTokenParser());
$source = new Source($source, '');
$stream = $env->tokenize($source);
diff --git a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
index d649df4ef955e..e0432987a5009 100644
--- a/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Translation/TwigExtractorTest.php
@@ -15,11 +15,10 @@
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Bridge\Twig\Translation\TwigExtractor;
+use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Translation\MessageCatalogue;
-use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
-use Twig\Loader\LoaderInterface;
class TwigExtractorTest extends TestCase
{
@@ -28,14 +27,13 @@ class TwigExtractorTest extends TestCase
#[DataProvider('getExtractData')]
public function testExtract($template, $messages)
{
- $loader = $this->createMock(LoaderInterface::class);
- $twig = new Environment($loader, [
+ $twig = new Environment(new ArrayLoader(), [
'strict_variables' => true,
'debug' => true,
'cache' => false,
'autoescape' => false,
]);
- $twig->addExtension(new TranslationExtension($this->createMock(TranslatorInterface::class)));
+ $twig->addExtension(new TranslationExtension(new IdentityTranslator()));
$extractor = new TwigExtractor($twig);
$extractor->setPrefix('prefix');
@@ -96,8 +94,8 @@ public static function getExtractData()
#[DataProvider('resourcesWithSyntaxErrorsProvider')]
public function testExtractSyntaxError($resources, array $messages)
{
- $twig = new Environment($this->createMock(LoaderInterface::class));
- $twig->addExtension(new TranslationExtension($this->createMock(TranslatorInterface::class)));
+ $twig = new Environment(new ArrayLoader());
+ $twig->addExtension(new TranslationExtension(new IdentityTranslator()));
$extractor = new TwigExtractor($twig);
$catalogue = new MessageCatalogue('en');
@@ -124,7 +122,7 @@ public function testExtractWithFiles($resource)
'cache' => false,
'autoescape' => false,
]);
- $twig->addExtension(new TranslationExtension($this->createMock(TranslatorInterface::class)));
+ $twig->addExtension(new TranslationExtension(new IdentityTranslator()));
$extractor = new TwigExtractor($twig);
$catalogue = new MessageCatalogue('en');
diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index be82a64bda2a2..914ff0d1c051b 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Deprecate setting the `framework.profiler.collect_serializer_data` config option
+
8.0
---
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 179d288504805..89f2f83e22069 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -361,7 +361,11 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode): void
->booleanNode('only_exceptions')->defaultFalse()->end()
->booleanNode('only_main_requests')->defaultFalse()->end()
->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end()
- ->enumNode('collect_serializer_data')->values([true])->defaultTrue()->end() // to be @deprecated in Symfony 8.1
+ ->enumNode('collect_serializer_data')
+ ->values([true])
+ ->defaultTrue()
+ ->setDeprecated('symfony/framework-bundle', '8.1', 'Setting the "%path%.%node%" configuration option is deprecated. It will be removed in version 9.0.')
+ ->end()
->end()
->end()
->end()
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 01d64ee611dfe..5e0d5a5ccaab7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -579,7 +579,7 @@ public function load(array $configs, ContainerBuilder $container): void
$this->registerNotifierConfiguration($config['notifier'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook']));
}
- // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered
+ // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered. console is optional
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) {
@@ -992,7 +992,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
$container->getDefinition('profiler_listener')
->addArgument($config['collect_parameter']);
- if (!$container->getParameter('kernel.debug') || !$container->has('debug.stopwatch')) {
+ if (!$container->getParameter('kernel.debug') || !$this->hasConsole() || !$container->has('debug.stopwatch')) {
$container->removeDefinition('console_profiler_listener');
}
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php
index 56cc6a1723fa4..60d0cdf8b54e2 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/profiler.php
@@ -3,7 +3,6 @@
$container->loadFromExtension('framework', [
'profiler' => [
'enabled' => true,
- 'collect_serializer_data' => true,
],
'serializer' => [
'enabled' => true,
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml
index 8cc59ab8f46fe..1cfe686b1eb19 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/profiler.yml
@@ -1,6 +1,5 @@
framework:
profiler:
enabled: true
- collect_serializer_data: true
serializer:
enabled: true
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml
index 7c0a1b2bcf3cc..17e9cba676fe6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml
@@ -13,8 +13,6 @@ framework:
storage_factory_id: session.storage.factory.mock_file
cookie_secure: auto
cookie_samesite: lax
- profiler:
- collect_serializer_data: true
services:
logger: { class: Psr\Log\NullLogger }
diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
index 73ba9206d744a..a92b1800eb20f 100644
--- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Add support for the `clientHints`, `prefetchCache`, and `prerenderCache` `ClearSite-Data` directives
+
8.0
---
diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
index 45476a176f69b..a8e5a6a317429 100644
--- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
+++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
@@ -236,7 +236,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
->beforeNormalization()->ifString()->then(static fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end()
->enumPrototype()
->values([
- '*', 'cache', 'cookies', 'storage', 'executionContexts',
+ '*', 'cache', 'cookies', 'storage', 'clientHints', 'executionContexts', 'prefetchCache', 'prerenderCache',
])
->end()
->end()
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml
index 0601c253a5c1c..14d47a95bb9be 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml
@@ -14,7 +14,6 @@ framework:
cookie_samesite: lax
profiler:
only_exceptions: false
- collect_serializer_data: true
services:
logger: { class: Psr\Log\NullLogger }
diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml
index 1b16f1f027444..38ea5de3c2659 100644
--- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml
+++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/framework.yml
@@ -15,7 +15,6 @@ framework:
cookie_samesite: lax
profiler:
only_exceptions: false
- collect_serializer_data: true
services:
logger: { class: Psr\Log\NullLogger }
diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md
index ed3776b92b16f..20d7ff6b5ecd0 100644
--- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md
@@ -7,6 +7,11 @@ CHANGELOG
* Make `TemplateCacheWarmer` class `final`
* Remove the `base_template_class` config option
+7.4
+---
+
+* Deprecate setting the `exception_controller` config to `null`. This was a legacy opt-out of a deprecation that is a no-op since Symfony 5.0. Remove that setting entirely instead.
+
7.3
---
diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
index dc63335624c30..c148fe59dc7ba 100644
--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
@@ -33,7 +33,7 @@ public function getConfigTreeBuilder(): TreeBuilder
$rootNode
->docUrl('https://symfony.com/doc/{version:major}.{version:minor}/reference/configuration/twig.html', 'symfony/twig-bundle')
- ->end();
+ ;
$this->addFormThemesSection($rootNode);
$this->addGlobalsSection($rootNode);
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php
index 556befe105409..4915fdb176248 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php
+++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php
@@ -52,7 +52,7 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa
{
$config = [
'secret' => 'foo-secret',
- 'profiler' => ['only_exceptions' => false, 'collect_serializer_data' => true],
+ 'profiler' => ['only_exceptions' => false],
'session' => ['handler_id' => null, 'storage_factory_id' => 'session.storage.factory.mock_file', 'cookie-secure' => 'auto', 'cookie-samesite' => 'lax'],
'router' => ['utf8' => true],
];
diff --git a/src/Symfony/Component/BrowserKit/AbstractBrowser.php b/src/Symfony/Component/BrowserKit/AbstractBrowser.php
index 6d26b86987449..559365db89c7d 100644
--- a/src/Symfony/Component/BrowserKit/AbstractBrowser.php
+++ b/src/Symfony/Component/BrowserKit/AbstractBrowser.php
@@ -673,3 +673,5 @@ private function extractHost(string $uri): ?string
return $host;
}
}
+
+// @php-cs-fixer-ignore error_suppression This file is explicitly expected to not silence each of trigger_error calls
diff --git a/src/Symfony/Component/BrowserKit/Cookie.php b/src/Symfony/Component/BrowserKit/Cookie.php
index 7a0cee905d6eb..b487fc5837e20 100644
--- a/src/Symfony/Component/BrowserKit/Cookie.php
+++ b/src/Symfony/Component/BrowserKit/Cookie.php
@@ -43,20 +43,20 @@ class Cookie
/**
* Sets a cookie.
*
- * @param string $name The cookie name
- * @param string|null $value The value of the cookie
- * @param string|null $expires The time the cookie expires
- * @param string|null $path The path on the server in which the cookie will be available on
- * @param string $domain The domain that the cookie is available
- * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
- * @param bool $httponly The cookie httponly flag
- * @param bool $encodedValue Whether the value is encoded or not
- * @param string|null $samesite The cookie samesite attribute
+ * @param string $name The cookie name
+ * @param string|null $value The value of the cookie
+ * @param string|int|null $expires The time the cookie expires
+ * @param string|null $path The path on the server in which the cookie will be available on
+ * @param string $domain The domain that the cookie is available
+ * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
+ * @param bool $httponly The cookie httponly flag
+ * @param bool $encodedValue Whether the value is encoded or not
+ * @param string|null $samesite The cookie samesite attribute
*/
public function __construct(
private string $name,
?string $value,
- ?string $expires = null,
+ string|int|null $expires = null,
?string $path = null,
private string $domain = '',
private bool $secure = false,
diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php
index 4ce8e0cd1948a..1aa5bcdced6f0 100644
--- a/src/Symfony/Component/Cache/CacheItem.php
+++ b/src/Symfony/Component/Cache/CacheItem.php
@@ -197,3 +197,5 @@ private function unpack(): bool
return true;
}
}
+
+// @php-cs-fixer-ignore protected_to_private Friend-level scope access relies on protected properties
diff --git a/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php b/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php
index 47930d5da3706..f8f818282c571 100644
--- a/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php
+++ b/src/Symfony/Component/Cache/Traits/Relay/Relay20Trait.php
@@ -27,7 +27,7 @@ public function delex($key, $options = null): \Relay\Relay|false|int
return $this->initializeLazyObject()->delex(...\func_get_args());
}
- public function digest($key): \Relay\Relay|false|null|string
+ public function digest($key): \Relay\Relay|false|string|null
{
return $this->initializeLazyObject()->digest(...\func_get_args());
}
diff --git a/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php b/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php
index a196f103dc55f..cefb8af28a897 100644
--- a/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php
+++ b/src/Symfony/Component/Cache/Traits/Relay/RelayCluster20Trait.php
@@ -27,7 +27,7 @@ public function delex($key, $options = null): \Relay\Cluster|false|int
return $this->initializeLazyObject()->delex(...\func_get_args());
}
- public function digest($key): \Relay\Cluster|false|null|string
+ public function digest($key): \Relay\Cluster|false|string|null
{
return $this->initializeLazyObject()->digest(...\func_get_args());
}
diff --git a/src/Symfony/Component/Cache/Traits/ValueWrapper.php b/src/Symfony/Component/Cache/Traits/ValueWrapper.php
index 718a23d391efe..a97c8571c2125 100644
--- a/src/Symfony/Component/Cache/Traits/ValueWrapper.php
+++ b/src/Symfony/Component/Cache/Traits/ValueWrapper.php
@@ -79,3 +79,6 @@ public function __unserialize(array $data): void
$this->metadata = $metadata;
}
}
+
+// @php-cs-fixer-ignore long_to_shorthand_operator To prevent false positive causing "Cannot use assign-op operators with string offsets" error
+// @php-cs-fixer-ignore psr_autoloading This class is explicitly having short, special name
diff --git a/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php
index 2548edc320c59..7fffca787bfc1 100644
--- a/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/FloatNodeDefinition.php
@@ -16,7 +16,7 @@
/**
* This class provides a fluent interface for defining a float node.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @extends NumericNodeDefinition
*
diff --git a/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php
index 0a16859e1491e..da28d5a40a220 100644
--- a/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/IntegerNodeDefinition.php
@@ -16,7 +16,7 @@
/**
* This class provides a fluent interface for defining an integer node.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @extends NumericNodeDefinition
*
diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php
index 45cde61b5ceac..c24bc570ac132 100644
--- a/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php
+++ b/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php
@@ -14,7 +14,7 @@
/**
* This class provides a fluent interface for building a node.
*
- * @template TParent of (NodeDefinition&ParentNodeDefinitionInterface)|null
+ * @template TParent of (NodeDefinition&ParentNodeDefinitionInterface)|null = null
*
* @author Johannes M. Schmitt
*/
diff --git a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
index c6f4d27375e23..ad74d3eab7781 100644
--- a/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php
@@ -19,7 +19,7 @@
/**
* This class provides a fluent interface for defining a node.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @author Johannes M. Schmitt
*/
diff --git a/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php
index 47656ea2a96aa..6a8f5b966ee1d 100644
--- a/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/NumericNodeDefinition.php
@@ -16,7 +16,7 @@
/**
* Abstract class that contains common code of integer and float node definitions.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @extends ScalarNodeDefinition
*
diff --git a/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php
index 27e4caf4c7f6d..6409aa69c5445 100644
--- a/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/ScalarNodeDefinition.php
@@ -16,7 +16,7 @@
/**
* This class provides a fluent interface for defining a node.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @extends VariableNodeDefinition
*
diff --git a/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php
index 9ff38d88dc3ad..e14f352adda25 100644
--- a/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/StringNodeDefinition.php
@@ -16,7 +16,7 @@
/**
* This class provides a fluent interface for defining a node.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @extends ScalarNodeDefinition
*
diff --git a/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php
index 6431a70a3a44e..6e6977f6d273b 100644
--- a/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/VariableNodeDefinition.php
@@ -17,7 +17,7 @@
/**
* This class provides a fluent interface for defining a node.
*
- * @template TParent of NodeParentInterface|null
+ * @template TParent of NodeParentInterface|null = null
*
* @extends NodeDefinition
*
diff --git a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
index ed401112404c2..18443f8d003c8 100644
--- a/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
+++ b/src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php
@@ -126,10 +126,11 @@ public function process(ContainerBuilder $container): void
}
if ($description ??= $attribute?->description) {
- $definition->addMethodCall('setDescription', [str_replace('%', '%%', $description)]);
+ $escapedDescription = str_replace('%', '%%', $description);
+ $definition->addMethodCall('setDescription', [$escapedDescription]);
$container->register('.'.$id.'.lazy', LazyCommand::class)
- ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);
+ ->setArguments([$commandName, $aliases, $escapedDescription, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);
$lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
}
diff --git a/src/Symfony/Component/Console/Helper/TableStyle.php b/src/Symfony/Component/Console/Helper/TableStyle.php
index 74ac589256834..bc941efc29548 100644
--- a/src/Symfony/Component/Console/Helper/TableStyle.php
+++ b/src/Symfony/Component/Console/Helper/TableStyle.php
@@ -78,10 +78,11 @@ public function getPaddingChar(): string
*
*
* ╔═══════════════╤══════════════════════════╤══════════════════╗
- * 1 ISBN 2 Title │ Author ║
- * ╠═══════════════╪══════════════════════════╪══════════════════╣
+ * ║ ISBN │ Title │ Author ║
+ * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
+ * ╟───────2───────┼──────────────────────────┼──────────────────╢
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* ╚═══════════════╧══════════════════════════╧══════════════════╝
@@ -102,11 +103,10 @@ public function setHorizontalBorderChars(string $outside, ?string $inside = null
*
*
* ╔═══════════════╤══════════════════════════╤══════════════════╗
- * ║ ISBN │ Title │ Author ║
- * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
+ * 1 ISBN 2 Title │ Author ║
+ * ╠═══════════════╪══════════════════════════╪══════════════════╣
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
- * ╟───────2───────┼──────────────────────────┼──────────────────╢
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* ╚═══════════════╧══════════════════════════╧══════════════════╝
diff --git a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
index 953e5843c24b8..790e31ab0a0ad 100644
--- a/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
+++ b/src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php
@@ -329,6 +329,25 @@ public function testProcessInvokableCommand()
$this->assertStringContainsString('usage1', $command->getUsages()[0]);
}
+ public function testProcessCommandWithDescriptionWithpercentageSigns()
+ {
+ $container = new ContainerBuilder();
+ $container
+ ->register(
+ 'description_with_percentage_signs_command',
+ DescriptionWithPercentageSignsCommand::class,
+ )
+ ->addTag('console.command')
+ ;
+ $pass = new AddConsoleCommandPass();
+ $pass->process($container);
+
+ $command = $container->get('console.command_loader')->get('description-percentage-signs');
+
+ self::assertTrue($container->has('description_with_percentage_signs_command.command'));
+ self::assertSame('Just testing %percentage-signs%', $command->getDescription());
+ }
+
public function testProcessInvokableSignalableCommand()
{
$container = new ContainerBuilder();
@@ -386,6 +405,14 @@ public function __invoke(): void
}
}
+#[AsCommand(name: 'description-percentage-signs', description: 'Just testing %percentage-signs%')]
+class DescriptionWithPercentageSignsCommand
+{
+ public function __invoke(): void
+ {
+ }
+}
+
#[AsCommand(name: 'invokable-signalable', description: 'Just testing', help: 'The %command.name% help content.')]
class InvokableSignalableCommand implements SignalableCommandInterface
{
diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
index 8d492c564a1a4..774ffc7032d69 100644
--- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php
@@ -970,7 +970,7 @@ public function testExitCommandOnInputSIGINT(string $mode)
}
$p = new Process(
- ['php', dirname(__DIR__).'/Fixtures/application_test_sigint.php', $mode],
+ ['php', \dirname(__DIR__).'/Fixtures/application_test_sigint.php', $mode],
timeout: 2, // the process will auto shutdown if not killed by SIGINT, to prevent blocking
);
$p->setPty(true);
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index 343ed02c18226..e2632babb4efd 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Deprecate configuring options `alias`, `parent`, `synthetic`, `file`, `arguments`, `properties`, `configurator` or `calls` when using `from_callable`
+
8.0
---
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php
index 02c8cf16347ee..a4348e934aad7 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php
@@ -39,6 +39,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass
private bool $lazy;
private bool $byConstructor;
private bool $byFactory;
+ private bool $byMultiUseArgument;
private array $definitions;
private array $aliases;
@@ -63,6 +64,7 @@ public function process(ContainerBuilder $container): void
$this->lazy = false;
$this->byConstructor = false;
$this->byFactory = false;
+ $this->byMultiUseArgument = false;
$this->definitions = $container->getDefinitions();
$this->aliases = $container->getAliases();
@@ -85,7 +87,12 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
if ($value instanceof ArgumentInterface) {
$this->lazy = !$this->byFactory || !$value instanceof IteratorArgument;
+ $byMultiUseArgument = $this->byMultiUseArgument;
+ if ($value instanceof IteratorArgument) {
+ $this->byMultiUseArgument = true;
+ }
parent::processValue($value->getValues());
+ $this->byMultiUseArgument = $byMultiUseArgument;
$this->lazy = $lazy;
return $value;
@@ -102,7 +109,8 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
$value,
$this->lazy || ($this->hasProxyDumper && $targetDefinition?->isLazy()),
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(),
- $this->byConstructor
+ $this->byConstructor,
+ $this->byMultiUseArgument
);
if ($inExpression) {
@@ -113,7 +121,9 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
$targetDefinition,
$value,
$this->lazy || $targetDefinition?->isLazy(),
- true
+ true,
+ $this->byConstructor,
+ $this->byMultiUseArgument
);
}
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
index 9a6c4c530eb8f..660bf510fdc03 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
@@ -31,6 +31,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass
private array $notInlinedIds = [];
private array $inlinedIds = [];
private array $notInlinableIds = [];
+ private array $autowireInline = [];
private ?ServiceReferenceGraph $graph = null;
public function __construct(
@@ -86,7 +87,9 @@ public function process(ContainerBuilder $container): void
$remainingInlinedIds[$id] = $id;
} else {
$container->removeDefinition($id);
- $analyzedContainer->removeDefinition($id);
+ if (!isset($this->autowireInline[$id])) {
+ $analyzedContainer->removeDefinition($id);
+ }
}
}
} while ($this->inlinedIds && $this->analyzingPass);
@@ -169,6 +172,8 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
private function isInlineableDefinition(string $id, Definition $definition): bool
{
if (str_starts_with($id, '.autowire_inline.')) {
+ $this->autowireInline[$id] = true;
+
return true;
}
if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) {
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php
index 2544cde95498e..97f9398e1b6b9 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php
@@ -74,7 +74,7 @@ public function clear(): void
/**
* Connects 2 nodes together in the Graph.
*/
- public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void
+ public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false, bool $byMultiUseArgument = false): void
{
if (null === $sourceId || null === $destId) {
return;
@@ -82,7 +82,7 @@ public function connect(?string $sourceId, mixed $sourceValue, ?string $destId,
$sourceNode = $this->createNode($sourceId, $sourceValue);
$destNode = $this->createNode($destId, $destValue);
- $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor);
+ $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor, $byMultiUseArgument);
$sourceNode->addOutEdge($edge);
$destNode->addInEdge($edge);
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php
index 1606ee14ad7f2..bab92e88ed6db 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphEdge.php
@@ -27,6 +27,7 @@ public function __construct(
private bool $lazy = false,
private bool $weak = false,
private bool $byConstructor = false,
+ private bool $byMultiUseArgument = false,
) {
}
@@ -77,4 +78,9 @@ public function isReferencedByConstructor(): bool
{
return $this->byConstructor;
}
+
+ public function isFromMultiUseArgument(): bool
+ {
+ return $this->byMultiUseArgument;
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index 1bb7e91650239..f9488c324b37a 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -443,7 +443,7 @@ private function collectCircularReferences(string $sourceId, array $edges, array
foreach ($edges as $edge) {
$node = $edge->getDestNode();
$id = $node->getId();
- if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) {
+ if (($sourceId === $id && !$edge->isLazy()) || !$node->getValue() instanceof Definition || $edge->isWeak()) {
continue;
}
@@ -684,7 +684,6 @@ private function addServiceInstance(string $id, Definition $definition, bool $is
$asGhostObject = false;
$isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id);
- $instantiation = '';
$lastWitherIndex = null;
foreach ($definition->getMethodCalls() as $k => $call) {
@@ -693,20 +692,26 @@ private function addServiceInstance(string $id, Definition $definition, bool $is
}
}
- if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) {
- $instantiation = \sprintf('$container->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance');
- } elseif (!$isSimpleInstance) {
- $instantiation = '$instance';
- }
+ $shouldShareInline = !$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex;
+ $serviceAccessor = \sprintf('$container->%s[%s]', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id));
+ $return = match (true) {
+ $shouldShareInline && !isset($this->circularReferences[$id]) && $isSimpleInstance => 'return '.$serviceAccessor.' = ',
+ $shouldShareInline && !isset($this->circularReferences[$id]) => $serviceAccessor.' = $instance = ',
+ $shouldShareInline || !$isSimpleInstance => '$instance = ',
+ default => 'return ',
+ };
- $return = '';
- if ($isSimpleInstance) {
- $return = 'return ';
- } else {
- $instantiation .= ' = ';
+ $code = $this->addNewInstance($definition, ' '.$return, $id, $asGhostObject);
+
+ if ($shouldShareInline && isset($this->circularReferences[$id])) {
+ $code .= \sprintf(
+ "\n if (isset(%s)) {\n return %1\$s;\n }\n\n %s%1\$s = \$instance;\n",
+ $serviceAccessor,
+ $isSimpleInstance ? 'return ' : ''
+ );
}
- return $this->addNewInstance($definition, ' '.$return.$instantiation, $id, $asGhostObject);
+ return $code;
}
private function isTrivialInstance(Definition $definition): bool
@@ -1037,7 +1042,7 @@ private function addInlineService(string $id, Definition $definition, ?Definitio
$code = '';
if ($isSimpleInstance = $isRootInstance = null === $inlineDef) {
- foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) {
+ foreach ($this->serviceCalls as $targetId => [, , $byConstructor]) {
if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) {
$code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor);
}
@@ -2200,7 +2205,7 @@ private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool
if (!$value = $edge->getSourceNode()->getValue()) {
continue;
}
- if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) {
+ if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared() || $edge->isFromMultiUseArgument()) {
return false;
}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
index a0572b3a86091..785740df6db87 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
@@ -412,6 +412,10 @@ private function parseDefinition(string $id, array|string|null $service, string
if (isset($service['factory'])) {
throw new InvalidArgumentException(\sprintf('The configuration key "%s" is unsupported for the service "%s" when using "from_callable" in "%s".', $key, $id, $file));
}
+
+ if (isset($service[$key])) {
+ trigger_deprecation('symfony/dependency-injection', '8.1', 'Configuring the "%s" key for the service "%s" when using "from_callable" is deprecated and will throw an "InvalidArgumentException" in 9.0.', $key, $id);
+ }
}
if ('Closure' !== $service['class'] ??= 'Closure') {
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php
index a075b51d41ae2..5b9e52e2a5706 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AnalyzeServiceReferencesPassTest.php
@@ -17,6 +17,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\ExpressionLanguage\Expression;
class AnalyzeServiceReferencesPassTest extends TestCase
{
@@ -204,6 +205,29 @@ public function testProcessDetectsFactoryReferences()
$this->assertCount(1, $graph->getNode('foo')->getInEdges());
}
+ public function testExpressionReferenceKeepsConstructorFlag()
+ {
+ $container = new ContainerBuilder();
+ $container->register('bar');
+ $container
+ ->register('foo')
+ ->addArgument(new Expression('service("bar")'));
+
+ $graph = $this->process($container);
+
+ $edges = $graph->getNode('bar')->getInEdges();
+ $exprEdge = null;
+ foreach ($edges as $edge) {
+ if ('.internal.reference_in_expression' === $edge->getSourceNode()->getId()) {
+ $exprEdge = $edge;
+ break;
+ }
+ }
+
+ $this->assertNotNull($exprEdge, 'Expression edge should exist.');
+ $this->assertTrue($exprEdge->isReferencedByConstructor());
+ }
+
protected function process(ContainerBuilder $container)
{
$pass = new AnalyzeServiceReferencesPass();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
index 6d23d45df4055..49c9caef2296f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
@@ -12,7 +12,6 @@
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
index c801e0e89b637..3b021f7427c6b 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
@@ -16,7 +16,6 @@
require_once __DIR__.'/Fixtures/includes/ProjectExtension.php';
use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\FileLocator;
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index 6f1952ce10b98..786453fdb4fb1 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -53,6 +53,9 @@
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
use Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid;
use Symfony\Component\DependencyInjection\Tests\Compiler\IInterface;
+use Symfony\Component\DependencyInjection\Tests\Compiler\Listener1;
+use Symfony\Component\DependencyInjection\Tests\Compiler\Listener2;
+use Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver;
use Symfony\Component\DependencyInjection\Tests\Compiler\MyCallable;
use Symfony\Component\DependencyInjection\Tests\Compiler\MyFactory;
use Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService;
@@ -320,6 +323,38 @@ public function testDumpAsFilesWithFactoriesInlinedWithTaggedIterator()
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_inlined_factories_with_tagged_iterrator.txt', $dump);
}
+ public function testTaggedIteratorServicesRemainSharedWhenUsedByFactory()
+ {
+ PhpDumperTest_TaggedIteratorService::reset();
+
+ $container = new ContainerBuilder();
+ $container
+ ->register('tagged_service', PhpDumperTest_TaggedIteratorService::class)
+ ->addTag('tag1');
+ $container
+ ->register('wrapper', PhpDumperTest_TaggedIteratorWrapper::class)
+ ->setFactory([new Reference('wrapper_factory'), 'create'])
+ ->setPublic(true);
+ $container
+ ->register('wrapper_factory', PhpDumperTest_TaggedIteratorWrapperFactory::class)
+ ->setArguments([new TaggedIteratorArgument('tag1')]);
+
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Tagged_Iterator_Factory']));
+
+ $compiled = new \Symfony_DI_PhpDumper_Test_Tagged_Iterator_Factory();
+ $wrapper = $compiled->get('wrapper');
+
+ $firstIteration = iterator_to_array($wrapper->getTaggedServices(), false);
+ $secondIteration = iterator_to_array($wrapper->getTaggedServices(), false);
+
+ $this->assertCount(1, $firstIteration);
+ $this->assertSame($firstIteration, $secondIteration);
+ $this->assertSame(1, PhpDumperTest_TaggedIteratorService::$constructed);
+ }
+
public function testDumpAsFilesWithLazyFactoriesInlined()
{
$container = new ContainerBuilder();
@@ -1813,6 +1848,37 @@ public function testReferencingDeprecatedPublicService()
$this->addToAssertionCount(1);
}
+ public function testDecoratedFactoryServiceKeepsReentrantInstance()
+ {
+ ReentrantFactory::reset();
+
+ $container = new ContainerBuilder();
+ $container
+ ->register('decorated_service', \stdClass::class)
+ ->setPublic(true);
+ $container
+ ->register('decorated_service.reentrant', \stdClass::class)
+ ->setPublic(true)
+ ->setDecoratedService('decorated_service')
+ ->setFactory([ReentrantFactory::class, 'create'])
+ ->setArguments([
+ new ServiceLocatorArgument(['decorated_service' => new Reference('decorated_service')]),
+ new Reference('decorated_service.reentrant.inner'),
+ ]);
+
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Reentrant_Service']));
+
+ $compiled = new \Symfony_DI_PhpDumper_Test_Reentrant_Service();
+
+ $service = $compiled->get('decorated_service');
+
+ $this->assertInstanceOf(\stdClass::class, ReentrantFactory::$reentrantInstance);
+ $this->assertSame(ReentrantFactory::$reentrantInstance, $service);
+ }
+
public function testExpressionInFactory()
{
$container = new ContainerBuilder();
@@ -2068,6 +2134,43 @@ public function testInlineAdapterConsumer()
$this->assertNotSame($fooService->factoredFromServiceWithParam, $barService->factoredFromServiceWithParam);
}
+ public function testAutowireCallableWithServiceLocator()
+ {
+ $containerBuilder = new ContainerBuilder();
+
+ $containerBuilder->register(MyInlineService::class, MyInlineService::class);
+ $containerBuilder->register(\stdClass::class, \stdClass::class);
+
+ $containerBuilder->register(Listener1::class, Listener1::class)
+ ->setAutowired(true);
+
+ $containerBuilder->register(Listener2::class, Listener2::class)
+ ->setAutowired(true);
+
+ $containerBuilder->register(ListenerResolver::class, ListenerResolver::class)
+ ->addArgument(new ServiceLocatorArgument([
+ Listener1::class => new TypedReference(Listener1::class, Listener1::class),
+ Listener2::class => new TypedReference(Listener2::class, Listener2::class),
+ ]))
+ ->setPublic(true);
+
+ $containerBuilder->compile();
+
+ $dumper = new PhpDumper($containerBuilder);
+
+ $this->assertStringEqualsFile(self::$fixturesPath.'/php/autowire_callable_with_service_locator.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_AutowireCallable_With_ServiceLocator']));
+
+ require self::$fixturesPath.'/php/autowire_callable_with_service_locator.php';
+
+ $container = new \Symfony_DI_PhpDumper_Test_AutowireCallable_With_ServiceLocator();
+
+ $listenerResolver = $container->get(ListenerResolver::class);
+ $this->assertTrue($listenerResolver->container->has(Listener1::class));
+ $this->assertInstanceOf(\Closure::class, $listenerResolver->container->get(Listener1::class)->closure);
+ $this->assertTrue($listenerResolver->container->has(Listener2::class));
+ $this->assertInstanceOf(\Closure::class, $listenerResolver->container->get(Listener2::class)->closure);
+ }
+
#[DataProvider('getStripCommentsCodes')]
public function testStripComments(string $source, string $expected)
{
@@ -2260,6 +2363,70 @@ public function __construct(
}
}
+class ReentrantFactory
+{
+ public static ?object $reentrantInstance = null;
+ private static bool $shouldReenter = true;
+
+ public static function reset(): void
+ {
+ self::$reentrantInstance = null;
+ self::$shouldReenter = true;
+ }
+
+ public static function create(ServiceLocator $locator, object $inner): \stdClass
+ {
+ if (self::$shouldReenter) {
+ self::$shouldReenter = false;
+ self::$reentrantInstance = $locator->get('decorated_service');
+ self::$shouldReenter = true;
+ }
+
+ return (object) ['inner' => $inner];
+ }
+}
+
+class PhpDumperTest_TaggedIteratorService
+{
+ public static int $constructed = 0;
+
+ public function __construct()
+ {
+ ++self::$constructed;
+ }
+
+ public static function reset(): void
+ {
+ self::$constructed = 0;
+ }
+}
+
+class PhpDumperTest_TaggedIteratorWrapper
+{
+ public function __construct(
+ private iterable $taggedServices,
+ ) {
+ }
+
+ public function getTaggedServices(): iterable
+ {
+ return $this->taggedServices;
+ }
+}
+
+class PhpDumperTest_TaggedIteratorWrapperFactory
+{
+ public function __construct(
+ private iterable $taggedServices,
+ ) {
+ }
+
+ public function create(): PhpDumperTest_TaggedIteratorWrapper
+ {
+ return new PhpDumperTest_TaggedIteratorWrapper($this->taggedServices);
+ }
+}
+
class InlineAdapterConsumer
{
public function __construct(
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
index 73d641f5466f2..72d9050afec0f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
@@ -2,8 +2,10 @@
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
+use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
+use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
use Symfony\Contracts\Service\Attribute\Required;
require __DIR__.'/uniontype_classes.php';
@@ -577,3 +579,29 @@ public function getDependency(): BaseLazyProxyClass
return $this->dep;
}
}
+
+class Listener1
+{
+ public function __construct(
+ #[AutowireCallable(service: MyInlineService::class, method: 'someMethod1')]
+ public \Closure $closure,
+ ) {
+ }
+}
+
+class Listener2
+{
+ public function __construct(
+ #[AutowireCallable(service: MyInlineService::class, method: 'someMethod2')]
+ public \Closure $closure,
+ public \stdClass $someOtherService,
+ ) {
+ }
+}
+
+class ListenerResolver
+{
+ public function __construct(public ContainerInterface $container)
+ {
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php
new file mode 100644
index 0000000000000..4d82a7f5ef777
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/autowire_callable_with_service_locator.php
@@ -0,0 +1,85 @@
+services = $this->privates = [];
+ $this->methodMap = [
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\ListenerResolver' => 'getListenerResolverService',
+ ];
+
+ $this->aliases = [];
+ }
+
+ public function compile(): void
+ {
+ throw new LogicException('You cannot compile a dumped container that was already compiled.');
+ }
+
+ public function isCompiled(): bool
+ {
+ return true;
+ }
+
+ public function getRemovedIds(): array
+ {
+ return [
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1' => true,
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2' => true,
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\MyInlineService' => true,
+ 'stdClass' => true,
+ ];
+ }
+
+ /**
+ * Gets the public 'Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver' shared service.
+ *
+ * @return \Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver
+ */
+ protected static function getListenerResolverService($container)
+ {
+ return $container->services['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\ListenerResolver'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\ListenerResolver(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($container->getService ??= $container->getService(...), [
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1', 'getListener1Service', false],
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2' => ['privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2', 'getListener2Service', false],
+ ], [
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1' => 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1',
+ 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2' => 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2',
+ ]));
+ }
+
+ /**
+ * Gets the private 'Symfony\Component\DependencyInjection\Tests\Compiler\Listener1' shared autowired service.
+ *
+ * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Listener1
+ */
+ protected static function getListener1Service($container)
+ {
+ return $container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener1'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Listener1(($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\MyInlineService'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService())->someMethod1(...));
+ }
+
+ /**
+ * Gets the private 'Symfony\Component\DependencyInjection\Tests\Compiler\Listener2' shared autowired service.
+ *
+ * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Listener2
+ */
+ protected static function getListener2Service($container)
+ {
+ return $container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Listener2'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Listener2(($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\MyInlineService'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\MyInlineService())->someMethod2(...), new \stdClass());
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php
index ccd8d2e0bf63b..216dca434e489 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/callable_adapter_consumer.php
@@ -50,6 +50,6 @@ public function getRemovedIds(): array
*/
protected static function getBarService($container)
{
- return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\CallableAdapterConsumer(new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } });
+ return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\CallableAdapterConsumer(new class(fn () => (new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } });
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php
index 15cab6e1d19de..2f0fb9fff66d4 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php
@@ -54,7 +54,13 @@ protected static function getFooService($container)
return $container->services['foo'];
}
- return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a);
+ $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a);
+
+ if (isset($container->services['foo'])) {
+ return $container->services['foo'];
+ }
+
+ return $container->services['foo'] = $instance;
}
/**
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
index 7c2345f1510c4..ba685d66ada8b 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
@@ -117,7 +117,13 @@ class getBazService extends ProjectServiceContainer
*/
public static function do($container, $lazyLoad = true)
{
- $container->services['baz'] = $instance = new \Baz();
+ $instance = new \Baz();
+
+ if (isset($container->services['baz'])) {
+ return $container->services['baz'];
+ }
+
+ $container->services['baz'] = $instance;
$instance->setFoo(($container->services['foo_with_inline'] ?? $container->load('getFooWithInlineService')));
@@ -339,7 +345,13 @@ class getFooWithInlineService extends ProjectServiceContainer
*/
public static function do($container, $lazyLoad = true)
{
- $container->services['foo_with_inline'] = $instance = new \Foo();
+ $instance = new \Foo();
+
+ if (isset($container->services['foo_with_inline'])) {
+ return $container->services['foo_with_inline'];
+ }
+
+ $container->services['foo_with_inline'] = $instance;
$a = new \Bar();
$a->pub = 'pub';
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
index e56d83936623c..949b5ff826980 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
@@ -162,7 +162,13 @@ protected static function getBar22Service($container)
*/
protected static function getBazService($container)
{
- $container->services['baz'] = $instance = new \Baz();
+ $instance = new \Baz();
+
+ if (isset($container->services['baz'])) {
+ return $container->services['baz'];
+ }
+
+ $container->services['baz'] = $instance;
$instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container)));
@@ -310,7 +316,13 @@ protected static function getFooBarService($container)
*/
protected static function getFooWithInlineService($container)
{
- $container->services['foo_with_inline'] = $instance = new \Foo();
+ $instance = new \Foo();
+
+ if (isset($container->services['foo_with_inline'])) {
+ return $container->services['foo_with_inline'];
+ }
+
+ $container->services['foo_with_inline'] = $instance;
$a = new \Bar();
$a->pub = 'pub';
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt
index eb1d7cd7846b0..6b159b5b4e224 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt
@@ -181,7 +181,13 @@ class ProjectServiceContainer extends Container
*/
protected static function getBazService($container)
{
- $container->services['baz'] = $instance = new \Baz();
+ $instance = new \Baz();
+
+ if (isset($container->services['baz'])) {
+ return $container->services['baz'];
+ }
+
+ $container->services['baz'] = $instance;
$instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container)));
@@ -331,7 +337,13 @@ class ProjectServiceContainer extends Container
*/
protected static function getFooWithInlineService($container)
{
- $container->services['foo_with_inline'] = $instance = new \Foo();
+ $instance = new \Foo();
+
+ if (isset($container->services['foo_with_inline'])) {
+ return $container->services['foo_with_inline'];
+ }
+
+ $container->services['foo_with_inline'] = $instance;
$a = new \Bar();
$a->pub = 'pub';
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php
index ae05d67a54999..7ac7a8e58eac9 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_adawson.php
@@ -78,7 +78,13 @@ protected static function getBusService($container)
*/
protected static function getDbService($container)
{
- $container->services['App\\Db'] = $instance = new \App\Db();
+ $instance = new \App\Db();
+
+ if (isset($container->services['App\\Db'])) {
+ return $container->services['App\\Db'];
+ }
+
+ $container->services['App\\Db'] = $instance;
$instance->schema = ($container->privates['App\\Schema'] ?? self::getSchemaService($container));
@@ -98,6 +104,12 @@ protected static function getSchemaService($container)
return $container->privates['App\\Schema'];
}
- return $container->privates['App\\Schema'] = new \App\Schema($a);
+ $instance = new \App\Schema($a);
+
+ if (isset($container->privates['App\\Schema'])) {
+ return $container->privates['App\\Schema'];
+ }
+
+ return $container->privates['App\\Schema'] = $instance;
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php
index 0c234ac3934c3..986be4bf95da8 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php
@@ -106,7 +106,13 @@ public function getRemovedIds(): array
*/
protected static function getBar2Service($container)
{
- $container->services['bar2'] = $instance = new \BarCircular();
+ $instance = new \BarCircular();
+
+ if (isset($container->services['bar2'])) {
+ return $container->services['bar2'];
+ }
+
+ $container->services['bar2'] = $instance;
$instance->addFoobar(new \FoobarCircular(($container->services['foo2'] ?? self::getFoo2Service($container))));
@@ -154,7 +160,13 @@ protected static function getConnectionService($container)
$b = new \stdClass();
- $container->services['connection'] = $instance = new \stdClass($a, $b);
+ $instance = new \stdClass($a, $b);
+
+ if (isset($container->services['connection'])) {
+ return $container->services['connection'];
+ }
+
+ $container->services['connection'] = $instance;
$b->logger = ($container->services['logger'] ?? self::getLoggerService($container));
@@ -174,7 +186,13 @@ protected static function getConnection2Service($container)
$b = new \stdClass();
- $container->services['connection2'] = $instance = new \stdClass($a, $b);
+ $instance = new \stdClass($a, $b);
+
+ if (isset($container->services['connection2'])) {
+ return $container->services['connection2'];
+ }
+
+ $container->services['connection2'] = $instance;
$c = new \stdClass($instance);
@@ -202,7 +220,13 @@ protected static function getDoctrine_EntityManagerService($container)
}, 1));
$a->flag = 'ok';
- return $container->services['doctrine.entity_manager'] = \FactoryChecker::create($a);
+ $instance = \FactoryChecker::create($a);
+
+ if (isset($container->services['doctrine.entity_manager'])) {
+ return $container->services['doctrine.entity_manager'];
+ }
+
+ return $container->services['doctrine.entity_manager'] = $instance;
}
/**
@@ -234,7 +258,13 @@ protected static function getFoo2Service($container)
return $container->services['foo2'];
}
- return $container->services['foo2'] = new \FooCircular($a);
+ $instance = new \FooCircular($a);
+
+ if (isset($container->services['foo2'])) {
+ return $container->services['foo2'];
+ }
+
+ return $container->services['foo2'] = $instance;
}
/**
@@ -261,7 +291,13 @@ protected static function getFoo5Service($container)
*/
protected static function getFoo6Service($container)
{
- $container->services['foo6'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['foo6'])) {
+ return $container->services['foo6'];
+ }
+
+ $container->services['foo6'] = $instance;
$instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container));
@@ -291,7 +327,13 @@ protected static function getFoobar4Service($container)
*/
protected static function getListener3Service($container)
{
- $container->services['listener3'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['listener3'])) {
+ return $container->services['listener3'];
+ }
+
+ $container->services['listener3'] = $instance;
$instance->manager = ($container->services['manager3'] ?? self::getManager3Service($container));
@@ -311,7 +353,13 @@ protected static function getListener4Service($container)
return $container->services['listener4'];
}
- return $container->services['listener4'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['listener4'])) {
+ return $container->services['listener4'];
+ }
+
+ return $container->services['listener4'] = $instance;
}
/**
@@ -327,7 +375,13 @@ protected static function getLoggerService($container)
return $container->services['logger'];
}
- $container->services['logger'] = $instance = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['logger'])) {
+ return $container->services['logger'];
+ }
+
+ $container->services['logger'] = $instance;
$instance->handler = new \stdClass(($container->services['manager'] ?? self::getManagerService($container)));
@@ -347,7 +401,13 @@ protected static function getManagerService($container)
return $container->services['manager'];
}
- return $container->services['manager'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['manager'])) {
+ return $container->services['manager'];
+ }
+
+ return $container->services['manager'] = $instance;
}
/**
@@ -363,7 +423,13 @@ protected static function getManager2Service($container)
return $container->services['manager2'];
}
- return $container->services['manager2'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['manager2'])) {
+ return $container->services['manager2'];
+ }
+
+ return $container->services['manager2'] = $instance;
}
/**
@@ -379,7 +445,13 @@ protected static function getManager3Service($container, $lazyLoad = true)
return $container->services['manager3'];
}
- return $container->services['manager3'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['manager3'])) {
+ return $container->services['manager3'];
+ }
+
+ return $container->services['manager3'] = $instance;
}
/**
@@ -424,7 +496,13 @@ protected static function getPAService($container)
}
$b = new \stdClass();
- $container->services['pA'] = $instance = new \stdClass($b, $a);
+ $instance = new \stdClass($b, $a);
+
+ if (isset($container->services['pA'])) {
+ return $container->services['pA'];
+ }
+
+ $container->services['pA'] = $instance;
$b->d = ($container->privates['pD'] ?? self::getPDService($container));
@@ -460,7 +538,13 @@ protected static function getSubscriberService($container)
return $container->services['subscriber'];
}
- return $container->services['subscriber'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['subscriber'])) {
+ return $container->services['subscriber'];
+ }
+
+ return $container->services['subscriber'] = $instance;
}
/**
@@ -476,7 +560,13 @@ protected static function getBar6Service($container)
return $container->privates['bar6'];
}
- return $container->privates['bar6'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['bar6'])) {
+ return $container->privates['bar6'];
+ }
+
+ return $container->privates['bar6'] = $instance;
}
/**
@@ -486,7 +576,13 @@ protected static function getBar6Service($container)
*/
protected static function getConnection3Service($container)
{
- $container->privates['connection3'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->privates['connection3'])) {
+ return $container->privates['connection3'];
+ }
+
+ $container->privates['connection3'] = $instance;
$instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))];
@@ -500,7 +596,13 @@ protected static function getConnection3Service($container)
*/
protected static function getConnection4Service($container)
{
- $container->privates['connection4'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->privates['connection4'])) {
+ return $container->privates['connection4'];
+ }
+
+ $container->privates['connection4'] = $instance;
$instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))];
@@ -520,7 +622,13 @@ protected static function getDoctrine_ListenerService($container)
return $container->privates['doctrine.listener'];
}
- return $container->privates['doctrine.listener'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['doctrine.listener'])) {
+ return $container->privates['doctrine.listener'];
+ }
+
+ return $container->privates['doctrine.listener'] = $instance;
}
/**
@@ -546,10 +654,16 @@ protected static function getLevel5Service($container)
*/
protected static function getMailer_TransportService($container)
{
- return $container->privates['mailer.transport'] = (new \FactoryCircular(new RewindableGenerator(function () use ($container) {
+ $instance = (new \FactoryCircular(new RewindableGenerator(function () use ($container) {
yield 0 => ($container->privates['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container));
- yield 1 => self::getMailerInline_TransportFactory_AmazonService($container);
+ yield 1 => ($container->privates['mailer_inline.transport_factory.amazon'] ?? self::getMailerInline_TransportFactory_AmazonService($container));
}, 2)))->create();
+
+ if (isset($container->privates['mailer.transport'])) {
+ return $container->privates['mailer.transport'];
+ }
+
+ return $container->privates['mailer.transport'] = $instance;
}
/**
@@ -561,7 +675,13 @@ protected static function getMailer_TransportFactory_AmazonService($container)
{
$a = new \stdClass();
- $container->privates['mailer.transport_factory.amazon'] = $instance = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['mailer.transport_factory.amazon'])) {
+ return $container->privates['mailer.transport_factory.amazon'];
+ }
+
+ $container->privates['mailer.transport_factory.amazon'] = $instance;
$a->handler = ($container->privates['mailer.transport'] ?? self::getMailer_TransportService($container));
@@ -588,7 +708,7 @@ protected static function getMailerInline_TransportFactory_AmazonService($contai
$a = new \stdClass();
$a->handler = ($container->privates['mailer_inline.mailer'] ?? self::getMailerInline_MailerService($container));
- return new \stdClass($a);
+ return $container->privates['mailer_inline.transport_factory.amazon'] = new \stdClass($a);
}
/**
@@ -604,7 +724,13 @@ protected static function getManager4Service($container, $lazyLoad = true)
return $container->privates['manager4'];
}
- return $container->privates['manager4'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['manager4'])) {
+ return $container->privates['manager4'];
+ }
+
+ return $container->privates['manager4'] = $instance;
}
/**
@@ -614,7 +740,13 @@ protected static function getManager4Service($container, $lazyLoad = true)
*/
protected static function getPCService($container, $lazyLoad = true)
{
- $container->privates['pC'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->privates['pC'])) {
+ return $container->privates['pC'];
+ }
+
+ $container->privates['pC'] = $instance;
$instance->d = ($container->privates['pD'] ?? self::getPDService($container));
@@ -634,6 +766,12 @@ protected static function getPDService($container)
return $container->privates['pD'];
}
- return $container->privates['pD'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['pD'])) {
+ return $container->privates['pD'];
+ }
+
+ return $container->privates['pD'] = $instance;
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
index ae283e556a0da..edb2da14c3bea 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
@@ -106,7 +106,13 @@ public function getRemovedIds(): array
*/
protected static function getBarService($container)
{
- $container->services['bar'] = $instance = new \BarCircular();
+ $instance = new \BarCircular();
+
+ if (isset($container->services['bar'])) {
+ return $container->services['bar'];
+ }
+
+ $container->services['bar'] = $instance;
$instance->addFoobar(($container->services['foobar'] ?? self::getFoobarService($container)));
@@ -142,7 +148,13 @@ protected static function getBar5Service($container)
return $container->services['bar5'];
}
- $container->services['bar5'] = $instance = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['bar5'])) {
+ return $container->services['bar5'];
+ }
+
+ $container->services['bar5'] = $instance;
$instance->foo = $a;
@@ -177,7 +189,13 @@ protected static function getConnectionService($container)
}
$b = new \stdClass();
- $container->services['connection'] = $instance = new \stdClass($a, $b);
+ $instance = new \stdClass($a, $b);
+
+ if (isset($container->services['connection'])) {
+ return $container->services['connection'];
+ }
+
+ $container->services['connection'] = $instance;
$b->logger = ($container->services['logger'] ?? self::getLoggerService($container));
@@ -198,7 +216,13 @@ protected static function getConnection2Service($container)
}
$b = new \stdClass();
- $container->services['connection2'] = $instance = new \stdClass($a, $b);
+ $instance = new \stdClass($a, $b);
+
+ if (isset($container->services['connection2'])) {
+ return $container->services['connection2'];
+ }
+
+ $container->services['connection2'] = $instance;
$c = new \stdClass($instance);
$c->handler2 = new \stdClass(($container->services['manager2'] ?? self::getManager2Service($container)));
@@ -215,7 +239,13 @@ protected static function getConnection2Service($container)
*/
protected static function getConnection3Service($container)
{
- $container->services['connection3'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['connection3'])) {
+ return $container->services['connection3'];
+ }
+
+ $container->services['connection3'] = $instance;
$instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))];
@@ -229,7 +259,13 @@ protected static function getConnection3Service($container)
*/
protected static function getConnection4Service($container)
{
- $container->services['connection4'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['connection4'])) {
+ return $container->services['connection4'];
+ }
+
+ $container->services['connection4'] = $instance;
$instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))];
@@ -243,7 +279,13 @@ protected static function getConnection4Service($container)
*/
protected static function getDispatcherService($container, $lazyLoad = true)
{
- $container->services['dispatcher'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['dispatcher'])) {
+ return $container->services['dispatcher'];
+ }
+
+ $container->services['dispatcher'] = $instance;
$instance->subscriber = ($container->services['subscriber'] ?? self::getSubscriberService($container));
@@ -257,7 +299,13 @@ protected static function getDispatcherService($container, $lazyLoad = true)
*/
protected static function getDispatcher2Service($container, $lazyLoad = true)
{
- $container->services['dispatcher2'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['dispatcher2'])) {
+ return $container->services['dispatcher2'];
+ }
+
+ $container->services['dispatcher2'] = $instance;
$instance->subscriber2 = ($container->privates['subscriber2'] ?? self::getSubscriber2Service($container));
@@ -271,9 +319,15 @@ protected static function getDispatcher2Service($container, $lazyLoad = true)
*/
protected static function getDoctrine_EntityListenerResolverService($container)
{
- return $container->services['doctrine.entity_listener_resolver'] = new \stdClass(new RewindableGenerator(function () use ($container) {
+ $instance = new \stdClass(new RewindableGenerator(function () use ($container) {
yield 0 => ($container->services['doctrine.listener'] ?? self::getDoctrine_ListenerService($container));
}, 1));
+
+ if (isset($container->services['doctrine.entity_listener_resolver'])) {
+ return $container->services['doctrine.entity_listener_resolver'];
+ }
+
+ return $container->services['doctrine.entity_listener_resolver'] = $instance;
}
/**
@@ -292,7 +346,13 @@ protected static function getDoctrine_EntityManagerService($container)
$b->resolver = $a;
$b->flag = 'ok';
- return $container->services['doctrine.entity_manager'] = \FactoryChecker::create($b);
+ $instance = \FactoryChecker::create($b);
+
+ if (isset($container->services['doctrine.entity_manager'])) {
+ return $container->services['doctrine.entity_manager'];
+ }
+
+ return $container->services['doctrine.entity_manager'] = $instance;
}
/**
@@ -308,7 +368,13 @@ protected static function getDoctrine_ListenerService($container)
return $container->services['doctrine.listener'];
}
- return $container->services['doctrine.listener'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['doctrine.listener'])) {
+ return $container->services['doctrine.listener'];
+ }
+
+ return $container->services['doctrine.listener'] = $instance;
}
/**
@@ -324,7 +390,13 @@ protected static function getFooService($container)
return $container->services['foo'];
}
- return $container->services['foo'] = new \FooCircular($a);
+ $instance = new \FooCircular($a);
+
+ if (isset($container->services['foo'])) {
+ return $container->services['foo'];
+ }
+
+ return $container->services['foo'] = $instance;
}
/**
@@ -336,7 +408,13 @@ protected static function getFoo2Service($container)
{
$a = new \BarCircular();
- $container->services['foo2'] = $instance = new \FooCircular($a);
+ $instance = new \FooCircular($a);
+
+ if (isset($container->services['foo2'])) {
+ return $container->services['foo2'];
+ }
+
+ $container->services['foo2'] = $instance;
$a->addFoobar(($container->services['foobar2'] ?? self::getFoobar2Service($container)));
@@ -368,7 +446,13 @@ protected static function getFoo4Service($container)
*/
protected static function getFoo5Service($container)
{
- $container->services['foo5'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['foo5'])) {
+ return $container->services['foo5'];
+ }
+
+ $container->services['foo5'] = $instance;
$instance->bar = ($container->services['bar5'] ?? self::getBar5Service($container));
@@ -382,7 +466,13 @@ protected static function getFoo5Service($container)
*/
protected static function getFoo6Service($container)
{
- $container->services['foo6'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['foo6'])) {
+ return $container->services['foo6'];
+ }
+
+ $container->services['foo6'] = $instance;
$instance->bar6 = ($container->privates['bar6'] ?? self::getBar6Service($container));
@@ -402,7 +492,13 @@ protected static function getFoobarService($container)
return $container->services['foobar'];
}
- return $container->services['foobar'] = new \FoobarCircular($a);
+ $instance = new \FoobarCircular($a);
+
+ if (isset($container->services['foobar'])) {
+ return $container->services['foobar'];
+ }
+
+ return $container->services['foobar'] = $instance;
}
/**
@@ -418,7 +514,13 @@ protected static function getFoobar2Service($container)
return $container->services['foobar2'];
}
- return $container->services['foobar2'] = new \FoobarCircular($a);
+ $instance = new \FoobarCircular($a);
+
+ if (isset($container->services['foobar2'])) {
+ return $container->services['foobar2'];
+ }
+
+ return $container->services['foobar2'] = $instance;
}
/**
@@ -454,7 +556,13 @@ protected static function getFoobar4Service($container)
*/
protected static function getListener3Service($container)
{
- $container->services['listener3'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['listener3'])) {
+ return $container->services['listener3'];
+ }
+
+ $container->services['listener3'] = $instance;
$instance->manager = ($container->services['manager3'] ?? self::getManager3Service($container));
@@ -474,7 +582,13 @@ protected static function getListener4Service($container)
return $container->services['listener4'];
}
- return $container->services['listener4'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['listener4'])) {
+ return $container->services['listener4'];
+ }
+
+ return $container->services['listener4'] = $instance;
}
/**
@@ -490,7 +604,13 @@ protected static function getLoggerService($container)
return $container->services['logger'];
}
- $container->services['logger'] = $instance = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['logger'])) {
+ return $container->services['logger'];
+ }
+
+ $container->services['logger'] = $instance;
$instance->handler = new \stdClass(($container->services['manager'] ?? self::getManagerService($container)));
@@ -510,7 +630,13 @@ protected static function getMailer_TransportService($container)
return $container->services['mailer.transport'];
}
- return $container->services['mailer.transport'] = $a->create();
+ $instance = $a->create();
+
+ if (isset($container->services['mailer.transport'])) {
+ return $container->services['mailer.transport'];
+ }
+
+ return $container->services['mailer.transport'] = $instance;
}
/**
@@ -520,10 +646,16 @@ protected static function getMailer_TransportService($container)
*/
protected static function getMailer_TransportFactoryService($container)
{
- return $container->services['mailer.transport_factory'] = new \FactoryCircular(new RewindableGenerator(function () use ($container) {
+ $instance = new \FactoryCircular(new RewindableGenerator(function () use ($container) {
yield 0 => ($container->services['mailer.transport_factory.amazon'] ?? self::getMailer_TransportFactory_AmazonService($container));
yield 1 => ($container->services['mailer_inline.transport_factory.amazon'] ?? self::getMailerInline_TransportFactory_AmazonService($container));
}, 2));
+
+ if (isset($container->services['mailer.transport_factory'])) {
+ return $container->services['mailer.transport_factory'];
+ }
+
+ return $container->services['mailer.transport_factory'] = $instance;
}
/**
@@ -539,7 +671,13 @@ protected static function getMailer_TransportFactory_AmazonService($container)
return $container->services['mailer.transport_factory.amazon'];
}
- return $container->services['mailer.transport_factory.amazon'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['mailer.transport_factory.amazon'])) {
+ return $container->services['mailer.transport_factory.amazon'];
+ }
+
+ return $container->services['mailer.transport_factory.amazon'] = $instance;
}
/**
@@ -575,7 +713,13 @@ protected static function getManagerService($container)
return $container->services['manager'];
}
- return $container->services['manager'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['manager'])) {
+ return $container->services['manager'];
+ }
+
+ return $container->services['manager'] = $instance;
}
/**
@@ -591,7 +735,13 @@ protected static function getManager2Service($container)
return $container->services['manager2'];
}
- return $container->services['manager2'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['manager2'])) {
+ return $container->services['manager2'];
+ }
+
+ return $container->services['manager2'] = $instance;
}
/**
@@ -607,7 +757,13 @@ protected static function getManager3Service($container, $lazyLoad = true)
return $container->services['manager3'];
}
- return $container->services['manager3'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['manager3'])) {
+ return $container->services['manager3'];
+ }
+
+ return $container->services['manager3'] = $instance;
}
/**
@@ -631,7 +787,13 @@ protected static function getMonolog_LoggerService($container)
*/
protected static function getMonolog_Logger2Service($container)
{
- $container->services['monolog.logger_2'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['monolog.logger_2'])) {
+ return $container->services['monolog.logger_2'];
+ }
+
+ $container->services['monolog.logger_2'] = $instance;
$instance->handler = ($container->services['mailer.transport'] ?? self::getMailer_TransportService($container));
@@ -684,7 +846,13 @@ protected static function getPAService($container)
return $container->services['pA'];
}
- return $container->services['pA'] = new \stdClass($a, $b);
+ $instance = new \stdClass($a, $b);
+
+ if (isset($container->services['pA'])) {
+ return $container->services['pA'];
+ }
+
+ return $container->services['pA'] = $instance;
}
/**
@@ -694,7 +862,13 @@ protected static function getPAService($container)
*/
protected static function getPBService($container)
{
- $container->services['pB'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['pB'])) {
+ return $container->services['pB'];
+ }
+
+ $container->services['pB'] = $instance;
$instance->d = ($container->services['pD'] ?? self::getPDService($container));
@@ -708,7 +882,13 @@ protected static function getPBService($container)
*/
protected static function getPCService($container, $lazyLoad = true)
{
- $container->services['pC'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['pC'])) {
+ return $container->services['pC'];
+ }
+
+ $container->services['pC'] = $instance;
$instance->d = ($container->services['pD'] ?? self::getPDService($container));
@@ -728,7 +908,13 @@ protected static function getPDService($container)
return $container->services['pD'];
}
- return $container->services['pD'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['pD'])) {
+ return $container->services['pD'];
+ }
+
+ return $container->services['pD'] = $instance;
}
/**
@@ -760,7 +946,13 @@ protected static function getSubscriberService($container)
return $container->services['subscriber'];
}
- return $container->services['subscriber'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->services['subscriber'])) {
+ return $container->services['subscriber'];
+ }
+
+ return $container->services['subscriber'] = $instance;
}
/**
@@ -776,7 +968,13 @@ protected static function getBar6Service($container)
return $container->privates['bar6'];
}
- return $container->privates['bar6'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['bar6'])) {
+ return $container->privates['bar6'];
+ }
+
+ return $container->privates['bar6'] = $instance;
}
/**
@@ -818,7 +1016,13 @@ protected static function getManager4Service($container, $lazyLoad = true)
return $container->privates['manager4'];
}
- return $container->privates['manager4'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['manager4'])) {
+ return $container->privates['manager4'];
+ }
+
+ return $container->privates['manager4'] = $instance;
}
/**
@@ -834,6 +1038,12 @@ protected static function getSubscriber2Service($container)
return $container->privates['subscriber2'];
}
- return $container->privates['subscriber2'] = new \stdClass($a);
+ $instance = new \stdClass($a);
+
+ if (isset($container->privates['subscriber2'])) {
+ return $container->privates['subscriber2'];
+ }
+
+ return $container->privates['subscriber2'] = $instance;
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php
index 982366cc0687c..a0f70aa0e0d9f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_deep_graph.php
@@ -44,7 +44,13 @@ public function isCompiled(): bool
*/
protected static function getBarService($container)
{
- $container->services['bar'] = $instance = new \stdClass();
+ $instance = new \stdClass();
+
+ if (isset($container->services['bar'])) {
+ return $container->services['bar'];
+ }
+
+ $container->services['bar'] = $instance;
$instance->p5 = new \stdClass(($container->services['foo'] ?? self::getFooService($container)));
@@ -70,6 +76,12 @@ protected static function getFooService($container)
$b->p2 = $c;
- return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph($a, $b);
+ $instance = new \Symfony\Component\DependencyInjection\Tests\Dumper\FooForDeepGraph($a, $b);
+
+ if (isset($container->services['foo'])) {
+ return $container->services['foo'];
+ }
+
+ return $container->services['foo'] = $instance;
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php
index 9c330aedbc410..17d7814805aaa 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php
@@ -162,7 +162,13 @@ protected static function getBar22Service($container)
*/
protected static function getBazService($container)
{
- $container->services['baz'] = $instance = new \Baz();
+ $instance = new \Baz();
+
+ if (isset($container->services['baz'])) {
+ return $container->services['baz'];
+ }
+
+ $container->services['baz'] = $instance;
$instance->setFoo(($container->services['foo_with_inline'] ?? self::getFooWithInlineService($container)));
@@ -310,7 +316,13 @@ protected static function getFooBarService($container)
*/
protected static function getFooWithInlineService($container)
{
- $container->services['foo_with_inline'] = $instance = new \Foo();
+ $instance = new \Foo();
+
+ if (isset($container->services['foo_with_inline'])) {
+ return $container->services['foo_with_inline'];
+ }
+
+ $container->services['foo_with_inline'] = $instance;
$a = new \Bar();
$a->pub = 'pub';
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
index 35527e9bc34dd..9e9f3b911e4c9 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
@@ -12,6 +12,8 @@
namespace Symfony\Component\DependencyInjection\Tests\Loader;
use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\Exception\LoaderLoadException;
@@ -1217,4 +1219,28 @@ public function testStaticConstructor()
$definition = $container->getDefinition('static_constructor');
$this->assertEquals((new Definition('stdClass'))->setFactory([null, 'create']), $definition);
}
+
+ #[IgnoreDeprecations]
+ #[Group('legacy')]
+ #[DataProvider('provideForbiddenKeys')]
+ public function testFromCallableTriggersDeprecationOnForbiddenKeys(string $key, mixed $value)
+ {
+ $this->expectUserDeprecationMessage(\sprintf('Since symfony/dependency-injection 8.1: Configuring the "%s" key for the service "my_service" when using "from_callable" is deprecated and will throw an "InvalidArgumentException" in 9.0.', $key));
+
+ $loader = new YamlFileLoader(new ContainerBuilder(), new FileLocator());
+
+ $reflectionMethod = new \ReflectionMethod($loader, 'parseDefinition');
+ $reflectionMethod->invoke($loader, 'my_service', ['from_callable' => 'strlen', $key => $value], 'config/services.yaml', []);
+ }
+
+ public static function provideForbiddenKeys(): iterable
+ {
+ yield 'parent' => ['parent', 'App\\SomeParent'];
+ yield 'synthetic' => ['synthetic', true];
+ yield 'file' => ['file', 'some_file.php'];
+ yield 'arguments' => ['arguments', []];
+ yield 'properties' => ['properties', ['foo' => 'bar']];
+ yield 'configurator' => ['configurator', 'some_configurator'];
+ yield 'calls' => ['calls', [['method' => 'setFoo', 'arguments' => ['bar']]]];
+ }
}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
index b57530391d9c3..0f391020974e4 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
@@ -120,7 +120,7 @@ public function testDeprecatedSuper(string $class, string $super, string $type)
{
set_error_handler(fn () => false);
$e = error_reporting(0);
- trigger_error('', E_USER_DEPRECATED);
+ trigger_error('', \E_USER_DEPRECATED);
class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true);
@@ -568,3 +568,5 @@ public function ownAbstractBaseMethod() { }
}
}
}
+
+// @php-cs-fixer-ignore error_suppression This file is explicitly expected to not silence each of trigger_error calls
diff --git a/src/Symfony/Component/Form/AbstractType.php b/src/Symfony/Component/Form/AbstractType.php
index 3716eb1fb92c1..74548bc6024d4 100644
--- a/src/Symfony/Component/Form/AbstractType.php
+++ b/src/Symfony/Component/Form/AbstractType.php
@@ -37,8 +37,6 @@ public function buildView(FormView $view, FormInterface $form, array $options):
{
}
- /**
- */
public function finishView(FormView $view, FormInterface $form, array $options): void
{
}
diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md
index 7ac95ccc8db1e..238f7ca83e9ea 100644
--- a/src/Symfony/Component/Form/CHANGELOG.md
+++ b/src/Symfony/Component/Form/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Add `ResetFlowType` button in `NavigatorFlowType` that you can display with `with_reset` option
+
8.0
---
diff --git a/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php b/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php
index 7dab23c35a4e0..3510c44da2ec6 100644
--- a/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php
+++ b/src/Symfony/Component/Form/Flow/Type/NavigatorFlowType.php
@@ -27,6 +27,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
$builder->add('previous', PreviousFlowType::class);
$builder->add('next', NextFlowType::class);
$builder->add('finish', FinishFlowType::class);
+
+ if ($options['with_reset']) {
+ $builder->add('reset', ResetFlowType::class);
+ }
}
public function configureOptions(OptionsResolver $resolver): void
@@ -36,5 +40,10 @@ public function configureOptions(OptionsResolver $resolver): void
'mapped' => false,
'priority' => -100,
]);
+
+ $resolver->define('with_reset')
+ ->allowedTypes('bool')
+ ->default(false)
+ ->info('Whether to add a reset button to restart the flow from the first step');
}
}
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php b/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php
index a4194e755bc4f..eb7e786cfff77 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Flow/LastStepSkippedType.php
@@ -24,7 +24,9 @@ public function buildFormFlow(FormFlowBuilderInterface $builder, array $options)
$builder->addStep('step1', TextType::class);
$builder->addStep('step2', skip: static fn () => true);
- $builder->add('navigator', NavigatorFlowType::class);
+ $builder->add('navigator', NavigatorFlowType::class, [
+ 'with_reset' => true,
+ ]);
}
public function configureOptions(OptionsResolver $resolver): void
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php b/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php
index ed1bc31d96590..2341423947992 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Flow/UserSignUpNavigatorType.php
@@ -14,8 +14,8 @@
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Flow\Type\NavigatorFlowType;
use Symfony\Component\Form\Flow\Type\NextFlowType;
-use Symfony\Component\Form\Flow\Type\ResetFlowType;
use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
class UserSignUpNavigatorType extends AbstractType
{
@@ -25,8 +25,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
'clear_submission' => true,
'include_if' => ['professional'],
]);
+ }
- $builder->add('reset', ResetFlowType::class);
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'with_reset' => true,
+ ]);
}
public function getParent(): string
diff --git a/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php b/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php
index 9d21b8d034d02..97d3c253e0af5 100644
--- a/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php
+++ b/src/Symfony/Component/Form/Tests/Flow/FormFlowTest.php
@@ -945,8 +945,9 @@ public function testLastStepSkippedMarkFlowAsFinished()
self::assertTrue($flow->has('navigator'));
$navigatorForm = $flow->get('navigator');
- self::assertCount(1, $navigatorForm->all());
+ self::assertCount(2, $navigatorForm->all());
self::assertTrue($navigatorForm->has('next'));
+ self::assertTrue($navigatorForm->has('reset'));
$flow->submit([
'step1' => 'foo',
diff --git a/src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php b/src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php
new file mode 100644
index 0000000000000..6d919b8ebeb75
--- /dev/null
+++ b/src/Symfony/Component/Form/Tests/Flow/Type/NavigatorFlowTypeTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests\Flow\Type;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Form\Flow\Type\NavigatorFlowType;
+use Symfony\Component\Form\FormFactoryInterface;
+use Symfony\Component\Form\Forms;
+
+class NavigatorFlowTypeTest extends TestCase
+{
+ private FormFactoryInterface $factory;
+
+ protected function setUp(): void
+ {
+ $this->factory = Forms::createFormFactoryBuilder()->getFormFactory();
+ }
+
+ public function testDefaultOptionsDoNotIncludeReset()
+ {
+ $form = $this->factory->create(NavigatorFlowType::class);
+
+ self::assertTrue($form->has('previous'));
+ self::assertTrue($form->has('next'));
+ self::assertTrue($form->has('finish'));
+ self::assertFalse($form->has('reset'));
+ }
+
+ public function testWithResetOptionAddsResetButton()
+ {
+ $form = $this->factory->create(NavigatorFlowType::class, null, [
+ 'with_reset' => true,
+ ]);
+
+ self::assertTrue($form->has('previous'));
+ self::assertTrue($form->has('next'));
+ self::assertTrue($form->has('finish'));
+ self::assertTrue($form->has('reset'));
+ }
+}
diff --git a/src/Symfony/Component/HttpClient/CachingHttpClient.php b/src/Symfony/Component/HttpClient/CachingHttpClient.php
index d0ec3c66fb544..b530bcc1906aa 100644
--- a/src/Symfony/Component/HttpClient/CachingHttpClient.php
+++ b/src/Symfony/Component/HttpClient/CachingHttpClient.php
@@ -486,7 +486,7 @@ private function evaluateCacheFreshness(array $data): Freshness
$now = time();
$expires = $data['expires_at'];
- if (null !== $expires && $now <= $expires) {
+ if (null !== $expires && $now < $expires) {
return Freshness::Fresh;
}
diff --git a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php
index aee15e68b94db..8890a2b318913 100644
--- a/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php
+++ b/src/Symfony/Component/HttpClient/DataCollector/HttpClientDataCollector.php
@@ -203,11 +203,14 @@ private function getCurlCommand(array $trace): ?string
$dataArg[] = '--data-raw '.$this->escapePayload($body);
} elseif (\is_array($body)) {
try {
- $body = explode('&', self::normalizeBody($body));
+ $body = self::normalizeBody($body);
} catch (TransportException) {
return null;
}
- foreach ($body as $value) {
+ if (!\is_string($body)) {
+ return null;
+ }
+ foreach (explode('&', $body) as $value) {
$dataArg[] = '--data-raw '.$this->escapePayload(urldecode($value));
}
} else {
diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php
index 4e0247521476e..1d2d826e25d9c 100644
--- a/src/Symfony/Component/HttpClient/HttpClientTrait.php
+++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php
@@ -169,13 +169,14 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
unset($options['auth_basic'], $options['auth_bearer']);
// Parse base URI
- if (\is_string($options['base_uri'])) {
- $options['base_uri'] = self::parseUrl($options['base_uri']);
+ if (\is_string($baseUri = $options['base_uri'] ?? null)) {
+ $baseUri = self::parseUrl($baseUri);
}
+ unset($options['base_uri']);
// Validate and resolve URL
$url = self::parseUrl($url, $options['query']);
- $url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []);
+ $url = self::resolveUrl($url, $baseUri, $defaultOptions['query'] ?? []);
}
// Finalize normalization of options
diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php
index e4536dc18f02e..e8c3ccb089ee4 100644
--- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php
+++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php
@@ -201,6 +201,8 @@ private static function shiftBaseUri(array $options, array &$baseUris): array
if ($baseUris) {
$baseUri = 1 < \count($baseUris) ? array_shift($baseUris) : current($baseUris);
$options['base_uri'] = \is_array($baseUri) ? $baseUri[array_rand($baseUri)] : $baseUri;
+ } elseif (\is_array($options['base_uri'] ?? null)) {
+ unset($options['base_uri']);
}
return $options;
diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php
index 739de0163e1a1..9caa803d2c208 100644
--- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php
+++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php
@@ -49,9 +49,11 @@ public function request(string $method, string $url, array $options = []): Respo
{
$e = null;
$url = self::parseUrl($url, $options['query'] ?? []);
+ $resolved = false;
if (\is_string($options['base_uri'] ?? null)) {
$options['base_uri'] = self::parseUrl($options['base_uri']);
+ $resolved = true;
}
try {
@@ -65,10 +67,15 @@ public function request(string $method, string $url, array $options = []): Respo
$options = self::mergeDefaultOptions($options, $defaultOptions, true);
if (\is_string($options['base_uri'] ?? null)) {
$options['base_uri'] = self::parseUrl($options['base_uri']);
+ $resolved = true;
}
$url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null, $defaultOptions['query'] ?? []));
}
+ if ($resolved) {
+ unset($options['base_uri']);
+ }
+
foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) {
if (preg_match("{{$regexp}}A", $url)) {
if (null === $e || $regexp !== $this->defaultRegexp) {
diff --git a/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php
index 064737abb9b3c..acb785a8dba63 100644
--- a/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/CachingHttpClientTest.php
@@ -172,9 +172,9 @@ public function testItDoesntServeAStaleResponse()
self::assertSame(200, $response->getStatusCode());
self::assertSame('foo', $response->getContent());
- sleep(5);
+ sleep(4);
- // After 5 seconds, the cached response is still considered valid.
+ // After 4 seconds, the cached response is still considered valid.
$response = $client->request('GET', 'http://example.com/foo-bar');
self::assertSame(200, $response->getStatusCode());
self::assertSame('foo', $response->getContent());
@@ -954,14 +954,14 @@ public function testHeuristicFreshnessWithLastModified()
self::assertSame('foo', $response->getContent());
// Heuristic: 10% of 3600s = 360s; should be fresh within this time
- sleep(360); // 5 minutes
+ sleep(359); // 5 minutes
$response = $client->request('GET', 'http://example.com/heuristic');
self::assertSame(200, $response->getStatusCode());
self::assertSame('foo', $response->getContent());
// After heuristic expires
- sleep(1); // Total 361s, past 360s heuristic
+ sleep(2); // Total 361s, past 360s heuristic
$response = $client->request('GET', 'http://example.com/heuristic');
self::assertSame(200, $response->getStatusCode());
diff --git a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
index 42cee882e9bae..f0964e9b1046e 100644
--- a/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/DataCollector/HttpClientDataCollectorTest.php
@@ -12,8 +12,11 @@
namespace Symfony\Component\HttpClient\Tests\DataCollector;
use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpClient\CurlHttpClient;
use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector;
+use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\NativeHttpClient;
use Symfony\Component\HttpClient\TraceableHttpClient;
@@ -426,6 +429,25 @@ public function testItDoesNotGeneratesCurlCommandsForUploadedFiles()
self::assertNull($curlCommand);
}
+ #[RequiresPhpExtension('curl')]
+ public function testGeneratingCurlCommandForArraysWithResourcesAndUnreachableHost()
+ {
+ $httpClient = new TraceableHttpClient(new CurlHttpClient());
+ try {
+ $httpClient->request('POST', 'http://localhast:8057/', [
+ 'body' => ['file' => fopen('data://text/plain,', 'r')],
+ ]);
+ } catch (TransportException) {
+ }
+ $sut = new HttpClientDataCollector();
+ $sut->registerClient('http_client', $httpClient);
+ $sut->lateCollect();
+ $collectedData = $sut->getClients();
+ self::assertCount(1, $collectedData['http_client']['traces']);
+ $curlCommand = $collectedData['http_client']['traces'][0]['curlCommand'];
+ self::assertNull($curlCommand);
+ }
+
private function httpClientThatHasTracedRequests($tracedRequests): TraceableHttpClient
{
$httpClient = new TraceableHttpClient(new NativeHttpClient());
diff --git a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php
index 96ad3d7250a86..c0338cc5cbe87 100644
--- a/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php
+++ b/src/Symfony/Component/HttpClient/Tests/ScopingHttpClientTest.php
@@ -15,6 +15,9 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\HttpClient\Response\MockResponse;
+use Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
+use Symfony\Component\HttpClient\RetryableHttpClient;
use Symfony\Component\HttpClient\ScopingHttpClient;
class ScopingHttpClientTest extends TestCase
@@ -93,10 +96,31 @@ public function testForBaseUri()
$client = ScopingHttpClient::forBaseUri(new MockHttpClient(null, null), 'http://example.com/foo');
$response = $client->request('GET', '/bar');
- $this->assertSame('http://example.com/foo', implode('', $response->getRequestOptions()['base_uri']));
$this->assertSame('http://example.com/bar', $response->getInfo('url'));
$response = $client->request('GET', 'http://foo.bar/');
- $this->assertNull($response->getRequestOptions()['base_uri']);
+ $this->assertSame('http://foo.bar/', $response->getInfo('url'));
+ }
+
+ public function testRetryableHttpClientIntegration()
+ {
+ $responses = [
+ new MockResponse(info: ['http_code' => 503]),
+ new MockResponse(info: ['http_code' => 503]),
+ new MockResponse(info: ['http_code' => 503]),
+ new MockResponse(),
+ ];
+
+ $client = ScopingHttpClient::forBaseUri(
+ new RetryableHttpClient(
+ new MockHttpClient($responses),
+ new GenericRetryStrategy(delayMs: 0)
+ ),
+ 'https://foo.example.com/app/',
+ );
+
+ $response = $client->request('GET', 'santysisi');
+ $this->assertSame(200, $response->getStatusCode());
+ $this->assertSame('https://foo.example.com/app/santysisi', $response->getInfo('url'));
}
}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php
index 9dd2abda846d5..0046e4a7be6a7 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/CacheAttributeListener.php
@@ -123,7 +123,7 @@ public function onKernelResponse(ResponseEvent $event): void
// Check if the response has a Vary header that should be considered, ignoring cases where
// it's only 'Accept-Language' and the request has the '_vary_by_language' attribute
$hasVary = ['Accept-Language'] === $response->getVary() ? !$request->attributes->get('_vary_by_language') : $response->hasVary();
- //Check if cache-control directive was set manually in cacheControl (not auto computed)
+ // Check if cache-control directive was set manually in cacheControl (not auto computed)
$hasCacheControlDirective = new class($response->headers) extends HeaderBag {
public function __construct(private parent $headerBag)
{
diff --git a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php
index 4422bfcdd3e54..06bc420d1c33d 100644
--- a/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php
+++ b/src/Symfony/Component/HttpKernel/HttpKernelBrowser.php
@@ -184,7 +184,7 @@ protected function filterResponse(object $response): DomResponse
ob_start(static function ($chunk) use (&$content) {
$content .= $chunk;
- return '';
+ return '';
});
try {
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index c60056404ecdd..6d5f80c329351 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -72,15 +72,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '8.0.2';
- public const VERSION_ID = 80002;
+ public const VERSION = '8.1.0-DEV';
+ public const VERSION_ID = 80100;
public const MAJOR_VERSION = 8;
- public const MINOR_VERSION = 0;
- public const RELEASE_VERSION = 2;
- public const EXTRA_VERSION = '';
+ public const MINOR_VERSION = 1;
+ public const RELEASE_VERSION = 0;
+ public const EXTRA_VERSION = 'DEV';
- public const END_OF_MAINTENANCE = '07/2026';
- public const END_OF_LIFE = '07/2026';
+ public const END_OF_MAINTENANCE = '01/2027';
+ public const END_OF_LIFE = '01/2027';
public function __construct(
protected string $environment,
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
index e8bffba48165a..76af83b0b87d6 100644
--- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php
@@ -350,7 +350,7 @@ public function testLoadsBodyEval()
/**
* Basic case when the second header has a different value.
- * Both responses should be cached
+ * Both responses should be cached.
*/
public function testWriteWithMultipleVaryAndCachedAllResponse()
{
@@ -376,7 +376,7 @@ public function testWriteWithMultipleVaryAndCachedAllResponse()
/**
* Basic case when the second header has the same value on both requests.
- * The last response should be cached
+ * The last response should be cached.
*/
public function testWriteWithMultipleVaryAndCachedLastResponse()
{
@@ -402,7 +402,7 @@ public function testWriteWithMultipleVaryAndCachedLastResponse()
/**
* Case when a vary value has been removed.
- * Both responses should be cached
+ * Both responses should be cached.
*/
public function testWriteWithChangingVary()
{
@@ -425,11 +425,11 @@ public function testWriteWithChangingVary()
/**
* Case when a vary value has been removed and headers of the new vary list are the same.
- * The last response should be cached
+ * The last response should be cached.
*/
public function testWriteWithRemoveVaryAndAllHeadersOnTheList()
{
- $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar',]);
+ $req1 = Request::create('/foo', 'get', [], [], [], ['HTTP_FOO' => 'foo', 'HTTP_FOOBAR' => 'bar']);
$content = str_repeat('a', 24).'b'.str_repeat('a', 24);
$res1 = new Response($content, 200, ['vary' => ['Foo', 'bar', 'foobar'], 'X-Body-Eval' => 'SSI']);
$this->store->write($req1, $res1);
@@ -448,7 +448,7 @@ public function testWriteWithRemoveVaryAndAllHeadersOnTheList()
/**
* Case when a vary value has been added and headers of the new vary list are the same.
- * The last response should be cached
+ * The last response should be cached.
*/
public function testWriteWithAddingVaryAndAllHeadersOnTheList()
{
diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
index 60043491cb762..09bdd06f48235 100644
--- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
@@ -17,8 +17,8 @@
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
+use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
diff --git a/src/Symfony/Component/JsonPath/JsonCrawler.php b/src/Symfony/Component/JsonPath/JsonCrawler.php
index faa87bf555425..e34479f34c348 100644
--- a/src/Symfony/Component/JsonPath/JsonCrawler.php
+++ b/src/Symfony/Component/JsonPath/JsonCrawler.php
@@ -38,6 +38,8 @@ final class JsonCrawler implements JsonCrawlerInterface
'value' => true,
];
+ private const SINGULAR_ARGUMENT_FUNCTIONS = ['length', 'match', 'search'];
+
/**
* Comparison operators and their corresponding lengths.
*/
@@ -776,6 +778,10 @@ private function evaluateFunction(string $name, string $args, mixed $context): m
$value = $argList[0] ?? null;
$nodelistSize = $nodelistSizes[0] ?? 0;
+ if ($nodelistSize > 1 && \in_array($name, self::SINGULAR_ARGUMENT_FUNCTIONS, true)) {
+ throw new JsonCrawlerException($args, \sprintf('non-singular query is not allowed as argument to "%s" function', $name));
+ }
+
return match ($name) {
'length' => match (true) {
\is_string($value) => mb_strlen($value),
@@ -1029,11 +1035,31 @@ private function validateFilterExpression(string $expr): void
throw new JsonCrawlerException($left, 'non-singular query is not comparable');
}
+ $this->validateFunctionArguments($left);
+ $this->validateFunctionArguments($right);
+
return;
}
}
}
+ private function validateFunctionArguments(string $expr): void
+ {
+ // is there a function call?
+ if (!preg_match('/^(\w+)\((.*)\)$/', trim($expr), $matches)) {
+ return;
+ }
+
+ if (!\in_array($functionName = $matches[1], self::SINGULAR_ARGUMENT_FUNCTIONS, true)) {
+ return;
+ }
+
+ $arg = trim($matches[2]);
+ if (str_starts_with($arg, '@') && $this->isNonSingularRelativeQuery($arg)) {
+ throw new JsonCrawlerException($arg, \sprintf('non-singular query is not allowed as argument to "%s" function', $functionName));
+ }
+ }
+
/**
* Transforms JSONPath regex patterns to comply with RFC 9485.
*
diff --git a/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile b/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile
index d9b4c353f4a76..420896e9d1513 100644
--- a/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile
+++ b/src/Symfony/Component/JsonPath/Tests/Fixtures/Makefile
@@ -1,4 +1,4 @@
-override hash := 05f6cac786bf0cce95437e6f1adedc3186d54a71
+override hash := b9d7153e58711ad38bb8e35ece69c13f4b2f7d63
.PHONY: cts.json
cts.json:
diff --git a/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json b/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json
index eedcdbaad55d6..1785527ed760a 100644
--- a/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json
+++ b/src/Symfony/Component/JsonPath/Tests/Fixtures/cts.json
@@ -565,6 +565,57 @@
"$['o'][1]['a']"
]
},
+ {
+ "name": "basic, name shorthand, true",
+ "selector": "$.true",
+ "document": {
+ "true": "A",
+ "_foo": "B"
+ },
+ "result": [
+ "A"
+ ],
+ "result_paths": [
+ "$['true']"
+ ],
+ "tags": [
+ "boundary"
+ ]
+ },
+ {
+ "name": "basic, name shorthand, false",
+ "selector": "$.false",
+ "document": {
+ "false": "A",
+ "_foo": "B"
+ },
+ "result": [
+ "A"
+ ],
+ "result_paths": [
+ "$['false']"
+ ],
+ "tags": [
+ "boundary"
+ ]
+ },
+ {
+ "name": "basic, name shorthand, null",
+ "selector": "$.null",
+ "document": {
+ "null": "A",
+ "_foo": "B"
+ },
+ "result": [
+ "A"
+ ],
+ "result_paths": [
+ "$['null']"
+ ],
+ "tags": [
+ "boundary"
+ ]
+ },
{
"name": "basic, descendant segment, wildcard shorthand, array data",
"selector": "$..*",
@@ -3980,6 +4031,272 @@
]
]
},
+ {
+ "name": "filter, two consecutive ands",
+ "selector": "$[?@.a && @.b && @.c]",
+ "document": [
+ {
+ "a": 1,
+ "b": 2
+ },
+ {
+ "a": 1,
+ "c": 3
+ },
+ {
+ "b": 2,
+ "c": 3
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3
+ }
+ ],
+ "result": [
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3
+ }
+ ],
+ "result_paths": [
+ "$[3]"
+ ]
+ },
+ {
+ "name": "filter, two consecutive ors",
+ "selector": "$[?@.a || @.b || @.c]",
+ "document": [
+ {
+ "a": 1,
+ "b": 2
+ },
+ {
+ "a": 1,
+ "c": 3
+ },
+ {
+ "b": 2,
+ "c": 3
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3
+ }
+ ],
+ "result": [
+ {
+ "a": 1,
+ "b": 2
+ },
+ {
+ "a": 1,
+ "c": 3
+ },
+ {
+ "b": 2,
+ "c": 3
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3
+ }
+ ],
+ "result_paths": [
+ "$[0]",
+ "$[1]",
+ "$[2]",
+ "$[3]"
+ ]
+ },
+ {
+ "name": "filter, multiple consecutive ands",
+ "selector": "$[?@.a && @.b && @.c && @.d && @.e]",
+ "document": [
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4
+ },
+ {
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "c": 3,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ }
+ ],
+ "result": [
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ }
+ ],
+ "result_paths": [
+ "$[3]"
+ ]
+ },
+ {
+ "name": "filter, multiple consecutive ors",
+ "selector": "$[?@.a || @.b || @.c || @.d || @.e]",
+ "document": [
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4
+ },
+ {
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "c": 3,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ }
+ ],
+ "result": [
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4
+ },
+ {
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "c": 3,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ }
+ ],
+ "result_paths": [
+ "$[0]",
+ "$[1]",
+ "$[2]",
+ "$[3]"
+ ]
+ },
+ {
+ "name": "filter, multiple consecutive ors and ands",
+ "selector": "$[?@.a && @.b && @.c || @.d || @.e]",
+ "document": [
+ {
+ "a": 1
+ },
+ {
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2
+ },
+ {
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3
+ },
+ {
+ "c": 3,
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "c": 3,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ }
+ ],
+ "result": [
+ {
+ "e": 5
+ },
+ {
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3
+ },
+ {
+ "c": 3,
+ "d": 4,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "c": 3,
+ "e": 5
+ },
+ {
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ "d": 4,
+ "e": 5
+ }
+ ],
+ "result_paths": [
+ "$[1]",
+ "$[3]",
+ "$[4]",
+ "$[5]",
+ "$[6]",
+ "$[7]"
+ ]
+ },
{
"name": "filter, and binds more tightly than or",
"selector": "$[?@.a || @.b && @.c]",
@@ -7733,6 +8050,24 @@
"length"
]
},
+ {
+ "name": "functions, length, non-singular query arg, multiple index selectors",
+ "selector": "$[?length(@[1, 2])<3]",
+ "invalid_selector": true,
+ "tags": [
+ "function",
+ "length"
+ ]
+ },
+ {
+ "name": "functions, length, non-singular query arg, multiple name selectors",
+ "selector": "$[?length(@['a', 'b'])<3]",
+ "invalid_selector": true,
+ "tags": [
+ "function",
+ "length"
+ ]
+ },
{
"name": "functions, match, found match",
"selector": "$[?match(@.a, 'a.*')]",
diff --git a/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php b/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php
index 8aa6f6ebfd025..aa89d4f0f4c6f 100644
--- a/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php
+++ b/src/Symfony/Component/JsonStreamer/Read/PhpGenerator.php
@@ -58,7 +58,7 @@ public function generate(DataModelNodeInterface $dataModel, bool $decodeFromStre
.$providers
.($this->canBeDecodedWithJsonDecode($dataModel, $decodeFromStream)
? $this->line(' return \\'.Decoder::class.'::decodeStream($stream, 0, null);', $context)
- : $this->line(' return $providers[\''.$dataModel->getIdentifier().'\']($stream, 0, null);', $context))
+ : $this->line(' return $providers['.$this->quote($dataModel->getIdentifier()).']($stream, 0, null);', $context))
.$this->line('};', $context);
}
@@ -71,7 +71,7 @@ public function generate(DataModelNodeInterface $dataModel, bool $decodeFromStre
.$providers
.($this->canBeDecodedWithJsonDecode($dataModel, $decodeFromStream)
? $this->line(' return \\'.Decoder::class.'::decodeString((string) $string);', $context)
- : $this->line(' return $providers[\''.$dataModel->getIdentifier().'\'](\\'.Decoder::class.'::decodeString((string) $string));', $context))
+ : $this->line(' return $providers['.$this->quote($dataModel->getIdentifier()).'](\\'.Decoder::class.'::decodeString((string) $string));', $context))
.$this->line('};', $context);
}
@@ -94,7 +94,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
$accessor = $decodeFromStream ? '\\'.Decoder::class.'::decodeStream($stream, $offset, $length)' : '$data';
$arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data';
- return $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) {", $context)
+ return $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) {", $context)
.$this->line(' return '.$this->generateValueFormat($node, $accessor).';', $context)
.$this->line('};', $context);
}
@@ -109,20 +109,20 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
$arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data';
- $php .= $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context);
+ $php .= $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context);
++$context['indentation_level'];
$php .= $decodeFromStream ? $this->line('$data = \\'.Decoder::class.'::decodeStream($stream, $offset, $length);', $context) : '';
foreach ($node->getNodes() as $n) {
- $value = $this->canBeDecodedWithJsonDecode($n, $decodeFromStream) ? $this->generateValueFormat($n, '$data') : '$providers[\''.$n->getIdentifier().'\']($data)';
+ $value = $this->canBeDecodedWithJsonDecode($n, $decodeFromStream) ? $this->generateValueFormat($n, '$data') : '$providers['.$this->quote($n->getIdentifier()).']($data)';
$php .= $this->line('if ('.$this->generateCompositeNodeItemCondition($n, '$data').') {', $context)
.$this->line(" return $value;", $context)
.$this->line('}', $context);
}
- $php .= $this->line('throw new \\'.UnexpectedValueException::class.'(\\sprintf(\'Unexpected "%s" value for "'.$node->getIdentifier().'".\', \\get_debug_type($data)));', $context);
+ $php .= $this->line('throw new \\'.UnexpectedValueException::class.'(\\sprintf(\'Unexpected "%s" value for "%s".\', \\get_debug_type($data), '.$this->quote($node->getIdentifier()).'));', $context);
--$context['indentation_level'];
@@ -132,7 +132,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
if ($node instanceof CollectionNode) {
$arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data';
- $php = $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context);
+ $php = $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context);
++$context['indentation_level'];
@@ -146,11 +146,11 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
if ($decodeFromStream) {
$php .= $this->canBeDecodedWithJsonDecode($node->getItemNode(), $decodeFromStream)
? $this->line(' yield $k => '.$this->generateValueFormat($node->getItemNode(), '\\'.Decoder::class.'::decodeStream($stream, $v[0], $v[1]);'), $context)
- : $this->line(' yield $k => $providers[\''.$node->getItemNode()->getIdentifier().'\']($stream, $v[0], $v[1]);', $context);
+ : $this->line(' yield $k => $providers['.$this->quote($node->getItemNode()->getIdentifier()).']($stream, $v[0], $v[1]);', $context);
} else {
$php .= $this->canBeDecodedWithJsonDecode($node->getItemNode(), $decodeFromStream)
? $this->line(' yield $k => $v;', $context)
- : $this->line(' yield $k => $providers[\''.$node->getItemNode()->getIdentifier().'\']($v);', $context);
+ : $this->line(' yield $k => $providers['.$this->quote($node->getItemNode()->getIdentifier()).']($v);', $context);
}
$php .= $this->line(' }', $context)
@@ -175,7 +175,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
$arguments = $decodeFromStream ? '$stream, $offset, $length' : '$data';
- $php = $this->line("\$providers['".$node->getIdentifier()."'] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context);
+ $php = $this->line('$providers['.$this->quote($node->getIdentifier())."] = static function ($arguments) use (\$options, \$valueTransformers, \$instantiator, &\$providers) {", $context);
++$context['indentation_level'];
@@ -189,7 +189,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
foreach ($node->getProperties() as $streamedName => $property) {
$propertyValuePhp = $this->canBeDecodedWithJsonDecode($property['value'], $decodeFromStream)
? $this->generateValueFormat($property['value'], '\\'.Decoder::class.'::decodeStream($stream, $v[0], $v[1])')
- : '$providers[\''.$property['value']->getIdentifier().'\']($stream, $v[0], $v[1])';
+ : '$providers['.$this->quote($property['value']->getIdentifier()).']($stream, $v[0], $v[1])';
$php .= $this->line(" '$streamedName' => \$object->".$property['name'].' = '.$property['accessor']($propertyValuePhp).',', $context);
}
@@ -204,7 +204,7 @@ private function generateProviders(DataModelNodeInterface $node, bool $decodeFro
foreach ($node->getProperties() as $streamedName => $property) {
$propertyValuePhp = $this->canBeDecodedWithJsonDecode($property['value'], $decodeFromStream)
? "\$data['$streamedName'] ?? '_symfony_missing_value'"
- : "\\array_key_exists('$streamedName', \$data) ? \$providers['".$property['value']->getIdentifier()."'](\$data['$streamedName']) : '_symfony_missing_value'";
+ : "\\array_key_exists('$streamedName', \$data) ? \$providers[".$this->quote($property['value']->getIdentifier())."](\$data['$streamedName']) : '_symfony_missing_value'";
$propertiesValuePhp .= "$separator'".$property['name']."' => ".$property['accessor']($propertyValuePhp);
$separator = ', ';
}
@@ -305,6 +305,11 @@ private function line(string $line, array $context): string
return str_repeat(' ', $context['indentation_level']).$line."\n";
}
+ private function quote(string $identifier): string
+ {
+ return \sprintf("'%s'", addcslashes($identifier, "'"));
+ }
+
/**
* Determines if the $node can be decoded using a simple "json_decode".
*/
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php
new file mode 100644
index 0000000000000..f0d75ddba23a7
--- /dev/null
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/array_shape.php
@@ -0,0 +1,8 @@
+ $v) {
+ yield $k => \Symfony\Component\JsonStreamer\Read\Decoder::decodeStream($stream, $v[0], $v[1]);
+ }
+ };
+ return \iterator_to_array($iterable($stream, $data));
+ };
+ return $providers['array{\'id\': int, \'name\': string}']($stream, 0, null);
+};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php
index fc286791e9f57..1fdf92f9ad238 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.php
@@ -14,7 +14,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php
index 1fe954ebf6fd3..eb7c1950d070f 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_backed_enum.stream.php
@@ -15,7 +15,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php
index 28ff2c0828061..e1f7c50822d1f 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.php
@@ -16,7 +16,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php
index ee8a34a2f8b8a..8056586d690fa 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object.stream.php
@@ -24,7 +24,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy|null']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php
index 83f8387bde755..22a67ce0679c6 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.php
@@ -24,7 +24,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "array|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'array|null'));
};
return $providers['array|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php
index c3bf621fed4d9..1b3fe01c609cb 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_dict.stream.php
@@ -33,7 +33,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "array|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'array|null'));
};
return $providers['array|null']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php
index 65cdae459555b..75c5fb7841614 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.php
@@ -24,7 +24,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "list|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'list|null'));
};
return $providers['list|null'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php
index 9c035367616a3..b8af34b638acc 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/nullable_object_list.stream.php
@@ -33,7 +33,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "list|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'list|null'));
};
return $providers['list|null']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php
index 91923525f1d32..0f0f3ab6fd00c 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.php
@@ -19,7 +19,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php
index c05e0f05d84cf..e7c47d31b5342 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_nullable_properties.stream.php
@@ -27,7 +27,7 @@
if (null === $data) {
return null;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNullableProperties']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php
index 9c64bdb6aa368..7f63358d3e215 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.php
@@ -22,7 +22,7 @@
if (\is_string($data)) {
return $data;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php
index 1ccf17a7b0bf2..cc6574e1ea17d 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/object_with_union.stream.php
@@ -29,7 +29,7 @@
if (\is_string($data)) {
return $data;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Enum\DummyBackedEnum|null|string'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithUnionProperties']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php
index ad118d3567dd8..09d6599e7ee82 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.php
@@ -30,7 +30,7 @@
if (\is_int($data)) {
return $data;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list'](\Symfony\Component\JsonStreamer\Read\Decoder::decodeString((string) $string));
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php
index ebbc18bc2d2c9..7f4a37b7acd86 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Fixtures/stream_reader/union.stream.php
@@ -39,7 +39,7 @@
if (\is_int($data)) {
return $data;
}
- throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list".', \get_debug_type($data)));
+ throw new \Symfony\Component\JsonStreamer\Exception\UnexpectedValueException(\sprintf('Unexpected "%s" value for "%s".', \get_debug_type($data), 'Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list'));
};
return $providers['Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes|int|list']($stream, 0, null);
};
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php b/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php
index 71b29edeabcb1..5bcd4b8c04da3 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Read/StreamReaderGeneratorTest.php
@@ -111,6 +111,8 @@ public static function generatedStreamReaderDataProvider(): iterable
yield ['object_list', Type::list(Type::object(ClassicDummy::class))];
yield ['nullable_object_list', Type::nullable(Type::list(Type::object(ClassicDummy::class)))];
+ yield ['array_shape', Type::arrayShape(['id' => Type::int(), 'name' => Type::string()])];
+
yield ['dict', Type::dict()];
yield ['object_dict', Type::dict(Type::object(ClassicDummy::class))];
yield ['nullable_object_dict', Type::nullable(Type::dict(Type::object(ClassicDummy::class)))];
diff --git a/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php b/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php
index 8fd7fb3f4b412..bbc3a8fa2b3ee 100644
--- a/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php
+++ b/src/Symfony/Component/JsonStreamer/Tests/Write/StreamWriterGeneratorTest.php
@@ -24,8 +24,8 @@
use Symfony\Component\JsonStreamer\Tests\Fixtures\Mapping\SyntheticPropertyMetadataLoader;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\ClassicDummy;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithArray;
-use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithList;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithDollarNamedProperties;
+use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithList;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNameAttributes;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedArray;
use Symfony\Component\JsonStreamer\Tests\Fixtures\Model\DummyWithNestedDictDummies;
diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md
index 403fbe93c4bd8..aeecb8624d328 100644
--- a/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md
+++ b/src/Symfony/Component/Mailer/Bridge/Infobip/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Add support of `ipPoolId` option
+
7.2
---
diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/README.md b/src/Symfony/Component/Mailer/Bridge/Infobip/README.md
index b260c10a74fa2..edfb5e2bd3a8e 100644
--- a/src/Symfony/Component/Mailer/Bridge/Infobip/README.md
+++ b/src/Symfony/Component/Mailer/Bridge/Infobip/README.md
@@ -25,8 +25,9 @@ This transport supports the following custom headers:
| `X-Infobip-MessageId` | string | The ID that uniquely identifies the message sent to a recipient. |
| `X-Infobip-Track` | boolean | Enable or disable open and click tracking. |
| `X-Infobip-TrackingUrl` | string | The URL on your callback server on which the open and click notifications will be sent. |
-| `X-Infobip-TrackClicks` | boolean | Enable or disable track click feature.. |
+| `X-Infobip-TrackClicks` | boolean | Enable or disable track click feature. |
| `X-Infobip-TrackOpens` | boolean | Enable or disable open click feature. |
+| `X-Infobip-IpPoolId` | string | The ID of the dedicated IP pool that will be used to deliver the message. |
Resources
---------
diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php
index aa417558c6a53..1cd48855c92a1 100644
--- a/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php
+++ b/src/Symfony/Component/Mailer/Bridge/Infobip/Tests/Transport/InfobipApiTransportTest.php
@@ -252,7 +252,8 @@ public function testSendEmailWithHeadersShouldCalledInfobipWithTheRightParameter
->addTextHeader('X-Infobip-Track', 'false')
->addTextHeader('X-Infobip-TrackingUrl', 'https://bar.foo')
->addTextHeader('X-Infobip-TrackClicks', 'true')
- ->addTextHeader('X-Infobip-TrackOpens', 'true');
+ ->addTextHeader('X-Infobip-TrackOpens', 'true')
+ ->addTextHeader('X-Infobip-IpPoolId', 'pool-123');
$this->transport->send($email);
@@ -308,6 +309,12 @@ public function testSendEmailWithHeadersShouldCalledInfobipWithTheRightParameter
Content-Disposition: form-data; name="trackOpens"
true
+ --%s
+ Content-Type: text/plain; charset=utf-8
+ Content-Transfer-Encoding: 8bit
+ Content-Disposition: form-data; name="ipPoolId"
+
+ pool-123
--%s--
TXT,
$options['body']
@@ -441,7 +448,8 @@ public function testSendEmailWithHeadersWithSuccess()
->addTextHeader('X-Infobip-Track', 'false')
->addTextHeader('X-Infobip-TrackingUrl', 'https://bar.foo')
->addTextHeader('X-Infobip-TrackClicks', 'true')
- ->addTextHeader('X-Infobip-TrackOpens', 'true');
+ ->addTextHeader('X-Infobip-TrackOpens', 'true')
+ ->addTextHeader('X-Infobip-IpPoolId', 'pool-123');
$sentMessage = $this->transport->send($email);
@@ -457,6 +465,7 @@ public function testSendEmailWithHeadersWithSuccess()
X-Infobip-TrackingUrl: https://bar.foo
X-Infobip-TrackClicks: true
X-Infobip-TrackOpens: true
+ X-Infobip-IpPoolId: pool-123
%a
TXT,
$sentMessage->toString()
diff --git a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php
index b2bfcc6961098..98a64f4a65f97 100644
--- a/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php
+++ b/src/Symfony/Component/Mailer/Bridge/Infobip/Transport/InfobipApiTransport.php
@@ -42,6 +42,7 @@ final class InfobipApiTransport extends AbstractApiTransport
'X-Infobip-TrackingUrl' => 'trackingUrl',
'X-Infobip-TrackClicks' => 'trackClicks',
'X-Infobip-TrackOpens' => 'trackOpens',
+ 'X-Infobip-IpPoolId' => 'ipPoolId',
];
public function __construct(
diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md
index 9bcfdb33600fd..7a025a0c6b198 100644
--- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md
+++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Add support for scheduling delivery with the `send_at` API parameter via a `Send-At` date-header
+
7.4
---
diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md b/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md
index 33ee90ef65b3b..bf3f1ece7f81d 100644
--- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md
+++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md
@@ -60,6 +60,20 @@ where:
- `GROUP_ID` is your Sendgrid suppression group ID
- `GROUPS_TO_DISPLAY_ID` is an array of the Sendgrid suppression group IDs presented to the user
+Scheduling
+----------
+
+When using the **API transport** (with a `sendgrid+api` DSN), you can schedule
+your emails by providing a `\DateTimeInterface` object in a
+`Symfony\Component\Mime\Header\DateHeader` named `Send-At`.
+
+```php
+$email = new \Symfony\Component\Mime\Email();
+$email->getHeaders()->addDateHeader('Send-At', new \DateTimeImmutable('+3 hours'));
+```
+It will be mapped to the `send_at` parameter of the `[POST] /mail/send`
+[API endpoint](https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send#request-body)
+
Resources
---------
diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php
index 3f3814df7ba90..b5a624579f31d 100644
--- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php
+++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Transport/SendgridApiTransportTest.php
@@ -313,4 +313,18 @@ public function testWithSuppressionGroup()
$this->assertSame([1, 2, 3, 4, 5], $payload['asm']['groups_to_display']);
}
+
+ public function testSendAtHeader()
+ {
+ $email = new Email();
+ $email->getHeaders()->addDateHeader('Send-At', new \DateTime('2025-05-07 16:00:00', new \DateTimeZone('Europe/Paris')));
+ $envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]);
+
+ $transport = new SendgridApiTransport('ACCESS_KEY');
+ $method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload');
+ $payload = $method->invoke($transport, $email, $envelope);
+
+ $this->assertArrayHasKey('send_at', $payload);
+ $this->assertSame(1746626400, $payload['send_at']);
+ }
}
diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php
index 5492d026870fc..39028b2d53c89 100644
--- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php
+++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Transport/SendgridApiTransport.php
@@ -23,6 +23,7 @@
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
+use Symfony\Component\Mime\Header\DateHeader;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -126,7 +127,12 @@ private function getPayload(Email $email, Envelope $envelope): array
continue;
}
- if ($header instanceof TagHeader) {
+ if ('send-at' === $name) {
+ if (!$header instanceof DateHeader) {
+ throw new TransportException(\sprintf('The "Send-At" header must be a "%s" instance.', DateHeader::class));
+ }
+ $payload['send_at'] = $header->getDateTime()->getTimestamp();
+ } elseif ($header instanceof TagHeader) {
if (10 === \count($categories)) {
throw new TransportException(\sprintf('Too many "%s" instances present in the email headers. Sendgrid does not accept more than 10 categories on an email.', TagHeader::class));
}
@@ -145,11 +151,11 @@ private function getPayload(Email $email, Envelope $envelope): array
}
}
- if (\count($categories) > 0) {
+ if ($categories) {
$payload['categories'] = $categories;
}
- if (\count($customArguments) > 0) {
+ if ($customArguments) {
$personalization['custom_args'] = $customArguments;
}
diff --git a/src/Symfony/Component/Mailer/Tests/MailerTest.php b/src/Symfony/Component/Mailer/Tests/MailerTest.php
index 1c537a0693934..76328aab37482 100644
--- a/src/Symfony/Component/Mailer/Tests/MailerTest.php
+++ b/src/Symfony/Component/Mailer/Tests/MailerTest.php
@@ -21,8 +21,8 @@
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mailer\Transport\NullTransport;
-use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\StampInterface;
use Symfony\Component\Mime\Address;
@@ -35,7 +35,7 @@ public function testSendingRawMessages()
{
$this->expectException(LogicException::class);
- $transport = new Mailer($this->createMock(TransportInterface::class), $this->createMock(MessageBusInterface::class), $this->createMock(EventDispatcherInterface::class));
+ $transport = new Mailer(new DummyTransport('localhost'), new MessageBus(), new EventDispatcher());
$transport->send(new RawMessage('Some raw email message'));
}
@@ -54,7 +54,7 @@ public function dispatch($message, array $stamps = []): Envelope
}
};
- $stamp = $this->createMock(StampInterface::class);
+ $stamp = new class implements StampInterface {};
$dispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher->expects($this->once())
diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
index 1d9dbcccfd20d..afee7cd21c3a2 100644
--- a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
+++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/SmtpTransportTest.php
@@ -142,8 +142,8 @@ public function testWriteEncodedRecipientAndSenderAddresses()
public function testMessageIdFromServerIsEmbeddedInSentMessageEvent()
{
$calls = 0;
- $eventDispatcher = $this->createMock(EventDispatcherInterface::class);
- $eventDispatcher->expects($this->any())
+ $eventDispatcher = $this->createStub(EventDispatcherInterface::class);
+ $eventDispatcher
->method('dispatch')
->with($this->callback(static function ($event) use (&$calls): bool {
++$calls;
diff --git a/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php b/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php
index e50db434ffc0c..2e725c90ae64f 100644
--- a/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php
+++ b/src/Symfony/Component/Mailer/Tests/Transport/TransportsTest.php
@@ -13,6 +13,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Tests\DummyTransport;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mailer\Transport\Transports;
use Symfony\Component\Mime\Header\Headers;
@@ -53,8 +54,8 @@ public function testOverrideTransport()
public function testTransportDoesNotExist()
{
$transport = new Transports([
- 'foo' => $this->createMock(TransportInterface::class),
- 'bar' => $this->createMock(TransportInterface::class),
+ 'foo' => new DummyTransport('localhost'),
+ 'bar' => new DummyTransport('localhost'),
]);
$headers = (new Headers())->addTextHeader('X-Transport', 'foobar');
@@ -69,7 +70,7 @@ public function testTransportRestoredAfterFailure()
{
$exception = new \Exception();
- $fooTransport = $this->createMock(TransportInterface::class);
+ $fooTransport = $this->createStub(TransportInterface::class);
$fooTransport->method('send')
->willThrowException($exception);
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php
index a908c234227ea..4cd709dcb46ca 100644
--- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php
@@ -667,6 +667,22 @@ public function testConfigureSchema()
$connection = new Connection(['table_name' => 'queue_table'], $driverConnection);
$connection->configureSchema($schema, $driverConnection, fn () => true);
$this->assertTrue($schema->hasTable('queue_table'));
+
+ // Ensure the covering index for the SELECT query exists
+ $table = $schema->getTable('queue_table');
+ $hasCoveringIndex = false;
+ foreach ($table->getIndexes() as $index) {
+ // Doctrine DBAL 4+: use getIndexedColumns(); fallback to getColumns() for older versions
+ $columns = method_exists($index, 'getIndexedColumns')
+ ? array_map(static fn ($ic) => $ic->getColumnName()->toString(), $index->getIndexedColumns())
+ : $index->getColumns();
+
+ if ($columns === ['queue_name', 'available_at', 'delivered_at', 'id']) {
+ $hasCoveringIndex = true;
+ break;
+ }
+ }
+ $this->assertTrue($hasCoveringIndex, 'Expected covering index on [queue_name, available_at, delivered_at, id] not found');
}
public function testConfigureSchemaDifferentDbalConnection()
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
index 24d03bbedc7ca..d6b109e893e8c 100644
--- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
@@ -12,10 +12,8 @@
namespace Symfony\Component\Messenger\Bridge\Doctrine\Transport;
use Doctrine\DBAL\Connection as DBALConnection;
-use Doctrine\DBAL\Driver\Exception as DriverException;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Exception\TableNotFoundException;
-use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
@@ -160,19 +158,6 @@ public function send(string $body, array $headers, int $delay = 0): string
public function get(): ?array
{
- if ($this->doMysqlCleanup && $this->driverConnection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
- try {
- $this->driverConnection->delete($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59']);
- $this->doMysqlCleanup = false;
- } catch (TableNotFoundException $e) {
- if ($this->autoSetup) {
- $this->setup();
- }
- } catch (DriverException $e) {
- // Ignore the exception
- }
- }
-
get:
$this->driverConnection->beginTransaction();
try {
@@ -246,14 +231,6 @@ public function get(): ?array
public function ack(string $id): bool
{
try {
- if ($this->driverConnection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
- if ($updated = $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0) {
- $this->doMysqlCleanup = true;
- }
-
- return $updated;
- }
-
return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
} catch (DBALException $exception) {
throw new TransportException($exception->getMessage(), 0, $exception);
@@ -263,14 +240,6 @@ public function ack(string $id): bool
public function reject(string $id): bool
{
try {
- if ($this->driverConnection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
- if ($updated = $this->driverConnection->update($this->configuration['table_name'], ['delivered_at' => '9999-12-31 23:59:59'], ['id' => $id]) > 0) {
- $this->doMysqlCleanup = true;
- }
-
- return $updated;
- }
-
return $this->driverConnection->delete($this->configuration['table_name'], ['id' => $id]) > 0;
} catch (DBALException $exception) {
throw new TransportException($exception->getMessage(), 0, $exception);
@@ -547,9 +516,7 @@ private function addTableToSchema(Schema $schema): void
$table->addColumn('delivered_at', Types::DATETIME_IMMUTABLE)
->setNotnull(false);
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
- $table->addIndex(['queue_name']);
- $table->addIndex(['available_at']);
- $table->addIndex(['delivered_at']);
+ $table->addIndex(['queue_name', 'available_at', 'delivered_at', 'id']);
// We need to create a sequence for Oracle and set the id column to get the correct nextval
if ($this->driverConnection->getDatabasePlatform() instanceof OraclePlatform) {
diff --git a/src/Symfony/Component/Messenger/Stamp/DelayStamp.php b/src/Symfony/Component/Messenger/Stamp/DelayStamp.php
index f75a2f85e40c7..1164007d15f3a 100644
--- a/src/Symfony/Component/Messenger/Stamp/DelayStamp.php
+++ b/src/Symfony/Component/Messenger/Stamp/DelayStamp.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Messenger\Stamp;
+use Symfony\Component\Clock\Clock;
+
/**
* Apply this stamp to delay delivery of your message on a transport.
*/
@@ -31,7 +33,7 @@ public function getDelay(): int
public static function delayFor(\DateInterval $interval): self
{
- $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
+ $now = Clock::get()->withTimeZone(new \DateTimeZone('UTC'))->now();
$end = $now->add($interval);
return new self(($end->getTimestamp() - $now->getTimestamp()) * 1000);
@@ -39,6 +41,6 @@ public static function delayFor(\DateInterval $interval): self
public static function delayUntil(\DateTimeInterface $dateTime): self
{
- return new self(($dateTime->getTimestamp() - time()) * 1000);
+ return new self(($dateTime->getTimestamp() - Clock::get()->now()->getTimestamp()) * 1000);
}
}
diff --git a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php
index 08077e73cde5b..bd5963c3b1a5c 100644
--- a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php
+++ b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Messenger\Stamp;
+use Symfony\Component\Clock\Clock;
use Symfony\Component\Messenger\Envelope;
/**
@@ -24,7 +25,7 @@ public function __construct(
private int $retryCount,
?\DateTimeInterface $redeliveredAt = null,
) {
- $this->redeliveredAt = $redeliveredAt ?? new \DateTimeImmutable();
+ $this->redeliveredAt = $redeliveredAt ?? Clock::get()->now();
}
public static function getRetryCountFromEnvelope(Envelope $envelope): int
diff --git a/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php b/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php
index cbcdf34706f4b..4392495cf8703 100644
--- a/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php
+++ b/src/Symfony/Component/Messenger/Tests/Command/FailedMessagesRetryCommandTest.php
@@ -226,7 +226,7 @@ public function testCompleteIdWithSpecifiedTransport()
$this->assertSame(['2ab50dfa1fbf', '78c2da843723'], $suggestions);
}
- public function testSuccessMessageGoesToStdout()
+ public function testSuccessMessageGoesToStdout()
{
$envelope = new Envelope(new \stdClass(), [new TransportMessageIdStamp('some_id')]);
$receiver = $this->createMock(ListableReceiverInterface::class);
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
index 06f06570465bd..6ca1ea8a9f3b0 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php
@@ -140,7 +140,7 @@ public function getTypeFromConstructor(string $class, string $property): ?Type
{
$declaringClass = $class;
if (!$tagDocNode = $this->getDocBlockFromConstructor($declaringClass, $property)) {
- return null;
+ return $this->getType($class, $property);
}
$typeContext = $this->typeContextFactory->createFromClassName($class, $declaringClass);
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
index 983ebfed680c3..ba03f89a6ed09 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
@@ -363,6 +363,7 @@ public function getWriteInfo(string $class, string $property, array $context = [
$allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true;
$camelized = $this->camelize($property);
+ $nonCamelized = ucfirst($property);
$constructor = $reflClass->getConstructor();
$singulars = $this->inflector->singularize($camelized);
$errors = [];
@@ -405,7 +406,26 @@ public function getWriteInfo(string $class, string $property, array $context = [
}
}
+ if ($camelized !== $nonCamelized) {
+ foreach ($this->mutatorPrefixes as $mutatorPrefix) {
+ $methodName = $mutatorPrefix.$nonCamelized;
+
+ [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1);
+ if (!$accessible) {
+ $errors[] = $methodAccessibleErrors;
+ continue;
+ }
+
+ $method = $reflClass->getMethod($methodName);
+
+ if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) {
+ return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisibilityForMethod($method), $method->isStatic());
+ }
+ }
+ }
+
$getsetter = lcfirst($camelized);
+ $getsetterNonCamelized = lcfirst($nonCamelized);
if ($allowGetterSetter) {
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1);
@@ -416,6 +436,16 @@ public function getWriteInfo(string $class, string $property, array $context = [
}
$errors[] = $methodAccessibleErrors;
+
+ if ($getsetter !== $getsetterNonCamelized) {
+ [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetterNonCamelized, 1);
+ if ($accessible) {
+ $method = $reflClass->getMethod($getsetterNonCamelized);
+
+ return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetterNonCamelized, $this->getWriteVisibilityForMethod($method), $method->isStatic());
+ }
+ $errors[] = $methodAccessibleErrors;
+ }
}
if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
index 47e061cf57172..f3e13ebf1d15a 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
@@ -12,9 +12,9 @@
namespace Symfony\Component\PropertyInfo\Tests\Extractor;
use phpDocumentor\Reflection\DocBlock;
+use phpDocumentor\Reflection\PseudoTypes\IntMask;
+use phpDocumentor\Reflection\PseudoTypes\IntMaskOf;
use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
-use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy;
@@ -29,7 +29,6 @@
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait;
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait;
use Symfony\Component\TypeInfo\Type;
-use Symfony\Component\TypeInfo\Type\NullableType;
/**
* @author Kévin Dunglas
@@ -372,6 +371,17 @@ public static function pseudoTypeProvider(): iterable
yield ['numericString', Type::string()];
yield ['traitString', Type::string()];
yield ['positiveInt', Type::int()];
+ yield ['true', Type::true()];
+ yield ['false', Type::false()];
+ yield ['valueOfStrings', null];
+ yield ['valueOfIntegers', null];
+ yield ['keyOfStrings', null];
+ yield ['keyOfIntegers', null];
+ yield ['arrayKey', null];
+ yield ['intMask', class_exists(IntMask::class) ? Type::int() : null];
+ yield ['intMaskOf', class_exists(IntMaskOf::class) ? Type::int() : null];
+ yield ['conditional', null];
+ yield ['offsetAccess', null];
}
#[DataProvider('promotedPropertyProvider')]
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
index 48f4c5781c5f3..f4fa28e28e01f 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php
@@ -18,6 +18,7 @@
use Symfony\Component\PropertyInfo\Tests\Fixtures\Clazz;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock;
+use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithVarTagsDocBlock;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
@@ -307,10 +308,29 @@ public static function constructorTypesProvider(): iterable
yield ['date', Type::int()];
yield ['timezone', Type::object(\DateTimeZone::class)];
yield ['dateObject', Type::object(\DateTimeInterface::class)];
- yield ['dateTime', null];
+ yield ['dateTime', Type::int()];
yield ['ddd', null];
}
+ #[DataProvider('constructorTypesWithOnlyVarTagsProvider')]
+ public function testExtractConstructorTypesWithOnlyVarTags(string $property, ?Type $type)
+ {
+ $this->assertEquals($type, $this->extractor->getTypeFromConstructor(ConstructorDummyWithVarTagsDocBlock::class, $property));
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function constructorTypesWithOnlyVarTagsProvider(): iterable
+ {
+ yield ['date', Type::int()];
+ yield ['dateObject', Type::object(\DateTimeInterface::class)];
+ yield ['objectsArray', Type::array(Type::object(ConstructorDummy::class))];
+ yield ['dateTime', null];
+ yield ['mixed', null];
+ yield ['timezone', null];
+ }
+
#[DataProvider('constructorTypesOfParentClassProvider')]
public function testExtractTypeFromConstructorOfParentClass(string $class, string $property, Type $type)
{
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
index b44469e87bea6..c1e27e19264fb 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
@@ -454,6 +454,9 @@ public static function writeMutatorProvider(): array
[Php71DummyExtended2::class, 'string', false, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended2::class, 'string', true, false, '', '', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
[Php71DummyExtended2::class, 'baz', false, true, PropertyWriteInfo::TYPE_ADDER_AND_REMOVER, null, 'addBaz', 'removeBaz', PropertyWriteInfo::VISIBILITY_PUBLIC, false],
+ [SnakeCaseDummy::class, 'snake_property', false, true, PropertyWriteInfo::TYPE_METHOD, 'setSnakeProperty', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
+ [SnakeCaseDummy::class, 'snake_method', false, true, PropertyWriteInfo::TYPE_METHOD, 'setSnake_method', null, null, PropertyWriteInfo::VISIBILITY_PUBLIC, false],
+ [SnakeCaseDummy::class, 'snake_readonly', false, false, PropertyWriteInfo::TYPE_NONE, null, null, null, null, null],
];
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php
new file mode 100644
index 0000000000000..57e50f4ea2f97
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummyWithVarTagsDocBlock.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
+
+
+class ConstructorDummyWithVarTagsDocBlock
+{
+ public function __construct(
+ public \DateTimeZone $timezone,
+ /** @var int */
+ public $date,
+ /** @var \DateTimeInterface */
+ public $dateObject,
+ public \DateTimeImmutable $dateTime,
+ public $mixed,
+ /** @var ConstructorDummy[] */
+ public array $objectsArray,
+ )
+ {
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php
index 48574a1efe43e..8000f79adf984 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/PseudoTypesDummy.php
@@ -16,6 +16,9 @@
*/
class PseudoTypesDummy
{
+ public const STRINGS = ['A' => 'A', 'B' => 'B'];
+ public const INTEGERS = [1, 2];
+
/** @var class-string */
public $classString;
@@ -45,4 +48,37 @@ class PseudoTypesDummy
/** @var literal-string */
public $literalString;
+
+ /** @var true */
+ public $true;
+
+ /** @var false */
+ public $false;
+
+ /** @var value-of */
+ public $valueOfStrings;
+
+ /** @var value-of */
+ public $valueOfIntegers;
+
+ /** @var key-of */
+ public $keyOfStrings;
+
+ /** @var key-of */
+ public $keyOfIntegers;
+
+ /** @var array-key */
+ public $arrayKey;
+
+ /** @var int-mask<1,2,4> */
+ public $intMask;
+
+ /** @var int-mask-of<1|2|4> */
+ public $intMaskOf;
+
+ /** @var (T is int ? string : int) */
+ public $conditional;
+
+ /** @var self::STRINGS['A'] */
+ public $offsetAccess;
}
diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
index 3c6abc57d2e67..ff0aceec29bf7 100644
--- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
+++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
@@ -157,14 +157,6 @@ private function createType(DocType $docType): ?Type
return Type::array($collectionValueType, $collectionKeyType);
}
- if ($docType instanceof PseudoType) {
- if ($docType->underlyingType() instanceof Integer) {
- return Type::int();
- } elseif ($docType->underlyingType() instanceof String_) {
- return Type::string();
- }
- }
-
$docTypeString = match ($docTypeString) {
'integer' => 'int',
'boolean' => 'bool',
@@ -181,7 +173,22 @@ private function createType(DocType $docType): ?Type
return Type::array();
}
- return null !== $class ? Type::object($class) : Type::builtin($phpType);
+ if (null === $class) {
+ return Type::builtin($phpType);
+ }
+
+ if ($docType instanceof PseudoType) {
+ if ($docType->underlyingType() instanceof Integer) {
+ return Type::int();
+ } elseif ($docType->underlyingType() instanceof String_) {
+ return Type::string();
+ } else {
+ // It's safer to fall back to other extractors here, as resolving pseudo types correctly is not easy at the moment
+ return null;
+ }
+ }
+
+ return Type::object($class);
}
private function getPhpTypeAndClass(string $docType): array
diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php
index 32d57e9c8dd5d..b85b1e1e5391f 100644
--- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php
+++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php
@@ -271,7 +271,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem
// add a query string if needed
$extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, fn ($a, $b) => $a == $b ? 0 : 1);
- $extra = array_merge($extra, $queryParameters);
+ $extra = array_replace($extra, $queryParameters);
array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) {
if (\is_object($v)) {
diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
index d7dd9bb6b885d..ddadcf0dc6c98 100644
--- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
+++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
@@ -12,7 +12,6 @@
namespace Symfony\Component\Routing\Tests\Generator;
use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
@@ -149,6 +148,7 @@ public static function valuesProvider(): array
'stdClass in nested stdClass' => ['?foo%5Bnested%5D%5Bbaz%5D=bar', 'foo', $nestedStdClass],
'non stringable object' => ['', 'foo', new NonStringableObject()],
'non stringable object but has public property' => ['?foo%5Bfoo%5D=property', 'foo', new NonStringableObjectWithPublicProperty()],
+ 'numeric key' => ['?123=foo', '123', 'foo'],
];
}
diff --git a/src/Symfony/Component/Routing/Tests/RequestContextTest.php b/src/Symfony/Component/Routing/Tests/RequestContextTest.php
index f40045f78919d..83c2293ceaf3e 100644
--- a/src/Symfony/Component/Routing/Tests/RequestContextTest.php
+++ b/src/Symfony/Component/Routing/Tests/RequestContextTest.php
@@ -47,7 +47,7 @@ public function testConstruct()
public function testConstructParametersBcLayer()
{
- $requestContext = new class() extends RequestContext {
+ $requestContext = new class extends RequestContext {
public function __construct()
{
$this->setParameters(['foo' => 'bar']);
diff --git a/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php b/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php
index 92b1f3a37c714..b4cd25b8a33f6 100644
--- a/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php
+++ b/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php
@@ -82,7 +82,7 @@ public function updateAutoloadFile(): void
$projectDir = substr($projectDir, 3);
}
- // the hack about __DIR__ is required because composer pre-processes plugins
+ // the hack about __DIR__ is required because Composer pre-processes plugins
if (!$nestingLevel) {
$projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true);
} else {
@@ -118,3 +118,5 @@ public static function getSubscribedEvents(): array
];
}
}
+
+// @php-cs-fixer-ignore no_useless_concat_operator Disable to not override hack about __DIR__ and Composer pre-processes plugins
diff --git a/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php b/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php
index eaba079f68350..4f64f9880921d 100644
--- a/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php
+++ b/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenGenerator.php
@@ -44,19 +44,19 @@ public function generate(string $userIdentifier, ?string $algorithmAlias = null,
$now = $this->clock->now();
$payload = [
$this->claim => $userIdentifier,
- 'iat' => $now->getTimestamp(), # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
- 'aud' => $this->audience, # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
- 'iss' => $this->getIssuer($issuer), # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
+ 'iat' => $now->getTimestamp(), // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
+ 'aud' => $this->audience, // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
+ 'iss' => $this->getIssuer($issuer), // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
];
if ($ttl) {
if (0 > $ttl) {
throw new \InvalidArgumentException('Time to live must be a positive integer.');
}
- $payload['exp'] = $now->add(new \DateInterval("PT{$ttl}S"))->getTimestamp(); # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
+ $payload['exp'] = $now->add(new \DateInterval("PT{$ttl}S"))->getTimestamp(); // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
}
if ($notBefore) {
- $payload['nbf'] = $notBefore->getTimestamp(); # https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
+ $payload['nbf'] = $notBefore->getTimestamp(); // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
}
$jws = $jwsBuilder
@@ -74,13 +74,14 @@ private function getAlgorithm(?string $alias): Algorithm
{
if ($alias) {
if (!$this->algorithmManager->has($alias)) {
- throw new \InvalidArgumentException(sprintf('"%s" is not a valid algorithm. Available algorithms: "%s".', $alias, implode('", "', $this->algorithmManager->list())));
+ throw new \InvalidArgumentException(\sprintf('"%s" is not a valid algorithm. Available algorithms: "%s".', $alias, implode('", "', $this->algorithmManager->list())));
}
+
return $this->algorithmManager->get($alias);
}
- if (1 !== count($list = $this->algorithmManager->list())) {
- throw new \InvalidArgumentException(sprintf('Please choose an algorithm. Available algorithms: "%s".', implode('", "', $list)));
+ if (1 !== \count($list = $this->algorithmManager->list())) {
+ throw new \InvalidArgumentException(\sprintf('Please choose an algorithm. Available algorithms: "%s".', implode('", "', $list)));
}
return $this->algorithmManager->get($list[0]);
@@ -89,15 +90,15 @@ private function getAlgorithm(?string $alias): Algorithm
private function getIssuer(?string $issuer): string
{
if ($issuer) {
- if (!in_array($issuer, $this->issuers, true)) {
- throw new \InvalidArgumentException(sprintf('"%s" is not a valid issuer. Available issuers: "%s".', $issuer, implode('", "', $this->issuers)));
+ if (!\in_array($issuer, $this->issuers, true)) {
+ throw new \InvalidArgumentException(\sprintf('"%s" is not a valid issuer. Available issuers: "%s".', $issuer, implode('", "', $this->issuers)));
}
return $issuer;
}
- if (1 !== count($this->issuers)) {
- throw new \InvalidArgumentException(sprintf('Please choose an issuer. Available issuers: "%s".', implode('", "', $this->issuers)));
+ if (1 !== \count($this->issuers)) {
+ throw new \InvalidArgumentException(\sprintf('Please choose an issuer. Available issuers: "%s".', implode('", "', $this->issuers)));
}
return $this->issuers[0];
diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md
index 7ed486f313342..1b3b9835c9bdd 100644
--- a/src/Symfony/Component/Security/Http/CHANGELOG.md
+++ b/src/Symfony/Component/Security/Http/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+8.1
+---
+
+ * Add support for the `clientHints`, `prefetchCache`, and `prerenderCache` `ClearSite-Data` directives
+
8.0
---
diff --git a/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php b/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php
index 77ca07a642835..0c72a214bc435 100644
--- a/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php
+++ b/src/Symfony/Component/Security/Http/EventListener/ClearSiteDataLogoutListener.php
@@ -27,7 +27,7 @@ class ClearSiteDataLogoutListener implements EventSubscriberInterface
/**
* @param string[] $cookieValue The value for the Clear-Site-Data header.
- * Can be '*' or a subset of 'cache', 'cookies', 'storage', 'executionContexts'.
+ * Can be '*' or a subset of 'cache', 'cookies', 'storage', 'clientHints', 'executionContexts', 'prefetchCache', 'prerenderCache'.
*/
public function __construct(private readonly array $cookieValue)
{
diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php
index 42ceff76444da..9cf21aa36ae26 100644
--- a/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/EventListener/ClearSiteDataLogoutListenerTest.php
@@ -43,5 +43,6 @@ public static function provideClearSiteDataConfig(): iterable
{
yield [['*'], '"*"'];
yield [['cache', 'cookies', 'storage', 'executionContexts'], '"cache", "cookies", "storage", "executionContexts"'];
+ yield [['clientHints', 'executionContexts', 'prefetchCache', 'prerenderCache'], '"clientHints", "executionContexts", "prefetchCache", "prerenderCache"'];
}
}
diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
index 0b2fb02fb06e4..d9b7a5d152394 100644
--- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
+++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
@@ -480,7 +480,7 @@ private function selectNodeType(\DOMNode $node, mixed $val, string $format, arra
return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context);
} elseif (is_numeric($val)) {
- return $this->appendText($node, (string) $val);
+ return $this->appendText($node, is_nan($val) ? 'NAN' : (string) $val);
} elseif (\is_string($val) && $this->needsCdataWrapping($node->nodeName, $val, $context)) {
return $this->appendCData($node, $val);
} elseif (\is_string($val)) {
diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
index ae6e6b2b875d5..39a8e118ca99d 100644
--- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
@@ -959,6 +959,26 @@ public function testEncodeWithoutComment()
$this->assertEquals($expected, $encoder->encode($data, 'xml'));
}
+ public function testEncodeNan()
+ {
+ $value = \NAN;
+
+ $expected = ''."\n".
+ 'NAN'."\n";
+
+ $this->assertEquals($expected, $this->encoder->encode($value, 'xml'));
+ }
+
+ public function testEncodeInfinite()
+ {
+ $value = \INF;
+
+ $expected = ''."\n".
+ 'INF'."\n";
+
+ $this->assertEquals($expected, $this->encoder->encode($value, 'xml'));
+ }
+
private function createXmlEncoderWithEnvelopeNormalizer(): XmlEncoder
{
$normalizers = [
diff --git a/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php b/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php
index 1d68494c66740..18ab959238fa0 100644
--- a/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php
+++ b/src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php
@@ -237,7 +237,7 @@ private function exportFilesAsync(array $locales, array $domains): array
return $this->getZipContents($extractPath);
} finally {
- if (is_resource($h)) {
+ if (\is_resource($h)) {
fclose($h);
}
@unlink($zipFile);
diff --git a/src/Symfony/Component/Uid/BinaryUtil.php b/src/Symfony/Component/Uid/BinaryUtil.php
index 7d1e524e5e43e..0c16b9414b19f 100644
--- a/src/Symfony/Component/Uid/BinaryUtil.php
+++ b/src/Symfony/Component/Uid/BinaryUtil.php
@@ -187,3 +187,5 @@ public static function dateTimeToHex(\DateTimeInterface $time): string
return bin2hex($time);
}
}
+
+// @php-cs-fixer-ignore long_to_shorthand_operator To prevent false positive causing "Cannot use assign-op operators with string offsets" error
diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php
index ad17f5d25c110..eefdfc434f9d0 100644
--- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php
@@ -83,7 +83,7 @@ public function validate(mixed $value, Constraint $constraint): void
}
$pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN;
- $pattern = sprintf($pattern, $protocols);
+ $pattern = \sprintf($pattern, $protocols);
if (!preg_match($pattern, $value)) {
$this->context->buildViolation($constraint->message)
diff --git a/src/Symfony/Component/Validator/Constraints/Video.php b/src/Symfony/Component/Validator/Constraints/Video.php
index 796dfb94e0dca..adc4cfedb72bd 100644
--- a/src/Symfony/Component/Validator/Constraints/Video.php
+++ b/src/Symfony/Component/Validator/Constraints/Video.php
@@ -13,7 +13,6 @@
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
-use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Exception\LogicException;
/**
@@ -131,7 +130,6 @@ class Video extends File
*
* @see https://www.iana.org/assignments/media-types/media-types.xhtml Existing media types
*/
- #[HasNamedArguments]
public function __construct(
int|string|null $maxSize = null,
?bool $binaryFormat = null,
diff --git a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php
index d112f86f7fba1..7120b86b66933 100644
--- a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php
+++ b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php
@@ -122,10 +122,10 @@ protected function restoreDefaultTimezone()
protected function createContext()
{
- $translator = $this->createMock(TranslatorInterface::class);
- $translator->expects($this->any())->method('trans')->willReturnArgument(0);
- $validator = $this->createMock(ValidatorInterface::class);
- $validator->expects($this->any())
+ $translator = $this->createStub(TranslatorInterface::class);
+ $translator->method('trans')->willReturnArgument(0);
+ $validator = $this->createStub(ValidatorInterface::class);
+ $validator
->method('validate')
->willReturnCallback(fn () => $this->expectedViolations[$this->call++] ?? new ConstraintViolationList());
@@ -134,36 +134,47 @@ protected function createContext()
$context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath);
$context->setConstraint($this->constraint);
- $contextualValidatorMockBuilder = $this->getMockBuilder(AssertingContextualValidator::class)
- ->setConstructorArgs([$context]);
- $contextualValidatorMethods = [
- 'atPath',
- 'validate',
- 'validateProperty',
- 'validatePropertyValue',
- 'getViolations',
- ];
-
- $contextualValidatorMockBuilder->onlyMethods($contextualValidatorMethods);
- $contextualValidator = $contextualValidatorMockBuilder->getMock();
- $contextualValidator->expects($this->any())
+ if (method_exists($this, 'getStubBuilder')) {
+ $contextualValidator = self::getStubBuilder(AssertingContextualValidator::class)
+ ->setConstructorArgs([$context])
+ ->onlyMethods([
+ 'atPath',
+ 'validate',
+ 'validateProperty',
+ 'validatePropertyValue',
+ 'getViolations',
+ ])
+ ->getStub();
+ } else {
+ $contextualValidator = $this->getMockBuilder(AssertingContextualValidator::class)
+ ->setConstructorArgs([$context])
+ ->onlyMethods([
+ 'atPath',
+ 'validate',
+ 'validateProperty',
+ 'validatePropertyValue',
+ 'getViolations',
+ ])
+ ->getMock();
+ }
+
+ $contextualValidator
->method('atPath')
->willReturnCallback(fn ($path) => $contextualValidator->doAtPath($path));
- $contextualValidator->expects($this->any())
+ $contextualValidator
->method('validate')
->willReturnCallback(fn ($value, $constraints = null, $groups = null) => $contextualValidator->doValidate($value, $constraints, $groups));
- $contextualValidator->expects($this->any())
+ $contextualValidator
->method('validateProperty')
->willReturnCallback(fn ($object, $propertyName, $groups = null) => $contextualValidator->validateProperty($object, $propertyName, $groups));
- $contextualValidator->expects($this->any())
+ $contextualValidator
->method('validatePropertyValue')
->willReturnCallback(fn ($objectOrClass, $propertyName, $value, $groups = null) => $contextualValidator->doValidatePropertyValue($objectOrClass, $propertyName, $value, $groups));
- $contextualValidator->expects($this->any())
+ $contextualValidator
->method('getViolations')
->willReturnCallback(fn () => $contextualValidator->doGetViolations());
- $validator->expects($this->any())
+ $validator
->method('inContext')
- ->with($context)
->willReturn($contextualValidator);
return $context;
diff --git a/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php
index 35f74a0c0ad30..bcc4587dc5c58 100644
--- a/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php
+++ b/src/Symfony/Component/Validator/Tests/Command/DebugCommandTest.php
@@ -15,7 +15,6 @@
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Validator\Command\DebugCommand;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
-use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
use Symfony\Component\Validator\Tests\Dummy\DummyClassOne;
@@ -180,9 +179,7 @@ public function testOutputWithPathArgument()
public function testOutputWithInvalidClassArgument()
{
- $validator = $this->createMock(MetadataFactoryInterface::class);
-
- $command = new DebugCommand($validator);
+ $command = new DebugCommand(new LazyLoadingMetadataFactory());
$tester = new CommandTester($command);
$tester->execute(['class' => 'App\\NotFoundResource'], ['decorated' => false]);
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php
index 8ac513e1b8fd8..a20602e30a883 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php
@@ -257,7 +257,7 @@ public function testExpressionLanguageUsage()
{
$constraint = new Expression(expression: 'false');
- $expressionLanguage = $this->createMock(ExpressionLanguage::class);
+ $expressionLanguage = $this->createStub(ExpressionLanguage::class);
$used = false;
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php
index db9ced0e6a25f..25d75f4ac776b 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Tests\Constraints;
+use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\Validator\Constraints\Luhn;
use Symfony\Component\Validator\Constraints\NotCompromisedPassword;
use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator;
@@ -18,9 +20,7 @@
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
-use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
-use Symfony\Contracts\HttpClient\ResponseInterface;
/**
* @author Kévin Dunglas
@@ -195,7 +195,6 @@ public function testInvalidValue()
public function testApiError()
{
$this->expectException(ExceptionInterface::class);
- $this->expectExceptionMessage('Problem contacting the Have I been Pwned API.');
$this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, new NotCompromisedPassword());
}
@@ -208,44 +207,20 @@ public function testApiErrorSkipped()
private function createHttpClientStub(?string $returnValue = null): HttpClientInterface
{
- $httpClientStub = $this->createMock(HttpClientInterface::class);
- $httpClientStub->method('request')->willReturnCallback(
- function (string $method, string $url) use ($returnValue): ResponseInterface {
- if (self::PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL === $url) {
- throw new class('Problem contacting the Have I been Pwned API.') extends \Exception implements ServerExceptionInterface {
- public function getResponse(): ResponseInterface
- {
- throw new \RuntimeException('Not implemented');
- }
- };
- }
-
- $responseStub = $this->createMock(ResponseInterface::class);
- $responseStub
- ->method('getContent')
- ->willReturn($returnValue ?? implode("\r\n", self::RETURN));
-
- return $responseStub;
+ return new MockHttpClient(function ($method, $url) use ($returnValue) {
+ if (self::PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL !== $url) {
+ return new MockResponse($returnValue ?? implode("\r\n", self::RETURN));
}
- );
-
- return $httpClientStub;
+ });
}
private function createHttpClientStubCustomEndpoint($expectedEndpoint): HttpClientInterface
{
- $httpClientStub = $this->createMock(HttpClientInterface::class);
- $httpClientStub->method('request')->with('GET', $expectedEndpoint)->willReturnCallback(
- function (string $method, string $url): ResponseInterface {
- $responseStub = $this->createMock(ResponseInterface::class);
- $responseStub
- ->method('getContent')
- ->willReturn(implode("\r\n", self::RETURN));
-
- return $responseStub;
- }
- );
+ return new MockHttpClient(function ($method, $url) use ($expectedEndpoint) {
+ $this->assertSame('GET', $method);
+ $this->assertSame($expectedEndpoint, $url);
- return $httpClientStub;
+ return new MockResponse(implode("\r\n", self::RETURN));
+ });
}
}
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php
index 879cee69f3706..f7b4d82e03218 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php
@@ -260,7 +260,7 @@ public function testDeprecatedTimezonesAreValidWithBC(string $timezone)
{
// Skip test if the timezone is not available in the current timezone database
if (!\in_array($timezone, \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), true)) {
- $this->markTestSkipped(sprintf('Timezone "%s" is not available in the current timezone database', $timezone));
+ $this->markTestSkipped(\sprintf('Timezone "%s" is not available in the current timezone database', $timezone));
}
$constraint = new Timezone(\DateTimeZone::ALL_WITH_BC);
diff --git a/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php b/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php
index 29fd4b38d151f..5bae9b6db835e 100644
--- a/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php
+++ b/src/Symfony/Component/Validator/Tests/DataCollector/ValidatorDataCollectorTest.php
@@ -22,14 +22,14 @@ class ValidatorDataCollectorTest extends TestCase
{
public function testCollectsValidatorCalls()
{
- $originalValidator = $this->createMock(ValidatorInterface::class);
+ $originalValidator = $this->createStub(ValidatorInterface::class);
$validator = new TraceableValidator($originalValidator);
$collector = new ValidatorDataCollector($validator);
$violations = new ConstraintViolationList([
- $this->createMock(ConstraintViolation::class),
- $this->createMock(ConstraintViolation::class),
+ $this->createStub(ConstraintViolation::class),
+ $this->createStub(ConstraintViolation::class),
]);
$originalValidator->method('validate')->willReturn($violations);
@@ -52,14 +52,14 @@ public function testCollectsValidatorCalls()
public function testReset()
{
- $originalValidator = $this->createMock(ValidatorInterface::class);
+ $originalValidator = $this->createStub(ValidatorInterface::class);
$validator = new TraceableValidator($originalValidator);
$collector = new ValidatorDataCollector($validator);
$violations = new ConstraintViolationList([
- $this->createMock(ConstraintViolation::class),
- $this->createMock(ConstraintViolation::class),
+ $this->createStub(ConstraintViolation::class),
+ $this->createStub(ConstraintViolation::class),
]);
$originalValidator->method('validate')->willReturn($violations);
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
index afd650581a77e..969dac113136e 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php
@@ -111,13 +111,12 @@ public function testCachedMetadata()
public function testNonClassNameStringValues()
{
$testedValue = 'error@example.com';
- $loader = $this->createMock(LoaderInterface::class);
$cache = $this->createMock(CacheItemPoolInterface::class);
$cache
->expects($this->never())
->method('getItem');
- $factory = new LazyLoadingMetadataFactory($loader, $cache);
+ $factory = new LazyLoadingMetadataFactory(new StaticMethodLoader(), $cache);
$this->expectException(NoSuchMetadataException::class);
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php
index ffb0dd23bdf3b..4cf4d1c557ad7 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php
@@ -14,6 +14,7 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
+use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
use Symfony\Component\Validator\Tests\Fixtures\FilesLoader;
use Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity;
@@ -21,7 +22,7 @@ class FilesLoaderTest extends TestCase
{
public function testCallsGetFileLoaderInstanceForeachPath()
{
- $loader = $this->getFilesLoader($this->createMock(LoaderInterface::class));
+ $loader = $this->getFilesLoader(new StaticMethodLoader());
$this->assertEquals(4, $loader->getTimesCalled());
}
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php
index 9ebe33ec1f59e..1d0138ec04e9e 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php
@@ -44,13 +44,13 @@ public function testReturnsTrueIfAnyLoaderReturnedTrue()
{
$metadata = new ClassMetadata('\stdClass');
- $loader1 = $this->createMock(LoaderInterface::class);
- $loader1->expects($this->any())
+ $loader1 = $this->createStub(LoaderInterface::class);
+ $loader1
->method('loadClassMetadata')
->willReturn(true);
- $loader2 = $this->createMock(LoaderInterface::class);
- $loader2->expects($this->any())
+ $loader2 = $this->createStub(LoaderInterface::class);
+ $loader2
->method('loadClassMetadata')
->willReturn(false);
@@ -66,13 +66,13 @@ public function testReturnsFalseIfNoLoaderReturnedTrue()
{
$metadata = new ClassMetadata('\stdClass');
- $loader1 = $this->createMock(LoaderInterface::class);
- $loader1->expects($this->any())
+ $loader1 = $this->createStub(LoaderInterface::class);
+ $loader1
->method('loadClassMetadata')
->willReturn(false);
- $loader2 = $this->createMock(LoaderInterface::class);
- $loader2->expects($this->any())
+ $loader2 = $this->createStub(LoaderInterface::class);
+ $loader2
->method('loadClassMetadata')
->willReturn(false);
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php
index 19c9045441aaf..bf685a3dbfb34 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php
@@ -39,7 +39,7 @@ class PropertyInfoLoaderTest extends TestCase
{
public function testLoadClassMetadata()
{
- $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class);
+ $propertyListExtractor = $this->createStub(PropertyListExtractorInterface::class);
$propertyListExtractor
->method('getProperties')
->willReturn([
@@ -228,7 +228,7 @@ public function getTypes(string $class, string $property, array $context = []):
#[DataProvider('regexpProvider')]
public function testClassValidator(bool $expected, ?string $classValidatorRegexp = null)
{
- $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class);
+ $propertyListExtractor = $this->createStub(PropertyListExtractorInterface::class);
$propertyListExtractor
->method('getProperties')
->willReturn(['string'])
@@ -267,7 +267,7 @@ public static function regexpProvider(): array
public function testClassNoAutoMapping(?PropertyTypeExtractorInterface $propertyListExtractor = null)
{
if (null === $propertyListExtractor) {
- $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class);
+ $propertyListExtractor = $this->createStub(PropertyListExtractorInterface::class);
$propertyListExtractor
->method('getProperties')
->willReturn(['string', 'autoMappingExplicitlyEnabled'])
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
index 162603b3a174a..a98d6be403df9 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
@@ -16,10 +16,14 @@
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;
+use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\IsTrue;
+use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
+use Symfony\Component\Validator\Constraints\Optional;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Regex;
+use Symfony\Component\Validator\Constraints\Required;
use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
@@ -77,6 +81,8 @@ public function testLoadClassMetadata()
$expected->addPropertyConstraint('firstName', new Collection(fields: [
'foo' => [new NotNull(), new Range(min: 3)],
'bar' => [new Range(min: 5)],
+ 'baz' => new Required([new Email()]),
+ 'qux' => new Optional([new NotBlank()]),
]));
$expected->addPropertyConstraint('firstName', new Choice(
message: 'Must be one of %choices%',
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
index e076e814ac492..97c13e3723408 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
@@ -17,9 +17,13 @@
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;
+use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\IsTrue;
+use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
+use Symfony\Component\Validator\Constraints\Optional;
use Symfony\Component\Validator\Constraints\Range;
+use Symfony\Component\Validator\Constraints\Required;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
use Symfony\Component\Validator\Tests\Dummy\DummyGroupProvider;
@@ -120,6 +124,8 @@ public function testLoadClassMetadata()
$expected->addPropertyConstraint('firstName', new Collection(fields: [
'foo' => [new NotNull(), new Range(min: 3)],
'bar' => [new Range(min: 5)],
+ 'baz' => new Required([new Email()]),
+ 'qux' => new Optional([new NotBlank()]),
]));
$expected->addPropertyConstraint('firstName', new Choice(
message: 'Must be one of %choices%',
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml
index 588833208db0a..4ece3aec548a0 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml
@@ -97,6 +97,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml
index e31a64189ecff..b40bfc02ad87a 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.yml
@@ -53,6 +53,14 @@ Symfony\Component\Validator\Tests\Fixtures\NestedAttribute\Entity:
bar:
- Range:
min: 5
+ baz:
+ - Required:
+ constraints:
+ - Email: ~
+ qux:
+ - Optional:
+ constraints:
+ - NotBlank: ~
# Constraint with options
- Choice: { choices: [A, B], message: Must be one of %choices% }
dummy:
diff --git a/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php
index ae728b19cb6c0..799d4b98a3061 100644
--- a/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Validator/TraceableValidatorTest.php
@@ -12,7 +12,7 @@
namespace Symfony\Component\Validator\Tests\Validator;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@@ -27,15 +27,15 @@ public function testValidate()
{
$originalValidator = $this->createMock(ValidatorInterface::class);
$violations = new ConstraintViolationList([
- $this->createMock(ConstraintViolation::class),
- $this->createMock(ConstraintViolation::class),
+ $this->createStub(ConstraintViolation::class),
+ $this->createStub(ConstraintViolation::class),
]);
$originalValidator->expects($this->exactly(2))->method('validate')->willReturn($violations);
$validator = new TraceableValidator($originalValidator);
$object = new \stdClass();
- $constraints = [$this->createMock(Constraint::class)];
+ $constraints = [new NotNull()];
$groups = ['Default', 'Create'];
$validator->validate($object, $constraints, $groups);
@@ -74,16 +74,16 @@ public function testForwardsToOriginalValidator()
$expects = fn ($method) => $originalValidator->expects($this->once())->method($method);
- $expects('getMetadataFor')->willReturn($expected = $this->createMock(MetadataInterface::class));
+ $expects('getMetadataFor')->willReturn($expected = $this->createStub(MetadataInterface::class));
$this->assertSame($expected, $validator->getMetadataFor('value'), 'returns original validator getMetadataFor() result');
$expects('hasMetadataFor')->willReturn($expected = false);
$this->assertSame($expected, $validator->hasMetadataFor('value'), 'returns original validator hasMetadataFor() result');
- $expects('inContext')->willReturn($expected = $this->createMock(ContextualValidatorInterface::class));
- $this->assertSame($expected, $validator->inContext($this->createMock(ExecutionContextInterface::class)), 'returns original validator inContext() result');
+ $expects('inContext')->willReturn($expected = $this->createStub(ContextualValidatorInterface::class));
+ $this->assertSame($expected, $validator->inContext($this->createStub(ExecutionContextInterface::class)), 'returns original validator inContext() result');
- $expects('startContext')->willReturn($expected = $this->createMock(ContextualValidatorInterface::class));
+ $expects('startContext')->willReturn($expected = $this->createStub(ContextualValidatorInterface::class));
$this->assertSame($expected, $validator->startContext(), 'returns original validator startContext() result');
$expects('validate')->willReturn($expected = new ConstraintViolationList());
diff --git a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php
index ec464b748635f..3c238a1051a4c 100644
--- a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php
+++ b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php
@@ -13,11 +13,11 @@
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\Validator\Validator\RecursiveValidator;
use Symfony\Component\Validator\ValidatorBuilder;
-use Symfony\Contracts\Translation\TranslatorInterface;
class ValidatorBuilderTest extends TestCase
{
@@ -31,7 +31,7 @@ protected function setUp(): void
public function testAddObjectInitializer()
{
$this->assertSame($this->builder, $this->builder->addObjectInitializer(
- $this->createMock(ObjectInitializerInterface::class)
+ $this->createStub(ObjectInitializerInterface::class)
));
}
@@ -77,21 +77,19 @@ public function testDisableAttributeMapping()
public function testSetMappingCache()
{
- $this->assertSame($this->builder, $this->builder->setMappingCache($this->createMock(CacheItemPoolInterface::class)));
+ $this->assertSame($this->builder, $this->builder->setMappingCache($this->createStub(CacheItemPoolInterface::class)));
}
public function testSetConstraintValidatorFactory()
{
$this->assertSame($this->builder, $this->builder->setConstraintValidatorFactory(
- $this->createMock(ConstraintValidatorFactoryInterface::class))
+ $this->createStub(ConstraintValidatorFactoryInterface::class))
);
}
public function testSetTranslator()
{
- $this->assertSame($this->builder, $this->builder->setTranslator(
- $this->createMock(TranslatorInterface::class))
- );
+ $this->assertSame($this->builder, $this->builder->setTranslator(new IdentityTranslator()));
}
public function testSetTranslationDomain()
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
index 0f12f14f393b3..dd35bfefa734c 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
@@ -651,7 +651,7 @@ public function testReflectionClassConstantWithAttribute()
public function testReflectionParameterWithAttribute()
{
$var = new \ReflectionParameter([LotsOfAttributes::class, 'someMethod'], 'someParameter');
-
+
$this->assertDumpMatchesFormat(<<assertEquals('0', $arc->place);
+ }
}
diff --git a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php
index 5342f5b82c341..c86efe17d1bb5 100644
--- a/src/Symfony/Contracts/Translation/Test/TranslatorTest.php
+++ b/src/Symfony/Contracts/Translation/Test/TranslatorTest.php
@@ -27,7 +27,7 @@
*
* As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199
*
- * The goal to cover all languages is to far fetched so this test case is smaller.
+ * The goal to cover all languages is too far fetched so this test case is smaller.
*
* @author Clemens Tolboom clemens@build2be.nl
*/
@@ -349,7 +349,7 @@ public static function successLangcodes(): array
* This both depends on a complete list trying to add above as understanding
* the plural rules of the current failing languages.
*
- * @return array with nplural together with langcodes
+ * @return array With nplural together with langcodes
*/
public static function failingLangcodes(): array
{
diff --git a/src/Symfony/Contracts/splitsh.json b/src/Symfony/Contracts/splitsh.json
new file mode 100644
index 0000000000000..1b1a16353f239
--- /dev/null
+++ b/src/Symfony/Contracts/splitsh.json
@@ -0,0 +1,13 @@
+{
+ "subtrees": {
+ "cache-contracts": "Cache",
+ "deprecation-contracts": "Deprecation",
+ "event-dispatcher-contracts": "EventDispatcher",
+ "http-client-contracts": "HttpClient",
+ "service-contracts": "Service",
+ "translation-contracts": "Translation"
+ },
+ "defaults": {
+ "git_constraint": "<1.8.2"
+ }
+}