diff --git a/.travis.yml b/.travis.yml index 24155a5c0c36..cbf4eee7dd1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,9 @@ matrix: - php: nightly services: [memcached] fast_finish: true + allow_failures: + - php: nightly + services: [memcached] cache: directories: diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index be58f04b9550..20f38b3422f1 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,68 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.9 (2020-05-31) + + * bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj) + * bug #36894 [Validator] never directly validate Existence (Required/Optional) constraints (xabbuh) + * bug #37007 [Console] Fix QuestionHelper::disableStty() (chalasr) + * bug #36865 [Form] validate subforms in all validation groups (xabbuh) + * bug #36907 Fixes sprintf(): Too few arguments in form transformer (pedrocasado) + * bug #36868 [Validator] Use Mime component to determine mime type for file validator (pierredup) + * bug #37000 Add meaningful message when using ProcessHelper and Process is not installed (l-vo) + * bug #36995 [TwigBridge] fix fallback html-to-txt body converter (nicolas-grekas) + * bug #36993 [ErrorHandler] fix setting $trace to null in FatalError (nicolas-grekas) + * bug #36987 Handle fetch mode deprecation of DBAL 2.11. (derrabus) + * bug #36974 [Security] Fixed handling of CSRF logout error (wouterj) + * bug #36947 [Mime] Allow email message to have "To", "Cc", or "Bcc" header to be valid (Ernest Hymel) + * bug #36914 Parse and render anonymous classes correctly on php 8 (derrabus) + * bug #36921 [OptionsResolver][Serializer] Remove calls to deprecated ReflectionParameter::getClass() (derrabus) + * bug #36920 [VarDumper] fix PHP 8 support (nicolas-grekas) + * bug #36917 [Cache] Accessing undefined constants raises an Error in php8 (derrabus) + * bug #36891 Address deprecation of ReflectionType::getClass() (derrabus) + * bug #36899 [VarDumper] ReflectionFunction::isDisabled() is deprecated (derrabus) + * bug #36905 [Validator] Catch expected ValueError (derrabus) + * bug #36915 [DomCrawler] Catch expected ValueError (derrabus) + * bug #36908 [Cache][HttpClient] Made method signatures compatible with their corresponding traits (derrabus) + * bug #36906 [DomCrawler] Catch expected ValueError (derrabus) + * bug #36904 [PropertyAccess] Parse php 8 TypeErrors correctly (derrabus) + * bug #36839 [BrowserKit] Raw body with custom Content-Type header (azhurb) + * bug #36896 [Config] Removed implicit cast of ReflectionProperty to string (derrabus) + * bug #35944 [Security/Core] Fix wrong roles comparison (thlbaut) + * bug #36882 [PhpUnitBridge] fix installing under PHP >= 8 (nicolas-grekas) + * bug #36833 [HttpKernel] Fix that the `Store` would not save responses with the X-Content-Digest header present (mpdude) + * bug #36867 [PhpUnitBridge] fix bad detection of unsilenced deprecations (nicolas-grekas) + * bug #36862 [Security] Unserialize $parentData, if needed, to avoid errors (rfaivre) + * bug #36855 [HttpKernel] Fix error logger when stderr is redirected to /dev/null (fabpot) + * bug #36838 [HttpKernel] Bring back the debug toolbar (derrabus) + * bug #36592 [BrowserKit] Allow Referer set by history to be overridden (Slamdunk) + * bug #36823 [HttpClient] fix PHP warning + accept status code >= 600 (nicolas-grekas) + * bug #36824 [Security/Core] fix compat of `NativePasswordEncoder` with pre-PHP74 values of `PASSWORD_*` consts (nicolas-grekas) + * bug #36811 [DependencyInjection] Fix register event listeners compiler pass (X-Coder264) + * bug #36789 Change priority of KernelEvents::RESPONSE subscriber (marcw) + * bug #36794 [Serializer] fix issue with PHP 8 (nicolas-grekas) + * bug #36786 [WebProfiler] Remove 'none' when appending CSP tokens (ndench) + * bug #36743 [Yaml] Fix escaped quotes in quoted multi-line string (ossinkine) + * bug #36777 [TwigBundle] FormExtension does not have a constructor anymore since sf 4.0 (Tobion) + * bug #36716 [Mime] handle passing custom mime types as string (mcneely) + * bug #36747 Queue name is a required parameter (theravel) + * bug #36751 [Mime] fix bad method call on `EmailAddressContains` (Kocal) + * bug #36696 [Console] don't check tty on stdin, it breaks with "data lost during stream conversion" (nicolas-grekas) + * bug #36569 [PhpUnitBridge] Mark parent class also covered in CoverageListener (lyrixx) + * bug #36690 [Yaml] prevent notice for invalid octal numbers on PHP 7.4 (xabbuh) + * bug #36590 [Console] Default hidden question to 1 attempt for non-tty session (ostrolucky) + * bug #36497 [Filesystem] Handle paths on different drives (crishoj) + * bug #36678 [WebProfiler] Do not add src-elem CSP directives if they do not exist (ndench) + * bug #36501 [DX] Show the ParseException message in all YAML file loaders (fancyweb) + * bug #36683 [Yaml] fix parse error when unindented collections contain a comment (wdiesveld) + * bug #36672 [Validator] Skip validation when email is an empty object (acrobat) + * bug #36673 [PhpUnitBridge] fix PHP 5.3 compat again (nicolas-grekas) + * bug #36505 [Translation] Fix for translation:update command updating ICU messages (artemoliynyk) + * bug #36627 [Validator] fix lazy property usage. (bendavies) + * bug #36601 [Serializer] do not transform empty \Traversable to Array (soyuka) + * bug #36606 [Cache] Fixed not supported Redis eviction policies (SerheyDolgushev) + * bug #36625 [PhpUnitBridge] fix compat with PHP 5.3 (nicolas-grekas) + * 4.4.8 (2020-04-28) * bug #36536 [Cache] Allow invalidateTags calls to be traced by data collector (l-vo) diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md index 1fb405767821..cbd366b3c739 100644 --- a/CHANGELOG-5.0.md +++ b/CHANGELOG-5.0.md @@ -7,6 +7,68 @@ in 5.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.0.0...v5.0.1 +* 5.0.9 (2020-05-31) + + * bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj) + * bug #36894 [Validator] never directly validate Existence (Required/Optional) constraints (xabbuh) + * bug #37007 [Console] Fix QuestionHelper::disableStty() (chalasr) + * bug #36865 [Form] validate subforms in all validation groups (xabbuh) + * bug #36907 Fixes sprintf(): Too few arguments in form transformer (pedrocasado) + * bug #36868 [Validator] Use Mime component to determine mime type for file validator (pierredup) + * bug #37000 Add meaningful message when using ProcessHelper and Process is not installed (l-vo) + * bug #36995 [TwigBridge] fix fallback html-to-txt body converter (nicolas-grekas) + * bug #36993 [ErrorHandler] fix setting $trace to null in FatalError (nicolas-grekas) + * bug #36987 Handle fetch mode deprecation of DBAL 2.11. (derrabus) + * bug #36974 [Security] Fixed handling of CSRF logout error (wouterj) + * bug #36947 [Mime] Allow email message to have "To", "Cc", or "Bcc" header to be valid (Ernest Hymel) + * bug #36914 Parse and render anonymous classes correctly on php 8 (derrabus) + * bug #36921 [OptionsResolver][Serializer] Remove calls to deprecated ReflectionParameter::getClass() (derrabus) + * bug #36920 [VarDumper] fix PHP 8 support (nicolas-grekas) + * bug #36917 [Cache] Accessing undefined constants raises an Error in php8 (derrabus) + * bug #36891 Address deprecation of ReflectionType::getClass() (derrabus) + * bug #36899 [VarDumper] ReflectionFunction::isDisabled() is deprecated (derrabus) + * bug #36905 [Validator] Catch expected ValueError (derrabus) + * bug #36915 [DomCrawler] Catch expected ValueError (derrabus) + * bug #36908 [Cache][HttpClient] Made method signatures compatible with their corresponding traits (derrabus) + * bug #36906 [DomCrawler] Catch expected ValueError (derrabus) + * bug #36904 [PropertyAccess] Parse php 8 TypeErrors correctly (derrabus) + * bug #36839 [BrowserKit] Raw body with custom Content-Type header (azhurb) + * bug #36896 [Config] Removed implicit cast of ReflectionProperty to string (derrabus) + * bug #35944 [Security/Core] Fix wrong roles comparison (thlbaut) + * bug #36882 [PhpUnitBridge] fix installing under PHP >= 8 (nicolas-grekas) + * bug #36833 [HttpKernel] Fix that the `Store` would not save responses with the X-Content-Digest header present (mpdude) + * bug #36867 [PhpUnitBridge] fix bad detection of unsilenced deprecations (nicolas-grekas) + * bug #36862 [Security] Unserialize $parentData, if needed, to avoid errors (rfaivre) + * bug #36855 [HttpKernel] Fix error logger when stderr is redirected to /dev/null (fabpot) + * bug #36838 [HttpKernel] Bring back the debug toolbar (derrabus) + * bug #36592 [BrowserKit] Allow Referer set by history to be overridden (Slamdunk) + * bug #36823 [HttpClient] fix PHP warning + accept status code >= 600 (nicolas-grekas) + * bug #36824 [Security/Core] fix compat of `NativePasswordEncoder` with pre-PHP74 values of `PASSWORD_*` consts (nicolas-grekas) + * bug #36811 [DependencyInjection] Fix register event listeners compiler pass (X-Coder264) + * bug #36789 Change priority of KernelEvents::RESPONSE subscriber (marcw) + * bug #36794 [Serializer] fix issue with PHP 8 (nicolas-grekas) + * bug #36786 [WebProfiler] Remove 'none' when appending CSP tokens (ndench) + * bug #36743 [Yaml] Fix escaped quotes in quoted multi-line string (ossinkine) + * bug #36777 [TwigBundle] FormExtension does not have a constructor anymore since sf 4.0 (Tobion) + * bug #36716 [Mime] handle passing custom mime types as string (mcneely) + * bug #36747 Queue name is a required parameter (theravel) + * bug #36751 [Mime] fix bad method call on `EmailAddressContains` (Kocal) + * bug #36696 [Console] don't check tty on stdin, it breaks with "data lost during stream conversion" (nicolas-grekas) + * bug #36569 [PhpUnitBridge] Mark parent class also covered in CoverageListener (lyrixx) + * bug #36690 [Yaml] prevent notice for invalid octal numbers on PHP 7.4 (xabbuh) + * bug #36590 [Console] Default hidden question to 1 attempt for non-tty session (ostrolucky) + * bug #36497 [Filesystem] Handle paths on different drives (crishoj) + * bug #36678 [WebProfiler] Do not add src-elem CSP directives if they do not exist (ndench) + * bug #36501 [DX] Show the ParseException message in all YAML file loaders (fancyweb) + * bug #36683 [Yaml] fix parse error when unindented collections contain a comment (wdiesveld) + * bug #36672 [Validator] Skip validation when email is an empty object (acrobat) + * bug #36673 [PhpUnitBridge] fix PHP 5.3 compat again (nicolas-grekas) + * bug #36505 [Translation] Fix for translation:update command updating ICU messages (artemoliynyk) + * bug #36627 [Validator] fix lazy property usage. (bendavies) + * bug #36601 [Serializer] do not transform empty \Traversable to Array (soyuka) + * bug #36606 [Cache] Fixed not supported Redis eviction policies (SerheyDolgushev) + * bug #36625 [PhpUnitBridge] fix compat with PHP 5.3 (nicolas-grekas) + * 5.0.8 (2020-04-28) * bug #36536 [Cache] Allow invalidateTags calls to be traced by data collector (l-vo) diff --git a/CHANGELOG-5.1.md b/CHANGELOG-5.1.md index 7d1409a1ec72..4f34853bc8b9 100644 --- a/CHANGELOG-5.1.md +++ b/CHANGELOG-5.1.md @@ -7,6 +7,49 @@ in 5.1 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.1.0...v5.1.1 +* 5.1.1 (2020-06-12) + + * bug #37227 [DependencyInjection][CheckTypeDeclarationsPass] Handle unresolved parameters pointing to environment variables (fancyweb) + * bug #37103 [Form] switch the context when validating nested forms (xabbuh) + * bug #37182 [HttpKernel] Fix regression where Store does not return response body correctly (mpdude) + * bug #37193 [DependencyInjection][CheckTypeDeclarationsPass] Always resolve parameters (fancyweb) + * bug #37044 [DependencyInjection] Apply ExpressionLanguageProviderPass to router.default (wizhippo) + * bug #37054 [String] Fix ellipsis of truncate when not using cut option (DuboisS) + * bug #37190 [HttpClient] disable AMP's inactivity timeout, we deal with it on our own already (nicolas-grekas) + * bug #37191 [HttpClient] fix offset computation for data chunks (nicolas-grekas) + * bug #37176 [Routing] Keeping routes priorities after add a name prefix to the collection (yceruto) + * bug #37140 [Lock] Fixed reading locks from replica set secondary nodes (kralos) + * bug #37177 [Ldap] fix refreshUser() ignoring extra_fields (arkste) + * bug #37181 [Mailer] Remove an internal annot (fabpot) + * bug #36913 [FrameworkBundle] fix type annotation on ControllerTrait::addFlash() (ThomasLandauer) + * bug #37153 [PhpUnitBridge] Fix ExpectDeprecationTrait::expectDeprecation() conflict (fancyweb) + * bug #37162 [Mailer] added the reply-to addresses to the API SES transport request. (ribeiropaulor) + * bug #37144 [DI] Add check around class_alias for generated proxy classes (enumag) + * bug #37167 [Mime] use fromString when creating a new Address (fabpot) + * bug #37169 [Cache] fix forward compatibility with Doctrine DBAL 3 (xabbuh) + * bug #37159 [Mailer] Fixed generator bug when creating multiple transports using Transport::fromDsn (atailouloute) + * bug #37154 [FrameworkBundle] Remove reference to APP_SECRET in MicroKernelTrait (nicolas-grekas) + * bug #37126 [SecurityBundle] Fix the session listener registration under the new authentication manager (johnvandeweghe) + * bug #37130 [Console] allow cursor to be used even when STDIN is not defined (xabbuh) + * bug #37053 [PropertyAccess] Fix getter call order BC (1ed) + * bug #37117 [Messenger/DoctrineBridge] set column length for mysql 5.6 compatibility (Nemo64) + * bug #37127 [Messenger/AmazonSqsBridge] Fixed left-over debug statement (sstok) + * bug #37048 [HttpClient] fix monitoring timeouts when other streams are active (nicolas-grekas) + * bug #37085 [Form] properly cascade validation to child forms (xabbuh) + * bug #37095 [PhpUnitBridge] Fix undefined index when output of "composer show" cannot be parsed (nicolas-grekas) + * bug #37092 [PhpUnitBridge] fix undefined var on version 3.4 (nicolas-grekas) + * bug #37022 [DependencyInjection] Improve missing package/version deprecation (acrobat) + * bug #37038 Fix invalid char in SQS Headers (jderusse) + * bug #37047 [SecurityBundle] Only register CSRF protection listener if CSRF is available (wouterj) + * bug #37065 [HttpClient] Throw JsonException instead of TransportException on empty response in Response::toArray() (jeroennoten) + * bug #37058 [FrameworkBundle] Extension Serializer issue (Korbeil) + * bug #37077 [WebProfilerBundle] Move ajax clear event listener initialization on loadToolbar (Bruno BOUTAREL) + * bug #37056 [DoctrineBridge] register event listeners depending on the installed packages (xabbuh) + * bug #37020 [ExpressionLanguage] reset the internal state when the parser is finished (xabbuh) + * bug #37049 [Serializer] take into account the context when preserving empty array objects (xabbuh) + * bug #37031 [Security] Fixed PUBLIC_ACCESS in authenticated sessions (wouterj) + * bug #37028 [FrameworkBundle] Fix enabled_locales behavior (tgalopin) + * 5.1.0 (2020-05-31) * bug #37009 [Validator] use "allowedVariables" to configure the ExpressionLanguageSyntax constraint (xabbuh) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 449973d7578e..baf025cfe048 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,15 +23,15 @@ Symfony is the result of the work of many people who made the code better - Johannes S (johannes) - Kris Wallsmith (kriswallsmith) - Yonel Ceruto (yonelceruto) - - Hugo Hamon (hhamon) - Wouter de Jong (wouterj) + - Hugo Hamon (hhamon) - Thomas Calvet (fancyweb) + - Alexander M. Turek (derrabus) - Abdellatif Ait boudad (aitboudad) - Samuel ROZE (sroze) - Romain Neutron (romain) - Pascal Borreli (pborreli) - Joseph Bielawski (stloyd) - - Alexander M. Turek (derrabus) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) - Jules Pietri (heah) @@ -60,16 +60,16 @@ Symfony is the result of the work of many people who made the code better - Alexander Mols (asm89) - Konstantin Myakshin (koc) - Grégoire Paris (greg0ire) - - Bulat Shakirzyanov (avalanche123) - Valentin Udaltsov (vudaltsov) + - Bulat Shakirzyanov (avalanche123) - Kevin Bond (kbond) - Saša Stamenković (umpirsky) - Peter Rehm (rpet) - - Henrik Bjørnskov (henrikbjorn) - Gabriel Ostrolucký (gadelat) + - Henrik Bjørnskov (henrikbjorn) + - Gábor Egyed (1ed) - Miha Vrhovnik - David Maicher (dmaicher) - - Gábor Egyed (1ed) - Diego Saint Esteben (dii3g0) - Jan Schädlich (jschaedl) - Titouan Galopin (tgalopin) @@ -132,6 +132,7 @@ Symfony is the result of the work of many people who made the code better - Daniel Wehner (dawehner) - Tugdual Saunier (tucksaun) - excelwebzone + - Massimiliano Arione (garak) - Gordon Franke (gimler) - Joel Wurtz (brouznouf) - Fabien Pennequin (fabienpennequin) @@ -139,7 +140,6 @@ Symfony is the result of the work of many people who made the code better - Przemysław Bogusz (przemyslaw-bogusz) - Eric GELOEN (gelo) - Lars Strojny (lstrojny) - - Massimiliano Arione (garak) - Jannik Zschiesche (apfelbox) - Robert Schönthal (digitalkaoz) - Gregor Harlan (gharlan) @@ -161,6 +161,7 @@ Symfony is the result of the work of many people who made the code better - Yanick Witschi (toflar) - Arnaud Kleinpeter (nanocom) - Guilherme Blanco (guilhermeblanco) + - Laurent VOULLEMIER (lvo) - SpacePossum - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) @@ -181,6 +182,7 @@ Symfony is the result of the work of many people who made the code better - jeremyFreeAgent (jeremyfreeagent) - Rouven Weßling (realityking) - Jérôme Parmentier (lctrs) + - Ben Davies (bendavies) - Andreas Schempp (aschempp) - Clemens Tolboom - Helmer Aaviksoo @@ -195,7 +197,7 @@ Symfony is the result of the work of many people who made the code better - Tyson Andre - GDIBass - Samuel NELA (snela) - - Ben Davies (bendavies) + - Saif (╯°□°)╯ (azjezz) - James Halsall (jaitsu) - Matthieu Napoli (mnapoli) - Florent Mata (fmata) @@ -204,6 +206,7 @@ Symfony is the result of the work of many people who made the code better - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) - Marek Štípek (maryo) + - Filippo Tessarotto (slamdunk) - Daniel Espendiller - Possum - Dorian Villet (gnutix) @@ -211,11 +214,11 @@ Symfony is the result of the work of many people who made the code better - Sergey Linnik (linniksa) - Richard Miller (mr_r_miller) - Albert Casademont (acasademont) + - Wouter J - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) - DQNEO - Andre Rømcke (andrerom) - - Saif (╯°□°)╯ (azjezz) - mcfedr (mcfedr) - Gary PEGEOT (gary-p) - Ruben Gonzalez (rubenrua) @@ -232,7 +235,6 @@ Symfony is the result of the work of many people who made the code better - Stadly - Stepan Anchugov (kix) - bronze1man - - Filippo Tessarotto (slamdunk) - sun (sun) - Larry Garfield (crell) - Nikolay Labinskiy (e-moe) @@ -245,7 +247,6 @@ Symfony is the result of the work of many people who made the code better - fivestar - Dominique Bongiraud - Jeremy Livingston (jeremylivingston) - - Laurent VOULLEMIER (lvo) - Michael Lee (zerustech) - Matthieu Auger (matthieuauger) - Mathias Arlaud (mtarld) @@ -255,6 +256,7 @@ Symfony is the result of the work of many people who made the code better - Dustin Whittle (dustinwhittle) - jeff - John Kary (johnkary) + - Tien Vo (tienvx) - Justin Hileman (bobthecow) - Blanchon Vincent (blanchonvincent) - Michele Orselli (orso) @@ -269,7 +271,6 @@ Symfony is the result of the work of many people who made the code better - Tristan Darricau (nicofuma) - Victor Bocharsky (bocharsky_bw) - Tomas Norkūnas (norkunas) - - Wouter J - Smaine Milianni (ismail1432) - Marcel Beerta (mazen) - Christopher Hertel (chertel) @@ -295,7 +296,6 @@ Symfony is the result of the work of many people who made the code better - Marcos Sánchez - Elnur Abdurrakhimov (elnur) - Manuel Reinhard (sprain) - - Tien Vo (tienvx) - Danny Berger (dpb587) - Antonio J. García Lagar (ajgarlag) - Adam Prager (padam87) @@ -309,6 +309,7 @@ Symfony is the result of the work of many people who made the code better - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA - Patrick McDougle (patrick-mcdougle) + - Marc Weistroff (futurecat) - Alif Rachmawadi - Anton Chernikov (anton_ch1989) - Kristen Gilden (kgilden) @@ -318,6 +319,7 @@ Symfony is the result of the work of many people who made the code better - Jakub Kucharovic (jkucharovic) - Loick Piera (pyrech) - Uwe Jäger (uwej711) + - Martin Hujer (martinhujer) - Eugene Leonovich (rybakit) - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) @@ -354,9 +356,9 @@ Symfony is the result of the work of many people who made the code better - Wouter Van Hecke - Peter Kruithof (pkruithof) - Michael Holm (hollo) + - Antonio Pauletich (x-coder264) - Arjen van der Meijden - Mathieu Lechat - - Marc Weistroff (futurecat) - Damien Alexandre (damienalexandre) - Simon Mönch (sm) - Christian Schmidt @@ -388,6 +390,7 @@ Symfony is the result of the work of many people who made the code better - Aurelijus Valeiša (aurelijus) - Jan Decavele (jandc) - Gustavo Piltcher + - Jesse Rushlow (geeshoe) - Stepan Tanasiychuk (stfalcon) - Tiago Ribeiro (fixe) - Hidde Boomsma (hboomsma) @@ -416,7 +419,9 @@ Symfony is the result of the work of many people who made the code better - Nicolas LEFEVRE (nicoweb) - alquerci - Oleg Andreyev + - Langlet Vincent (deviling) - Mateusz Sip (mateusz_sip) + - Alessandro Lai (jean85) - Francesco Levorato - Vitaliy Zakharov (zakharovvi) - Tobias Sjösten (tobiassjosten) @@ -427,14 +432,15 @@ Symfony is the result of the work of many people who made the code better - Tomasz Kowalczyk (thunderer) - Artur Eshenbrener - Timo Bakx (timobakx) - - Antonio Pauletich (x-coder264) - Thomas Perez (scullwm) - Felix Labrecque - Yaroslav Kiliba - Terje Bråten - Robbert Klarenbeek (robbertkl) + - soyuka - Eric Masoero (eric-masoero) - Denis Brumann (dbrumann) + - Gocha Ossinkine (ossinkine) - JhonnyL - Haralan Dobrev (hkdobrev) - hossein zolfi (ocean) @@ -449,10 +455,10 @@ Symfony is the result of the work of many people who made the code better - Philipp Kräutli (pkraeutli) - Grzegorz (Greg) Zdanowski (kiler129) - Iker Ibarguren (ikerib) + - Dimitri Gritsajuk (ottaviano) - Kirill chEbba Chebunin (chebba) - Rokas Mikalkėnas (rokasm) - Greg Thornton (xdissent) - - Martin Hujer (martinhujer) - Alex Bowers - Philipp Cordes - Costin Bereveanu (schniper) @@ -473,6 +479,7 @@ Symfony is the result of the work of many people who made the code better - Endre Fejes - Tobias Naumann (tna) - Daniel Beyer + - Timothée Barray (tyx) - Shein Alexey - Romain Gautier (mykiwi) - Joe Lencioni @@ -489,7 +496,6 @@ Symfony is the result of the work of many people who made the code better - Xavier HAUSHERR - Albert Jessurum (ajessu) - Laszlo Korte - - Jesse Rushlow (geeshoe) - Miha Vrhovnik - Alessandro Desantis - hubert lecorche (hlecorche) @@ -501,6 +507,7 @@ Symfony is the result of the work of many people who made the code better - Christophe L. (christophelau) - Sander Toonen (xatoo) - Anthon Pang (robocoder) + - Marko Kaznovac (kaznovac) - Sébastien Santoro (dereckson) - Brian King - Michel Salib (michelsalib) @@ -524,7 +531,6 @@ Symfony is the result of the work of many people who made the code better - Mihai Stancu - Ivan Nikolaev (destillat) - Gildas Quéméner (gquemener) - - Alessandro Lai (jean85) - Desjardins Jérôme (jewome62) - Arturs Vonda - Josip Kruslin @@ -561,7 +567,6 @@ Symfony is the result of the work of many people who made the code better - Marek Pietrzak - Luc Vieillescazes (iamluc) - franek (franek) - - soyuka - Raulnet - Christian Wahler - Giso Stallenberg (gisostallenberg) @@ -571,7 +576,6 @@ Symfony is the result of the work of many people who made the code better - HypeMC - Soufian EZ-ZANTAR (soezz) - Zander Baldwin - - Gocha Ossinkine (ossinkine) - Adam Harvey - Anton Bakai - Martin Auswöger @@ -603,9 +607,9 @@ Symfony is the result of the work of many people who made the code better - Philipp Rieber (bicpi) - Manuel de Ruiter (manuel) - Nathanael Noblet (gnat) - - Dimitri Gritsajuk (ottaviano) - nikos.sotiropoulos - Eduardo Oliveira (entering) + - Oleksii Zhurbytskyi - Ilya Antipenko (aivus) - Ricardo Oliveira (ricardolotr) - Roy Van Ginneken (rvanginneken) @@ -645,9 +649,9 @@ Symfony is the result of the work of many people who made the code better - Gábor Fási - DUPUCH (bdupuch) - Nate (frickenate) - - Timothée Barray (tyx) - jhonnyL - Jacek Jędrzejewski (jacek.jedrzejewski) + - Stefan Kruppa - sasezaki - Bozhidar Hristov (warxcell) - Dawid Pakuła (zulusx) @@ -684,7 +688,6 @@ Symfony is the result of the work of many people who made the code better - Pavel Campr (pcampr) - Andrii Dembitskyi - Johnny Robeson (johnny) - - Marko Kaznovac (kaznovac) - Guilliam Xavier - Disquedur - Michiel Boeckaert (milio) @@ -711,6 +714,7 @@ Symfony is the result of the work of many people who made the code better - vitaliytv - Philippe Segatori - Dalibor Karlović (dkarlovi) + - Andrey Sevastianov - Sebastian Blum - Alexis Lefebvre - aubx @@ -728,6 +732,7 @@ Symfony is the result of the work of many people who made the code better - Sinan Eldem - BoShurik - Alexandre Dupuy (satchette) + - Michel Hunziker - Malte Blättermann - Simeon Kolev (simeon_kolev9) - Joost van Driel (j92) @@ -741,7 +746,6 @@ Symfony is the result of the work of many people who made the code better - Stefan Gehrig (sgehrig) - Hany el-Kerdany - Wang Jingyu - - Langlet Vincent (deviling) - Åsmund Garfors - Gunnstein Lye (glye) - Maxime Douailin @@ -847,8 +851,10 @@ Symfony is the result of the work of many people who made the code better - Michael Lutz - Koen Reiniers (koenre) - jochenvdv + - Michel Roca (mroca) - Reedy - Arturas Smorgun (asarturas) + - Michał (bambucha15) - Alexander Volochnev (exelenz) - Michael Piecko - Toni Peric (tperic) @@ -879,12 +885,12 @@ Symfony is the result of the work of many people who made the code better - Axel Guckelsberger (guite) - Jose Gonzalez - Jonathan (jls-esokia) - - Oleksii Zhurbytskyi - Dariusz Ruminski - Joshua Nye - Claudio Zizza - Dave Marshall (davedevelopment) - Jakub Kulhan (jakubkulhan) + - Nathan Dench (ndenc2) - Shaharia Azam - avorobiev - stoccc @@ -948,9 +954,11 @@ Symfony is the result of the work of many people who made the code better - GDIBass - Antoine Lamirault - Adrien Lucas (adrienlucas) + - Jeroen Thora (bolle) - Zhuravlev Alexander (scif) - Stefano Degenkamp (steef) - James Michael DuPont + - Carlos Buenosvinos (carlosbuenosvinos) - Tom Klingenberg - Christopher Hall (mythmakr) - Patrick Dawkins (pjcdawkins) @@ -982,6 +990,7 @@ Symfony is the result of the work of many people who made the code better - Julie Hourcade (juliehde) - Dmitry Parnas (parnas) - Paul LE CORRE + - Loïc Beurlet - Daniel Gorgan - Tony Malzhacker - Mathieu MARCHOIS @@ -1006,7 +1015,6 @@ Symfony is the result of the work of many people who made the code better - Jelle Kapitein - Benoît Bourgeois - mantulo - - Stefan Kruppa - corphi - JoppeDC - grizlik @@ -1049,6 +1057,7 @@ Symfony is the result of the work of many people who made the code better - Arno Geurts - Adán Lobato (adanlobato) - Ian Jenkins (jenkoian) + - Hugo Alliaume (kocal) - Marcos Gómez Vilches (markitosgv) - Matthew Davis (mdavis1982) - Markus S. (staabm) @@ -1062,6 +1071,7 @@ Symfony is the result of the work of many people who made the code better - Daniel Cestari - Matt Janssen - David Lima + - Dmitriy Derepko - Stéphane Delprat - Brian Freytag (brianfreytag) - Samuele Lilli (doncallisto) @@ -1148,6 +1158,7 @@ Symfony is the result of the work of many people who made the code better - Anton Babenko (antonbabenko) - Irmantas Šiupšinskas (irmantas) - Danilo Silva + - Giuseppe Campanelli - Arnaud PETITPAS (apetitpa) - Ken Stanley - Zachary Tong (polyfractal) @@ -1164,6 +1175,7 @@ Symfony is the result of the work of many people who made the code better - Tero Alén (tero) - Stanislav Kocanda - DerManoMann + - MatTheCat - Guillaume Royer - Artem (digi) - boite @@ -1184,7 +1196,6 @@ Symfony is the result of the work of many people who made the code better - Danijel Obradović - Pablo Borowicz - Mathieu Santostefano - - Michel Hunziker - Arjan Keeman - Máximo Cuadros (mcuadros) - Lukas Mencl @@ -1277,7 +1288,6 @@ Symfony is the result of the work of many people who made the code better - Jakub Sacha - Olaf Klischat - orlovv - - Andrey Sevastianov - Claude Dioudonnat - Jonathan Hedstrom - Peter Smeets (darkspartan) @@ -1297,12 +1307,10 @@ Symfony is the result of the work of many people who made the code better - James Hudson - Stephen Clouse - e-ivanov - - Michał (bambucha15) - Benjamin Dos Santos - Einenlum - Jérémy Jarrié (gagnar) - Jochen Bayer (jocl) - - Michel Roca (mroca) - Patrick Carlo-Hickman - Bruno MATEU - Jeremy Bush @@ -1416,6 +1424,7 @@ Symfony is the result of the work of many people who made the code better - Florian Hermann (fhermann) - Mo Di (modi) - Pablo Schläpfer + - Christian Rishøj - Patrick Berenschot - SuRiKmAn - Gert de Pagter @@ -1423,6 +1432,7 @@ Symfony is the result of the work of many people who made the code better - David Négrier (moufmouf) - Quique Porta (quiqueporta) - mohammadreza honarkhah + - Artem Oliynyk (artemoliynyk) - Andrea Quintino (dirk39) - Tomasz Szymczyk (karion) - Alex Vasilchenko @@ -1453,6 +1463,7 @@ Symfony is the result of the work of many people who made the code better - Andrei Igna - Adam Prickett - azine + - Anton Kroshilin - Dawid Sajdak - Ludek Stepan - Aaron Stephens (astephens) @@ -1605,6 +1616,7 @@ Symfony is the result of the work of many people who made the code better - Robert Queck - Peter Bouwdewijn - mlively + - Wouter Diesveld - Amine Matmati - caalholm - Nouhail AL FIDI (alfidi) @@ -1612,6 +1624,7 @@ Symfony is the result of the work of many people who made the code better - Felipy Tavares Amorim (felipyamorim) - Guillaume Loulier (guikingone) - Klaus Silveira (klaussilveira) + - Pedro Casado (pdr33n) - Pierre Grimaud (pgrimaud) - Thomas Chmielowiec (chmielot) - Jānis Lukss @@ -1724,6 +1737,7 @@ Symfony is the result of the work of many people who made the code better - Stanislav Gamayunov (happyproff) - Iwan van Staveren (istaveren) - Alexander McCullagh (mccullagh) + - Paul L McNeely (mcneely) - Povilas S. (povilas) - Laurent Negre (raulnet) - Evrard Boulou @@ -1809,6 +1823,7 @@ Symfony is the result of the work of many people who made the code better - Mathieu Dewet (mdewet) - Nicolas Tallefourtané (nicolab) - Botond Dani (picur) + - Rémi Faivre (rfv) - Romaric Drigon (romaricdrigon) - Thierry Marianne (thierrymarianne) - Nick Stemerdink @@ -1816,6 +1831,7 @@ Symfony is the result of the work of many people who made the code better - jjanvier - Julius Beckmann - loru88 + - Thibaut Salanon - Romain Dorgueil - Christopher Parotat - Dennis Haarbrink @@ -1860,6 +1876,7 @@ Symfony is the result of the work of many people who made the code better - Chris - Farid Jalilov - Florent Olivaud + - Eric Hertwig - JakeFr - Simon Sargeant - efeen @@ -1920,6 +1937,7 @@ Symfony is the result of the work of many people who made the code better - Michael van Tricht - ReScO - Tim Strehle + - Sébastien COURJEAN - Sam Ward - Michael Voříšek - Walther Lalk @@ -1972,6 +1990,7 @@ Symfony is the result of the work of many people who made the code better - Martijn Boers (plebian) - Pedro Magalhães (pmmaga) - Rares Vlaseanu (raresvla) + - Sergii Dolgushev (serhey) - tante kinast (tante) - Stephen Lewis (tehanomalousone) - Ahmed Hannachi (tiecoders) @@ -1981,6 +2000,7 @@ Symfony is the result of the work of many people who made the code better - Darryl Hein (xmmedia) - Sadicov Vladimir (xtech) - Kevin EMO (zarcox) + - sdkawata - Andrzej - Alexander Zogheb - Rémi Blaise @@ -2016,6 +2036,7 @@ Symfony is the result of the work of many people who made the code better - Ashura - Götz Gottwald - Veres Lajos + - Ernest Hymel - Nick Chiu - grifx - Robert Campbell @@ -2045,6 +2066,7 @@ Symfony is the result of the work of many people who made the code better - Rowan Manning - Per Modin - David Windell + - Christian Scheb - Gabriel Birke - skafandri - Derek Bonner @@ -2088,7 +2110,6 @@ Symfony is the result of the work of many people who made the code better - baron (bastien) - Rosio (ben-rosio) - Simon Paarlberg (blamh) - - Jeroen Thora (bolle) - Brieuc THOMAS (brieucthomas) - Masao Maeda (brtriver) - Damien Harper (damien.harper) @@ -2148,6 +2169,7 @@ Symfony is the result of the work of many people who made the code better - Michael Orlitzky - Nicolas A. Bérard-Nault - Quentin Favrie + - Matthias Derer - Saem Ghani - Stefan Oderbolz - Curtis @@ -2173,7 +2195,6 @@ Symfony is the result of the work of many people who made the code better - Daniele Cesarini (ijanki) - Ismail Asci (ismailasci) - Jeffrey Moelands (jeffreymoelands) - - Hugo Alliaume (kocal) - Simon CONSTANS (kosssi) - Dennis Langen (nijusan) - Paulius Jarmalavičius (pjarmalavicius) @@ -2218,6 +2239,7 @@ Symfony is the result of the work of many people who made the code better - Antonio Angelino - Jens Schulze - Matt Fields + - Olatunbosun Egberinde - Niklas Keller - Andras Debreczeni - Vladimir Sazhin @@ -2352,11 +2374,11 @@ Symfony is the result of the work of many people who made the code better - Karolis Daužickas - Nicolas - Sergio Santoro - - Dmitriy Derepko - tirnanog06 - phc - Дмитрий Пацура - Signor Pedro + - Matthias Larisch - ilyes kooli - Ilia Lazarev - Michaël VEROUX @@ -2388,8 +2410,10 @@ Symfony is the result of the work of many people who made the code better - Christian Gripp (core23) - Christoph Schaefer (cvschaefer) - Damon Jones (damon__jones) + - Cătălin Dan (dancatalin) - Łukasz Giza (destroyer) - Daniel Londero (dlondero) + - Dmitrii Tarasov (dtarasov) - Sebastian Landwehr (dword123) - Adel ELHAIBA (eadel) - Damián Nohales (eagleoneraptor) diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php index 3311e6316825..5b3798eb3918 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaSubscriber.php @@ -89,9 +89,16 @@ public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void public function getSubscribedEvents(): array { - return [ - ToolEvents::postGenerateSchema, - Events::onSchemaCreateTable, - ]; + $subscribedEvents = []; + + if (class_exists(ToolEvents::class)) { + $subscribedEvents[] = ToolEvents::postGenerateSchema; + } + + if (class_exists(Events::class)) { + $subscribedEvents[] = Events::onSchemaCreateTable; + } + + return $subscribedEvents; } } diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/PdoCacheAdapterDoctrineSchemaSubscriber.php b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoCacheAdapterDoctrineSchemaSubscriber.php index 41330e7971b5..527b055b2807 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/PdoCacheAdapterDoctrineSchemaSubscriber.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/PdoCacheAdapterDoctrineSchemaSubscriber.php @@ -43,6 +43,10 @@ public function postGenerateSchema(GenerateSchemaEventArgs $event): void public function getSubscribedEvents(): array { + if (!class_exists(ToolEvents::class)) { + return []; + } + return [ ToolEvents::postGenerateSchema, ]; diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index eda901d0541e..9ac53b87f661 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Security\RememberMe; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; @@ -63,7 +64,7 @@ public function loadTokenBySeries(string $series) $paramValues = ['series' => $series]; $paramTypes = ['series' => \PDO::PARAM_STR]; $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); - $row = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC); + $row = $stmt instanceof Result ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC); if ($row) { return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); diff --git a/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php b/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php index 0db391d12aba..fd7f2c80b84f 100644 --- a/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php +++ b/src/Symfony/Bridge/PhpUnit/ExpectDeprecationTrait.php @@ -11,21 +11,20 @@ namespace Symfony\Bridge\PhpUnit; -use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait; +use Symfony\Bridge\PhpUnit\Legacy\ExpectDeprecationTraitBeforeV8_4; +use Symfony\Bridge\PhpUnit\Legacy\ExpectDeprecationTraitForV8_4; -trait ExpectDeprecationTrait -{ +if (version_compare(\PHPUnit\Runner\Version::id(), '8.4.0', '<')) { + trait ExpectDeprecationTrait + { + use ExpectDeprecationTraitBeforeV8_4; + } +} else { /** - * @param string $message - * - * @return void + * @method void expectDeprecation(string $message) */ - protected function expectDeprecation($message) + trait ExpectDeprecationTrait { - if (!SymfonyTestsListenerTrait::$previousErrorHandler) { - SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']); - } - - SymfonyTestsListenerTrait::$expectedDeprecations[] = $message; + use ExpectDeprecationTraitForV8_4; } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php new file mode 100644 index 000000000000..47c8226e6e55 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitBeforeV8_4.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +/** + * @internal, use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait instead. + */ +trait ExpectDeprecationTraitBeforeV8_4 +{ + /** + * @param string $message + * + * @return void + */ + protected function expectDeprecation($message) + { + if (!SymfonyTestsListenerTrait::$previousErrorHandler) { + SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']); + } + + SymfonyTestsListenerTrait::$expectedDeprecations[] = $message; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitForV8_4.php b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitForV8_4.php new file mode 100644 index 000000000000..ceaefdb0b3a2 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/ExpectDeprecationTraitForV8_4.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +/** + * @internal use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait instead. + */ +trait ExpectDeprecationTraitForV8_4 +{ + /** + * @param string $message + */ + public function expectDeprecation(): void + { + if (1 > func_num_args() || !\is_string($message = func_get_arg(0))) { + throw new \InvalidArgumentException(sprintf('The "%s()" method requires the string $message argument.', __FUNCTION__)); + } + + if (!SymfonyTestsListenerTrait::$previousErrorHandler) { + SymfonyTestsListenerTrait::$previousErrorHandler = set_error_handler([SymfonyTestsListenerTrait::class, 'handleError']); + } + + SymfonyTestsListenerTrait::$expectedDeprecations[] = $message; + } + + /** + * @internal use expectDeprecation() instead. + */ + public function expectDeprecationMessage(string $message): void + { + throw new \BadMethodCallException(sprintf('The "%s()" method is not supported by Symfony\'s PHPUnit Bridge ExpectDeprecationTrait, pass the message to expectDeprecation() instead.', __FUNCTION__)); + } + + /** + * @internal use expectDeprecation() instead. + */ + public function expectDeprecationMessageMatches(string $regularExpression): void + { + throw new \BadMethodCallException(sprintf('The "%s()" method is not supported by Symfony\'s PHPUnit Bridge ExpectDeprecationTrait.', __FUNCTION__)); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index 70c2fcdbddcf..ca41113b373c 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -182,6 +182,11 @@ } } + $info += [ + 'versions' => [], + 'requires' => ['php' => '*'], + ]; + if (1 === \count($info['versions'])) { $passthruOrFail("$COMPOSER create-project --ignore-platform-reqs --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi -s dev phpunit/phpunit $PHPUNIT_VERSION_DIR \"$PHPUNIT_VERSION.*\""); } else { diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 343c2bbaf552..23ae45754456 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -197,7 +197,7 @@ protected function file($file, string $fileName = null, string $disposition = Re * * @throws \LogicException */ - protected function addFlash(string $type, string $message): void + protected function addFlash(string $type, $message): void { if (!$this->container->has('session')) { throw new \LogicException('You can not use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".'); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index 47195b48300f..3e2f2768edc1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -28,8 +28,8 @@ class AddExpressionLanguageProvidersPass implements CompilerPassInterface public function process(ContainerBuilder $container) { // routing - if ($container->has('router')) { - $definition = $container->findDefinition('router'); + if ($container->has('router.default')) { + $definition = $container->findDefinition('router.default'); foreach ($container->findTaggedServiceIds('routing.expression_language_provider', true) as $id => $attributes) { $definition->addMethodCall('addExpressionLanguageProvider', [new Reference($id)]); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 666797f2039c..4517521c7643 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1094,7 +1094,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $defaultOptions['cache_dir'] = $config['cache_dir']; $translator->setArgument(4, $defaultOptions); - $translator->setArgument(6, $config['enabled_locales']); + $translator->setArgument(5, $config['enabled_locales']); $container->setParameter('translator.logging', $config['logging']); $container->setParameter('translator.default_path', $config['default_path']); @@ -1379,7 +1379,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) { + if (!class_exists(PropertyAccessor::class)) { return; } @@ -1463,7 +1463,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) { + if (!class_exists(PropertyAccessor::class)) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } @@ -1472,7 +1472,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.encoder.yaml'); } - if (!class_exists(UnwrappingDenormalizer::class)) { + if (!class_exists(UnwrappingDenormalizer::class) || !class_exists(PropertyAccessor::class)) { $container->removeDefinition('serializer.denormalizer.unwrapping'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index 540c97672c14..abb220b4aa2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -83,7 +83,6 @@ public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(function (ContainerBuilder $container) use ($loader) { $container->loadFromExtension('framework', [ - 'secret' => '%env(APP_SECRET)%', 'router' => [ 'resource' => 'kernel::loadRoutes', 'type' => 'service', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 99ffbabb82cd..71eb751a5d41 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -567,7 +567,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml index ef5ed701adea..bc7dd2028a39 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -6,7 +6,6 @@ %kernel.cache_dir%/serialization.php - @@ -103,7 +102,6 @@ - null diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index 070908f3db35..7c10470d5196 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -5,7 +5,6 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - %kernel.cache_dir%/validation.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php index 95ed74f8b9fc..1207a4820d10 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -22,16 +22,16 @@ class AddExpressionLanguageProvidersPassTest extends TestCase public function testProcessForRouter() { $container = new ContainerBuilder(); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); $definition = new Definition('\stdClass'); $definition->addTag('routing.expression_language_provider'); $container->setDefinition('some_routing_provider', $definition->setPublic(true)); - $container->register('router', '\stdClass')->setPublic(true); + $container->register('router.default', '\stdClass')->setPublic(true); $container->compile(); - $router = $container->getDefinition('router'); + $router = $container->getDefinition('router.default'); $calls = $router->getMethodCalls(); $this->assertCount(1, $calls); $this->assertEquals('addExpressionLanguageProvider', $calls[0][0]); @@ -41,14 +41,14 @@ public function testProcessForRouter() public function testProcessForRouterAlias() { $container = new ContainerBuilder(); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); $definition = new Definition('\stdClass'); $definition->addTag('routing.expression_language_provider'); $container->setDefinition('some_routing_provider', $definition->setPublic(true)); $container->register('my_router', '\stdClass')->setPublic(true); - $container->setAlias('router', 'my_router'); + $container->setAlias('router.default', 'my_router'); $container->compile(); $router = $container->getDefinition('my_router'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index f17597589683..c4729771bbca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1049,7 +1049,6 @@ public function testSerializerEnabled() $this->assertCount(2, $argument); $this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass()); - $this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1)); $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1)); $this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3)); $this->assertArrayHasKey('circular_reference_handler', $container->getDefinition('serializer.normalizer.object')->getArgument(6)); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php new file mode 100644 index 000000000000..5af1c49568ba --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfFeaturesPass.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener; +use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; + +/** + * @author Christian Flothmann + * @author Wouter de Jong + * + * @internal + */ +class RegisterCsrfFeaturesPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->has('security.csrf.token_storage')) { + return; + } + + $this->registerCsrfProtectionListener($container); + $this->registerLogoutHandler($container); + } + + private function registerCsrfProtectionListener(ContainerBuilder $container) + { + if (!$container->has('security.authenticator.manager')) { + return; + } + + $container->register('security.listener.csrf_protection', CsrfProtectionListener::class) + ->addArgument(new Reference('security.csrf.token_storage')) + ->addTag('kernel.event_subscriber') + ->setPublic(false); + } + + protected function registerLogoutHandler(ContainerBuilder $container) + { + if (!$container->has('security.logout_listener')) { + return; + } + + $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); + $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); + + if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) { + return; + } + + $container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class) + ->addArgument(new Reference('security.csrf.token_storage')) + ->addTag('kernel.event_subscriber') + ->setPublic(false); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php index 2d6960e1fe45..8cabb9d73d36 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php @@ -11,32 +11,21 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener; + +trigger_deprecation('symfony/security-bundle', '5.1', 'The "%s" class is deprecated.', RegisterCsrfTokenClearingLogoutHandlerPass::class); /** - * @author Christian Flothmann + * @deprecated since symfony/security-bundle 5.1 */ -class RegisterCsrfTokenClearingLogoutHandlerPass implements CompilerPassInterface +class RegisterCsrfTokenClearingLogoutHandlerPass extends RegisterCsrfFeaturesPass { public function process(ContainerBuilder $container) { - if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) { - return; - } - - $csrfTokenStorage = $container->findDefinition('security.csrf.token_storage'); - $csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass()); - - if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) { + if (!$container->has('security.csrf.token_storage')) { return; } - $container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class) - ->addArgument(new Reference('security.csrf.token_storage')) - ->addTag('kernel.event_subscriber') - ->setPublic(false); + $this->registerLogoutHandler($container); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index afa04d7cad7d..35cc7b1b4d91 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -168,14 +168,6 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition('security.authentication.guard_handler') ->replaceArgument(2, $this->statelessFirewallKeys); - if ($this->authenticatorManagerEnabled) { - foreach ($this->statelessFirewallKeys as $statelessFirewallId) { - $container - ->setDefinition('security.listener.session.'.$statelessFirewallId, new ChildDefinition('security.listener.session')) - ->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$statelessFirewallId]); - } - } - if ($config['encoders']) { $this->createEncoders($config['encoders'], $container); } @@ -373,6 +365,12 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $contextKey = $firewall['context'] ?? $id; $listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey)); $sessionStrategyId = 'security.authentication.session_strategy'; + + if ($this->authenticatorManagerEnabled) { + $container + ->setDefinition('security.listener.session.'.$id, new ChildDefinition('security.listener.session')) + ->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]); + } } else { $this->statelessFirewallKeys[] = $id; $sessionStrategyId = 'security.authentication.session_strategy_noop'; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml index 26e47613c102..65bc75594169 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml @@ -63,12 +63,6 @@ class="Symfony\Component\Security\Http\EventListener\SessionStrategyListener" abstract="true"> - stateless firewall keys - - - - - addCompilerPass(new AddExpressionLanguageProvidersPass()); $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); - $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); + $container->addCompilerPass(new RegisterCsrfFeaturesPass()); $container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200); $container->addCompilerPass(new RegisterLdapLocatorPass()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index b7063f42a05b..b2595dc4346c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -559,6 +559,48 @@ public function provideConfigureCustomAuthenticatorData() ]; } + public function testCompilesWithoutSessionListenerWithStatelessFirewallWithAuthenticationManager() + { + $container = $this->getRawContainer(); + + $firewallId = 'stateless_firewall'; + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + 'firewalls' => [ + $firewallId => [ + 'pattern' => '/.*', + 'stateless' => true, + 'http_basic' => null, + ], + ], + ]); + + $container->compile(); + + $this->assertFalse($container->has('security.listener.session.'.$firewallId)); + } + + public function testCompilesWithSessionListenerWithStatefulllFirewallWithAuthenticationManager() + { + $container = $this->getRawContainer(); + + $firewallId = 'statefull_firewall'; + $container->loadFromExtension('security', [ + 'enable_authenticator_manager' => true, + 'firewalls' => [ + $firewallId => [ + 'pattern' => '/.*', + 'stateless' => false, + 'http_basic' => null, + ], + ], + ]); + + $container->compile(); + + $this->assertTrue($container->has('security.listener.session.'.$firewallId)); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index c65e278021a0..58f3867c6f2f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -131,13 +131,6 @@ removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading'); removeClass(ajaxToolbarPanel, 'sf-toolbar-status-red'); } - - addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() { - requestStack = []; - renderAjaxRequests(); - successStreak = 4; - document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = ''; - }); }; var startAjaxRequest = function(index) { @@ -506,6 +499,12 @@ setPreference('toolbar/displayState', 'block'); }); renderAjaxRequests(); + addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() { + requestStack = []; + renderAjaxRequests(); + successStreak = 4; + document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = ''; + }); addEventListener(document.querySelector('.sf-toolbar-block-ajax'), 'mouseenter', function (event) { var elem = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info'); elem.scrollTop = elem.scrollHeight; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php index df3054bdea06..1d3bcf33cdcf 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Functional/WebProfilerBundleKernel.php @@ -2,6 +2,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Tests\Functional; +use Psr\Log\NullLogger; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\TwigBundle\TwigBundle; @@ -62,6 +63,11 @@ public function getLogDir() return sys_get_temp_dir().'/log-'.spl_object_hash($this); } + protected function build(ContainerBuilder $container) + { + $container->register('logger', NullLogger::class); + } + public function homepageController() { return new Response('Homepage Controller.'); diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index a042ad8a2c1d..97ff79ec2873 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Cache\Adapter; +use Doctrine\DBAL\Abstraction\Result; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception\TableNotFoundException; @@ -217,15 +219,16 @@ protected function doFetch(array $ids) foreach ($ids as $id) { $stmt->bindValue(++$i, $id); } - $stmt->execute(); + $result = $stmt->execute(); - if (method_exists($stmt, 'iterateNumeric')) { - $stmt = $stmt->iterateNumeric(); + if ($result instanceof Result) { + $result = $result->iterateNumeric(); } else { $stmt->setFetchMode(\PDO::FETCH_NUM); + $result = $stmt; } - foreach ($stmt as $row) { + foreach ($result as $row) { if (null === $row[1]) { $expired[] = $row[0]; } else { @@ -255,9 +258,9 @@ protected function doHave(string $id) $stmt->bindValue(':id', $id); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); - $stmt->execute(); + $result = $stmt->execute(); - return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); + return (bool) ($result instanceof DriverResult ? $result->fetchOne() : $stmt->fetchColumn()); } /** @@ -387,19 +390,19 @@ protected function doSave(array $values, int $lifetime) foreach ($values as $id => $data) { try { - $stmt->execute(); + $result = $stmt->execute(); } catch (TableNotFoundException $e) { if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { $this->createTable(); } - $stmt->execute(); + $result = $stmt->execute(); } catch (\PDOException $e) { if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { $this->createTable(); } - $stmt->execute(); + $result = $stmt->execute(); } - if (null === $driver && !$stmt->rowCount()) { + if (null === $driver && !($result instanceof DriverResult ? $result : $stmt)->rowCount()) { try { $insertStmt->execute(); } catch (DBALException $e) { diff --git a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php index 05a88e086c3c..53d0ccc5db44 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php +++ b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Cache\Tests\Traits; +use Doctrine\DBAL\Driver\Result; + trait PdoPruneableTrait { protected function isPruned($cache, string $name): bool @@ -27,8 +29,8 @@ protected function isPruned($cache, string $name): bool /** @var \Doctrine\DBAL\Statement|\PDOStatement $select */ $select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id'); $select->bindValue(':id', sprintf('%%%s', $name)); - $select->execute(); + $result = $select->execute(); - return 1 !== (int) (method_exists($select, 'fetchOne') ? $select->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN)); + return 1 !== (int) ($result instanceof Result ? $result->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN)); } } diff --git a/src/Symfony/Component/Console/Cursor.php b/src/Symfony/Component/Console/Cursor.php index 9f8be9649c52..2da899b5564a 100644 --- a/src/Symfony/Component/Console/Cursor.php +++ b/src/Symfony/Component/Console/Cursor.php @@ -21,10 +21,10 @@ final class Cursor private $output; private $input; - public function __construct(OutputInterface $output, $input = STDIN) + public function __construct(OutputInterface $output, $input = null) { $this->output = $output; - $this->input = $input; + $this->input = $input ?? (\defined('STDIN') ? STDIN : fopen('php://input', 'r+')); } public function moveUp(int $lines = 1): self diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php index a5d06f1f8da6..8229beb106ef 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php @@ -198,9 +198,10 @@ private function checkType(Definition $checkedDefinition, $value, \ReflectionPar } } elseif (\is_string($value)) { if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) { - // Only array parameters are not inlined when dumped. - $value = []; - } elseif ($envPlaceholderUniquePrefix && false !== strpos($value, 'env_')) { + $value = $this->container->getParameter(substr($value, 1, -1)); + } + + if ($envPlaceholderUniquePrefix && \is_string($value) && false !== strpos($value, 'env_')) { // If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it. // We don't need to change the value because it is already a string. if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index c343de30357e..28e475c4600e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -567,7 +567,7 @@ private function generateProxyClasses(): array $proxyClass = explode(' ', $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1]; if ($this->asFiles || $this->namespace) { - $proxyCode .= "\n\\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n"; + $proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n"; } $proxyClasses[$proxyClass.'.php'] = $proxyCode; @@ -1086,7 +1086,7 @@ private function addNewInstance(Definition $definition, string $return = '', str // If the class is a string we can optimize away if (0 === strpos($class, "'") && false === strpos($class, '$')) { if ("''" === $class) { - throw new RuntimeException(sprintf('Cannot dump definition: %s service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline')); + throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline')); } return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 3cb0cba8040e..7bce62a5f7dc 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -205,11 +205,11 @@ private function parseDefinition(\DOMElement $service, string $file, Definition $version = $deprecated[0]->getAttribute('version') ?: ''; if (!$deprecated[0]->hasAttribute('package')) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); } if (!$deprecated[0]->hasAttribute('version')) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); } $alias->setDeprecated($package, $version, $message); @@ -265,11 +265,11 @@ private function parseDefinition(\DOMElement $service, string $file, Definition $version = $deprecated[0]->getAttribute('version') ?: ''; if ('' === $package) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); } if ('' === $version) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); } $definition->setDeprecated($package, $version, $message); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index a5eedb9db300..417e568ed0af 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -409,11 +409,11 @@ private function parseDefinition(string $id, $service, string $file, array $defa $deprecation = \is_array($value) ? $value : ['message' => $value]; if (!isset($deprecation['package'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); } if (!isset($deprecation['version'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); } $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message']); @@ -478,11 +478,11 @@ private function parseDefinition(string $id, $service, string $file, array $defa $deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']]; if (!isset($deprecation['package'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); } if (!isset($deprecation['version'])) { - trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option is deprecated.'); + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); } $definition->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message']); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php index 56589ae0d6f8..0aef6e89e3b5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php @@ -612,20 +612,6 @@ public function testProcessThrowsOnIterableTypeWhenScalarPassed() $this->assertInstanceOf(\stdClass::class, $container->get('bar')->foo); } - public function testProcessResolveArrayParameters() - { - $container = new ContainerBuilder(); - $container->setParameter('ccc', ['foobar']); - - $container - ->register('foobar', BarMethodCall::class) - ->addMethodCall('setArray', ['%ccc%']); - - (new CheckTypeDeclarationsPass(true))->process($container); - - $this->addToAssertionCount(1); - } - public function testProcessResolveExpressions() { $container = new ContainerBuilder(); @@ -791,4 +777,30 @@ public function testExpressionLanguageWithSyntheticService() $this->addToAssertionCount(1); } + + public function testProcessResolveParameters() + { + putenv('ARRAY={"foo":"bar"}'); + + $container = new ContainerBuilder(new EnvPlaceholderParameterBag([ + 'env_array_param' => '%env(json:ARRAY)%', + ])); + $container->setParameter('array_param', ['foobar']); + $container->setParameter('string_param', 'ccc'); + + $definition = $container->register('foobar', BarMethodCall::class); + $definition + ->addMethodCall('setArray', ['%array_param%']) + ->addMethodCall('setString', ['%string_param%']); + + (new ResolveParameterPlaceHoldersPass())->process($container); + + $definition->addMethodCall('setArray', ['%env_array_param%']); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + + putenv('ARRAY='); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php index 69f1a693a4c5..65437a63ec74 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/BarMethodCall.php @@ -44,4 +44,8 @@ public function setCallable(callable $callable): void public function setClosure(\Closure $closure): void { } + + public function setString(string $string) + { + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt index f1dd4db45184..92c7299ac305 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt @@ -160,7 +160,9 @@ class FooClass_%s extends \Bar\FooClass implements \ProxyManager\Proxy\VirtualPr %A } -\class_alias(__NAMESPACE__.'\\FooClass_%s', 'FooClass_%s', false); +if (!\class_exists('FooClass_%s', false)) { + \class_alias(__NAMESPACE__.'\\FooClass_%s', 'FooClass_%s', false); +} [ProjectServiceContainer.preload.php] => expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "package" of the node "deprecated" is deprecated.'); + $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.'); $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); @@ -442,7 +442,7 @@ public function testDeprecatedAliases() */ public function testDeprecatedAliaseWithoutPackageAndVersion() { - $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "package" of the node "deprecated" is deprecated.'); + $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.'); $container = new ContainerBuilder(); $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index bd46044e4f56..ab9021ba265d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -238,8 +238,8 @@ public function testDeprecatedAliases() */ public function testDeprecatedAliasesWithoutPackageAndVersion() { - $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "package" of the "deprecated" option is deprecated.'); - $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "version" of the "deprecated" option is deprecated.'); + $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.'); + $this->expectDeprecation('Since symfony/dependency-injection 5.1: Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.'); $container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php index 34658b97c0c0..fb9c321c4b9f 100644 --- a/src/Symfony/Component/ExpressionLanguage/Parser.php +++ b/src/Symfony/Component/ExpressionLanguage/Parser.php @@ -124,6 +124,9 @@ private function doParse(TokenStream $stream, ?array $names = []): Node\Node throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression()); } + $this->stream = null; + $this->names = null; + return $node; } diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index bd7f553cdedc..b21c442a8354 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -72,7 +72,6 @@ public function validate($form, Constraint $formConstraint) if ($groups instanceof GroupSequence) { // Validate the data, the form AND nested fields in sequence $violationsCount = $this->context->getViolations()->count(); - $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s'; foreach ($groups->groups as $group) { if ($validateDataGraph) { @@ -91,7 +90,8 @@ public function validate($form, Constraint $formConstraint) // in different steps without breaking early enough $this->resolvedGroups[$field] = (array) $group; $fieldFormConstraint = new Form(); - $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint); + $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); + $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint); } } @@ -100,8 +100,6 @@ public function validate($form, Constraint $formConstraint) } } } else { - $fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s'; - if ($validateDataGraph) { $validator->atPath('data')->validate($data, null, $groups); } @@ -138,7 +136,8 @@ public function validate($form, Constraint $formConstraint) if ($field->isSubmitted()) { $this->resolvedGroups[$field] = $groups; $fieldFormConstraint = new Form(); - $validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint); + $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); + $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint); } } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php new file mode 100644 index 000000000000..4a91f16af066 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php @@ -0,0 +1,362 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Validator\Constraints; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormFactoryBuilder; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Expression; +use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Validation; + +class FormValidatorFunctionalTest extends TestCase +{ + private $validator; + private $formFactory; + + protected function setUp(): void + { + $this->validator = Validation::createValidatorBuilder() + ->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader())) + ->getValidator(); + $this->formFactory = (new FormFactoryBuilder()) + ->addExtension(new ValidatorExtension($this->validator)) + ->getFormFactory(); + } + + public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted() + { + $form = $this->formFactory->create(FooType::class); + $form->submit(['baz' => 'foobar'], false); + + $this->assertTrue($form->isSubmitted()); + $this->assertFalse($form->isValid()); + $this->assertFalse($form->get('bar')->isSubmitted()); + $this->assertCount(1, $form->get('bar')->getErrors()); + } + + public function testFieldConstraintsDoNotInvalidateFormIfFieldIsNotSubmitted() + { + $form = $this->formFactory->create(FooType::class); + $form->submit(['bar' => 'foobar'], false); + + $this->assertTrue($form->isSubmitted()); + $this->assertTrue($form->isValid()); + } + + public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted() + { + $form = $this->formFactory->create(FooType::class); + $form->submit(['bar' => 'foobar', 'baz' => ''], false); + + $this->assertTrue($form->isSubmitted()); + $this->assertFalse($form->isValid()); + $this->assertTrue($form->get('bar')->isSubmitted()); + $this->assertTrue($form->get('bar')->isValid()); + $this->assertTrue($form->get('baz')->isSubmitted()); + $this->assertFalse($form->get('baz')->isValid()); + } + + public function testNonCompositeConstraintValidatedOnce() + { + $form = $this->formFactory->create(TextType::class, null, [ + 'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])], + 'validation_groups' => ['foo', 'bar'], + ]); + $form->submit(''); + + $violations = $this->validator->validate($form); + + $this->assertCount(1, $violations); + $this->assertSame('This value should not be blank.', $violations[0]->getMessage()); + $this->assertSame('data', $violations[0]->getPropertyPath()); + } + + public function testCompositeConstraintValidatedInEachGroup() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'constraints' => [ + new Collection([ + 'field1' => new NotBlank([ + 'groups' => ['field1'], + ]), + 'field2' => new NotBlank([ + 'groups' => ['field2'], + ]), + ]), + ], + 'validation_groups' => ['field1', 'field2'], + ]); + $form->add('field1'); + $form->add('field2'); + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(2, $violations); + $this->assertSame('This value should not be blank.', $violations[0]->getMessage()); + $this->assertSame('data[field1]', $violations[0]->getPropertyPath()); + $this->assertSame('This value should not be blank.', $violations[1]->getMessage()); + $this->assertSame('data[field2]', $violations[1]->getPropertyPath()); + } + + public function testCompositeConstraintValidatedInSequence() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'constraints' => [ + new Collection([ + 'field1' => new NotBlank([ + 'groups' => ['field1'], + ]), + 'field2' => new NotBlank([ + 'groups' => ['field2'], + ]), + ]), + ], + 'validation_groups' => new GroupSequence(['field1', 'field2']), + ]); + $form->add('field1'); + $form->add('field2'); + + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(1, $violations); + $this->assertSame('This value should not be blank.', $violations[0]->getMessage()); + $this->assertSame('data[field1]', $violations[0]->getPropertyPath()); + } + + public function testFieldsValidateInSequence() + { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1', 'group2']), + ]) + ->add('foo', TextType::class, [ + 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)], + ]) + ->add('bar', TextType::class, [ + 'constraints' => [new NotBlank(['groups' => ['group2']])], + ]) + ; + + $form->submit(['foo' => 'invalid', 'bar' => null]); + + $errors = $form->getErrors(true); + + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + } + + public function testFieldsValidateInSequenceWithNestedGroupsArray() + { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']), + ]) + ->add('foo', TextType::class, [ + 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)], + ]) + ->add('bar', TextType::class, [ + 'constraints' => [new Length(['min' => 10, 'groups' => ['group2']] + $allowEmptyString)], + ]) + ->add('baz', TextType::class, [ + 'constraints' => [new NotBlank(['groups' => ['group3']])], + ]) + ; + + $form->submit(['foo' => 'invalid', 'bar' => 'invalid', 'baz' => null]); + + $errors = $form->getErrors(true); + + $this->assertCount(2, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + $this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint()); + } + + public function testConstraintsInDifferentGroupsOnSingleField() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1', 'group2']), + ]) + ->add('foo', TextType::class, [ + 'constraints' => [ + new NotBlank([ + 'groups' => ['group1'], + ]), + new Length([ + 'groups' => ['group2'], + 'max' => 3, + ]), + ], + ]); + $form->submit([ + 'foo' => 'test@example.com', + ]); + + $errors = $form->getErrors(true); + + $this->assertFalse($form->isValid()); + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + } + + public function testCascadeValidationToChildFormsUsingPropertyPaths() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => ['group1', 'group2'], + ]) + ->add('field1', null, [ + 'constraints' => [new NotBlank(['groups' => 'group1'])], + 'property_path' => '[foo]', + ]) + ->add('field2', null, [ + 'constraints' => [new NotBlank(['groups' => 'group2'])], + 'property_path' => '[bar]', + ]) + ; + + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(2, $violations); + $this->assertSame('This value should not be blank.', $violations[0]->getMessage()); + $this->assertSame('children[field1].data', $violations[0]->getPropertyPath()); + $this->assertSame('This value should not be blank.', $violations[1]->getMessage()); + $this->assertSame('children[field2].data', $violations[1]->getPropertyPath()); + } + + public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSequence() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1', 'group2']), + ]) + ->add('field1', null, [ + 'constraints' => [new NotBlank(['groups' => 'group1'])], + 'property_path' => '[foo]', + ]) + ->add('field2', null, [ + 'constraints' => [new NotBlank(['groups' => 'group2'])], + 'property_path' => '[bar]', + ]) + ; + + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(1, $violations); + $this->assertSame('This value should not be blank.', $violations[0]->getMessage()); + $this->assertSame('children[field1].data', $violations[0]->getPropertyPath()); + } + + public function testContextIsPopulatedWithFormBeingValidated() + { + $form = $this->formFactory->create(FormType::class) + ->add('field1', null, [ + 'constraints' => [new Expression([ + 'expression' => '!this.getParent().get("field2").getData()', + ])], + ]) + ->add('field2') + ; + + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(0, $violations); + } + + public function testContextIsPopulatedWithFormBeingValidatedUsingGroupSequence() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1']), + ]) + ->add('field1', null, [ + 'constraints' => [new Expression([ + 'expression' => '!this.getParent().get("field2").getData()', + 'groups' => ['group1'], + ])], + ]) + ->add('field2') + ; + + $form->submit([ + 'field1' => '', + 'field2' => '', + ]); + + $violations = $this->validator->validate($form); + + $this->assertCount(0, $violations); + } +} + +class Foo +{ + public $bar; + public $baz; + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('bar', new NotBlank()); + } +} + +class FooType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('bar') + ->add('baz', null, [ + 'constraints' => [new NotBlank()], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', Foo::class); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index b6e0cfa16499..2c0a728610e1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -18,13 +18,13 @@ use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Extension\Validator\Constraints\Form; use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormFactoryBuilder; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\SubmitButtonBuilder; use Symfony\Component\Translation\IdentityTranslator; -use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; @@ -52,7 +52,9 @@ class FormValidatorTest extends ConstraintValidatorTestCase protected function setUp(): void { $this->dispatcher = new EventDispatcher(); - $this->factory = (new FormFactoryBuilder())->getFormFactory(); + $this->factory = (new FormFactoryBuilder()) + ->addExtension(new ValidatorExtension(Validation::createValidator())) + ->getFormFactory(); parent::setUp(); @@ -746,96 +748,6 @@ public function testCauseForNotAllowedExtraFieldsIsTheFormConstraint() $this->assertSame($constraint, $context->getViolations()->get(0)->getConstraint()); } - public function testNonCompositeConstraintValidatedOnce() - { - $form = $this - ->getBuilder('form', null, [ - 'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])], - 'validation_groups' => ['foo', 'bar'], - ]) - ->setCompound(false) - ->getForm(); - $form->submit(''); - - $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator()); - $this->validator->initialize($context); - $this->validator->validate($form, new Form()); - - $this->assertCount(1, $context->getViolations()); - $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage()); - $this->assertSame('data', $context->getViolations()[0]->getPropertyPath()); - } - - public function testCompositeConstraintValidatedInEachGroup() - { - $form = $this->getBuilder('form', null, [ - 'constraints' => [ - new Collection([ - 'field1' => new NotBlank([ - 'groups' => ['field1'], - ]), - 'field2' => new NotBlank([ - 'groups' => ['field2'], - ]), - ]), - ], - 'validation_groups' => ['field1', 'field2'], - ]) - ->setData([]) - ->setCompound(true) - ->setDataMapper(new PropertyPathMapper()) - ->getForm(); - $form->add($this->getForm('field1')); - $form->add($this->getForm('field2')); - $form->submit([ - 'field1' => '', - 'field2' => '', - ]); - - $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator()); - $this->validator->initialize($context); - $this->validator->validate($form, new Form()); - - $this->assertCount(2, $context->getViolations()); - $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage()); - $this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath()); - $this->assertSame('This value should not be blank.', $context->getViolations()[1]->getMessage()); - $this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath()); - } - - public function testCompositeConstraintValidatedInSequence() - { - $form = $this->getCompoundForm([], [ - 'constraints' => [ - new Collection([ - 'field1' => new NotBlank([ - 'groups' => ['field1'], - ]), - 'field2' => new NotBlank([ - 'groups' => ['field2'], - ]), - ]), - ], - 'validation_groups' => new GroupSequence(['field1', 'field2']), - ]) - ->add($this->getForm('field1')) - ->add($this->getForm('field2')) - ; - - $form->submit([ - 'field1' => '', - 'field2' => '', - ]); - - $context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator()); - $this->validator->initialize($context); - $this->validator->validate($form, new Form()); - - $this->assertCount(1, $context->getViolations()); - $this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage()); - $this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath()); - } - protected function createValidator() { return new FormValidator(); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php index 4324541cb7ff..4a12acf4126b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php @@ -12,23 +12,12 @@ namespace Symfony\Component\Form\Tests\Extension\Validator; use PHPUnit\Framework\TestCase; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\FormType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Validator\Constraints\Form as FormConstraint; use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser; use Symfony\Component\Form\Form; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormFactoryBuilder; -use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Validator\Constraints\GroupSequence; -use Symfony\Component\Validator\Constraints\Length; -use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Mapping\CascadingStrategy; use Symfony\Component\Validator\Mapping\ClassMetadata; -use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; -use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\Mapping\TraversalStrategy; use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory; use Symfony\Component\Validator\Validation; @@ -57,155 +46,4 @@ public function test2Dot5ValidationApi() $this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy); $this->assertCount(0, $metadata->getPropertyMetadata('children')); } - - public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted() - { - $form = $this->createForm(FooType::class); - $form->submit(['baz' => 'foobar'], false); - - $this->assertTrue($form->isSubmitted()); - $this->assertFalse($form->isValid()); - $this->assertFalse($form->get('bar')->isSubmitted()); - $this->assertCount(1, $form->get('bar')->getErrors()); - } - - public function testFieldConstraintsDoNotInvalidateFormIfFieldIsNotSubmitted() - { - $form = $this->createForm(FooType::class); - $form->submit(['bar' => 'foobar'], false); - - $this->assertTrue($form->isSubmitted()); - $this->assertTrue($form->isValid()); - } - - public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted() - { - $form = $this->createForm(FooType::class); - $form->submit(['bar' => 'foobar', 'baz' => ''], false); - - $this->assertTrue($form->isSubmitted()); - $this->assertFalse($form->isValid()); - $this->assertTrue($form->get('bar')->isSubmitted()); - $this->assertTrue($form->get('bar')->isValid()); - $this->assertTrue($form->get('baz')->isSubmitted()); - $this->assertFalse($form->get('baz')->isValid()); - } - - public function testFieldsValidateInSequence() - { - $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; - - $form = $this->createForm(FormType::class, null, [ - 'validation_groups' => new GroupSequence(['group1', 'group2']), - ]) - ->add('foo', TextType::class, [ - 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)], - ]) - ->add('bar', TextType::class, [ - 'constraints' => [new NotBlank(['groups' => ['group2']])], - ]) - ; - - $form->submit(['foo' => 'invalid', 'bar' => null]); - - $errors = $form->getErrors(true); - - $this->assertCount(1, $errors); - $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); - } - - public function testFieldsValidateInSequenceWithNestedGroupsArray() - { - $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; - - $form = $this->createForm(FormType::class, null, [ - 'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']), - ]) - ->add('foo', TextType::class, [ - 'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)], - ]) - ->add('bar', TextType::class, [ - 'constraints' => [new Length(['min' => 10, 'groups' => ['group2']] + $allowEmptyString)], - ]) - ->add('baz', TextType::class, [ - 'constraints' => [new NotBlank(['groups' => ['group3']])], - ]) - ; - - $form->submit(['foo' => 'invalid', 'bar' => 'invalid', 'baz' => null]); - - $errors = $form->getErrors(true); - - $this->assertCount(2, $errors); - $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); - $this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint()); - } - - public function testConstraintsInDifferentGroupsOnSingleField() - { - $form = $this->createForm(FormType::class, null, [ - 'validation_groups' => new GroupSequence(['group1', 'group2']), - ]) - ->add('foo', TextType::class, [ - 'constraints' => [ - new NotBlank([ - 'groups' => ['group1'], - ]), - new Length([ - 'groups' => ['group2'], - 'max' => 3, - ]), - ], - ]); - $form->submit([ - 'foo' => 'test@example.com', - ]); - - $errors = $form->getErrors(true); - - $this->assertFalse($form->isValid()); - $this->assertCount(1, $errors); - $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); - } - - private function createForm($type, $data = null, array $options = []) - { - $validator = Validation::createValidatorBuilder() - ->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader())) - ->getValidator(); - $formFactoryBuilder = new FormFactoryBuilder(); - $formFactoryBuilder->addExtension(new ValidatorExtension($validator)); - $formFactory = $formFactoryBuilder->getFormFactory(); - - return $formFactory->create($type, $data, $options); - } -} - -class Foo -{ - public $bar; - public $baz; - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('bar', new NotBlank()); - } -} - -class FooType extends AbstractType -{ - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('bar') - ->add('baz', null, [ - 'constraints' => [new NotBlank()], - ]) - ; - } - - public function configureOptions(OptionsResolver $resolver) - { - $resolver->setDefault('data_class', Foo::class); - } } diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index cc398d2f7668..8976d79837b2 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -31,6 +31,7 @@ "doctrine/collections": "~1.0", "symfony/validator": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", "symfony/config": "^4.4|^5.0", "symfony/console": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", diff --git a/src/Symfony/Component/HttpClient/AmpHttpClient.php b/src/Symfony/Component/HttpClient/AmpHttpClient.php index 1520d22a2727..0877b8883bef 100644 --- a/src/Symfony/Component/HttpClient/AmpHttpClient.php +++ b/src/Symfony/Component/HttpClient/AmpHttpClient.php @@ -120,6 +120,9 @@ public function request(string $method, string $url, array $options = []): Respo $request->setTcpConnectTimeout(1000 * $options['timeout']); $request->setTlsHandshakeTimeout(1000 * $options['timeout']); $request->setTransferTimeout(1000 * $options['max_duration']); + if (method_exists($request, 'setInactivityTimeout')) { + $request->setInactivityTimeout(0); + } if ('' !== $request->getUri()->getUserInfo() && !$request->hasHeader('authorization')) { $auth = explode(':', $request->getUri()->getUserInfo(), 2); diff --git a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php index 6578929dc546..2a47dbcca0ec 100644 --- a/src/Symfony/Component/HttpClient/Internal/NativeClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/NativeClientState.php @@ -28,8 +28,6 @@ final class NativeClientState extends ClientState public $responseCount = 0; /** @var string[] */ public $dnsCache = []; - /** @var resource[] */ - public $handles = []; /** @var bool */ public $sleep = false; diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index a406585b9d9a..7745461c9475 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -173,21 +173,18 @@ private static function perform(ClientState $multi, array &$responses = null): v */ private static function select(ClientState $multi, float $timeout): int { - $start = microtime(true); - $remaining = $timeout; - - while (true) { - self::$delay = Loop::delay(1000 * $remaining, [Loop::class, 'stop']); - Loop::run(); - - if (null === self::$delay) { - return 1; + $timeout += microtime(true); + self::$delay = Loop::defer(static function () use ($timeout) { + if (0 < $timeout -= microtime(true)) { + self::$delay = Loop::delay(ceil(1000 * $timeout), [Loop::class, 'stop']); + } else { + Loop::stop(); } + }); - if (0 >= $remaining = $timeout - microtime(true) + $start) { - return 0; - } - } + Loop::run(); + + return null === self::$delay ? 1 : 0; } private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger) diff --git a/src/Symfony/Component/HttpClient/Response/NativeResponse.php b/src/Symfony/Component/HttpClient/Response/NativeResponse.php index 639957415f0d..2ba1b010e41f 100644 --- a/src/Symfony/Component/HttpClient/Response/NativeResponse.php +++ b/src/Symfony/Component/HttpClient/Response/NativeResponse.php @@ -220,11 +220,6 @@ private static function schedule(self $response, array &$runningResponses): void */ private static function perform(ClientState $multi, array &$responses = null): void { - // List of native handles for stream_select() - if (null !== $responses) { - $multi->handles = []; - } - foreach ($multi->openHandles as $i => [$h, $buffer, $onProgress]) { $hasActivity = false; $remaining = &$multi->openHandles[$i][3]; @@ -291,8 +286,6 @@ private static function perform(ClientState $multi, array &$responses = null): v $multi->handlesActivity[$i][] = $e; unset($multi->openHandles[$i]); $multi->sleep = false; - } elseif (null !== $responses) { - $multi->handles[] = $h; } } @@ -307,7 +300,7 @@ private static function perform(ClientState $multi, array &$responses = null): v } } - if (\count($multi->handles) >= $multi->maxHostConnections) { + if (\count($multi->openHandles) >= $multi->maxHostConnections) { return; } @@ -318,10 +311,6 @@ private static function perform(ClientState $multi, array &$responses = null): v $multi->sleep = false; self::perform($multi); - if (null !== $response->handle) { - $multi->handles[] = $response->handle; - } - break; } } @@ -335,7 +324,8 @@ private static function perform(ClientState $multi, array &$responses = null): v private static function select(ClientState $multi, float $timeout): int { $_ = []; + $handles = array_column($multi->openHandles, 0); - return (!$multi->sleep = !$multi->sleep) ? -1 : stream_select($multi->handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout))); + return (!$multi->sleep = !$multi->sleep) ? -1 : stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout))); } } diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index 44f2b559795c..37a134cea01e 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -137,7 +137,7 @@ public function getContent(bool $throw = true): string public function toArray(bool $throw = true): array { if ('' === $content = $this->getContent($throw)) { - throw new TransportException('Response body is empty.'); + throw new JsonException('Response body is empty.'); } if (null !== $this->jsonData) { @@ -316,7 +316,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene } $lastActivity = microtime(true); - $isTimeout = false; + $enlapsedTimeout = 0; while (true) { $hasActivity = false; @@ -338,7 +338,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene } elseif (!isset($multi->openHandles[$j])) { unset($responses[$j]); continue; - } elseif ($isTimeout) { + } elseif ($enlapsedTimeout >= $timeoutMax) { $multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))]; } else { continue; @@ -346,7 +346,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene while ($multi->handlesActivity[$j] ?? false) { $hasActivity = true; - $isTimeout = false; + $enlapsedTimeout = 0; if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) { if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) { @@ -359,8 +359,9 @@ public static function stream(iterable $responses, float $timeout = null): \Gene continue; } - $response->offset += \strlen($chunk); + $chunkLen = \strlen($chunk); $chunk = new DataChunk($response->offset, $chunk); + $response->offset += $chunkLen; } elseif (null === $chunk) { $e = $multi->handlesActivity[$j][0]; unset($responses[$j], $multi->handlesActivity[$j]); @@ -379,7 +380,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene } } elseif ($chunk instanceof ErrorChunk) { unset($responses[$j]); - $isTimeout = true; + $enlapsedTimeout = $timeoutMax; } elseif ($chunk instanceof FirstChunk) { if ($response->logger) { $info = $response->getInfo(); @@ -447,10 +448,11 @@ public static function stream(iterable $responses, float $timeout = null): \Gene continue; } - switch (self::select($multi, $timeoutMin)) { - case -1: usleep(min(500, 1E6 * $timeoutMin)); break; - case 0: $isTimeout = microtime(true) - $lastActivity > $timeoutMax; break; + if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $enlapsedTimeout))) { + usleep(min(500, 1E6 * $timeoutMin)); } + + $enlapsedTimeout = microtime(true) - $lastActivity; } } } diff --git a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php index d238034f451e..269464d400e3 100644 --- a/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php @@ -33,4 +33,13 @@ protected function getHttpClient(string $testCase): HttpClientInterface return new CurlHttpClient(['verify_peer' => false, 'verify_host' => false]); } + + public function testTimeoutIsNotAFatalError() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Too transient on Windows'); + } + + parent::testTimeoutIsNotAFatalError(); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index 907857fa4d11..f8a84ea4bcfe 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -83,9 +83,9 @@ public function testToStream404() $this->assertSame($response, stream_get_meta_data($stream)['wrapper_data']->getResponse()); $this->assertSame(404, $response->getStatusCode()); - $this->expectException(ClientException::class); $response = $client->request('GET', 'http://localhost:8057/404'); - $stream = $response->toStream(); + $this->expectException(ClientException::class); + $response->toStream(); } public function testNonBlockingStream() @@ -93,6 +93,7 @@ public function testNonBlockingStream() $client = $this->getHttpClient(__FUNCTION__); $response = $client->request('GET', 'http://localhost:8057/timeout-body'); $stream = $response->toStream(); + usleep(10000); $this->assertTrue(stream_set_blocking($stream, false)); $this->assertSame('<1>', fread($stream, 8192)); diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index 7c9a1aa3c6da..56b7232bc4d8 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -190,6 +190,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface $this->markTestSkipped("MockHttpClient doesn't unzip"); break; + case 'testTimeoutWithActiveConcurrentStream': + $this->markTestSkipped('Real transport required'); + break; + case 'testDestruct': $this->markTestSkipped("MockHttpClient doesn't timeout on destruct"); break; diff --git a/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php b/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php index 77b0dfa7c8c0..b1cb16c1d90e 100644 --- a/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Response/MockResponseTest.php @@ -35,6 +35,12 @@ public function testToArrayError($content, $responseHeaders, $message) public function toArrayErrors() { + yield [ + 'content' => '', + 'responseHeaders' => [], + 'message' => 'Response body is empty.', + ]; + yield [ 'content' => '{}', 'responseHeaders' => ['content-type' => 'plain/text'], diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index 96c310fe95dc..22a03f261f6a 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -150,8 +150,8 @@ public function lookup(Request $request) } $headers = $match[1]; - if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { - return $this->restoreResponse($headers, $body); + if (file_exists($path = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $path); } // TODO the metaStore referenced an entity that doesn't exist in @@ -175,15 +175,28 @@ public function write(Request $request, Response $response) $key = $this->getCacheKey($request); $storedEnv = $this->persistRequest($request); - $digest = $this->generateContentDigest($response); - $response->headers->set('X-Content-Digest', $digest); + if ($response->headers->has('X-Body-File')) { + // Assume the response came from disk, but at least perform some safeguard checks + if (!$response->headers->has('X-Content-Digest')) { + throw new \RuntimeException('A restored response must have the X-Content-Digest header.'); + } - if (!$this->save($digest, $response->getContent(), false)) { - throw new \RuntimeException('Unable to store the entity.'); - } + $digest = $response->headers->get('X-Content-Digest'); + if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) { + throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.'); + } + // Everything seems ok, omit writing content to disk + } else { + $digest = $this->generateContentDigest($response); + $response->headers->set('X-Content-Digest', $digest); - if (!$response->headers->has('Transfer-Encoding')) { - $response->headers->set('Content-Length', \strlen($response->getContent())); + if (!$this->save($digest, $response->getContent(), false)) { + throw new \RuntimeException('Unable to store the entity.'); + } + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); + } } // read existing cache entries, remove non-varying, and add this one to the list @@ -446,15 +459,15 @@ private function persistResponse(Response $response): array /** * Restores a Response from the HTTP headers and body. */ - private function restoreResponse(array $headers, string $body = null): Response + private function restoreResponse(array $headers, string $path = null): Response { $status = $headers['X-Status'][0]; unset($headers['X-Status']); - if (null !== $body) { - $headers['X-Body-File'] = [$body]; + if (null !== $path) { + $headers['X-Body-File'] = [$path]; } - return new Response($body, $status, $headers); + return new Response($path, $status, $headers); } } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 0df231dca933..ceb069ee2c4f 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,11 +73,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.0'; - const VERSION_ID = 50100; + const VERSION = '5.1.1'; + const VERSION_ID = 50101; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; - const RELEASE_VERSION = 0; + const RELEASE_VERSION = 1; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2021'; diff --git a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php index e1c0ff11926b..b8337dc737e4 100644 --- a/src/Symfony/Component/HttpKernel/Resources/welcome.html.php +++ b/src/Symfony/Component/HttpKernel/Resources/welcome.html.php @@ -65,7 +65,7 @@
- You're seeing this page because you haven't configured any homepage URL. + You're seeing this page because you haven't configured any homepage URL and debug mode is enabled.
diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php index b17cc0a44f9e..1f5f472802e7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php @@ -118,6 +118,39 @@ public function testWritesResponseEvenIfXContentDigestIsPresent() $this->assertNotNull($response); } + public function testWritingARestoredResponseDoesNotCorruptCache() + { + /* + * This covers the regression reported in https://github.com/symfony/symfony/issues/37174. + * + * A restored response does *not* load the body, but only keep the file path in a special X-Body-File + * header. For reasons (?), the file path was also used as the restored response body. + * It would be up to others (HttpCache...?) to honor this header and actually load the response content + * from there. + * + * When a restored response was stored again, the Store itself would ignore the header. In the first + * step, this would compute a new Content Digest based on the file path in the restored response body; + * this is covered by "Checkpoint 1" below. But, since the X-Body-File header was left untouched (Checkpoint 2), downstream + * code (HttpCache...) would not immediately notice. + * + * Only upon performing the lookup for a second time, we'd get a Response where the (wrong) Content Digest + * is also reflected in the X-Body-File header, this time also producing wrong content when the downstream + * evaluates it. + */ + $this->store->write($this->request, $this->response); + $digest = $this->response->headers->get('X-Content-Digest'); + $path = $this->getStorePath($digest); + + $response = $this->store->lookup($this->request); + $this->store->write($this->request, $response); + $this->assertEquals($digest, $response->headers->get('X-Content-Digest')); // Checkpoint 1 + $this->assertEquals($path, $response->headers->get('X-Body-File')); // Checkpoint 2 + + $response = $this->store->lookup($this->request); + $this->assertEquals($digest, $response->headers->get('X-Content-Digest')); + $this->assertEquals($path, $response->headers->get('X-Body-File')); + } + public function testFindsAStoredEntryWithLookup() { $this->storeSimpleEntry(); diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php index 28bd1d4c6892..c593a1376e5d 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php +++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php @@ -108,7 +108,7 @@ public function refreshUser(UserInterface $user) throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } - return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles()); + return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->getExtraFields()); } /** diff --git a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php index 8d0a7a351758..a2e888077cde 100644 --- a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php +++ b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php @@ -330,4 +330,14 @@ public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute() $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']); $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); } + + public function testRefreshUserShouldReturnUserWithSameProperties() + { + $ldap = $this->createMock(LdapInterface::class); + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']); + + $user = new LdapUser(new Entry('foo'), 'foo', 'bar', ['ROLE_DUMMY'], ['email' => 'foo@symfony.com']); + + $this->assertEquals($user, $provider->refreshUser($user)); + } } diff --git a/src/Symfony/Component/Lock/Store/MongoDbStore.php b/src/Symfony/Component/Lock/Store/MongoDbStore.php index 296c68be1072..34c740a6685c 100644 --- a/src/Symfony/Component/Lock/Store/MongoDbStore.php +++ b/src/Symfony/Component/Lock/Store/MongoDbStore.php @@ -14,8 +14,8 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Client; use MongoDB\Collection; -use MongoDB\Driver\Command; use MongoDB\Driver\Exception\WriteException; +use MongoDB\Driver\ReadPreference; use MongoDB\Exception\DriverRuntimeException; use MongoDB\Exception\InvalidArgumentException as MongoInvalidArgumentException; use MongoDB\Exception\UnsupportedException; @@ -54,8 +54,6 @@ class MongoDbStore implements BlockingStoreInterface private $options; private $initialTtl; - private $databaseVersion; - use ExpiringStoreTrait; /** @@ -87,8 +85,8 @@ class MongoDbStore implements BlockingStoreInterface * to 0.0 and optionally leverage * self::createTtlIndex(int $expireAfterSeconds = 0). * - * writeConcern, readConcern and readPreference are not specified by - * MongoDbStore meaning the collection's settings will take effect. + * writeConcern and readConcern are not specified by MongoDbStore meaning the connection's settings will take effect. + * readPreference is primary for all queries. * @see https://docs.mongodb.com/manual/applications/replication/ */ public function __construct($mongo, array $options = [], float $initialTtl = 300.0) @@ -262,6 +260,8 @@ public function exists(Key $key): bool 'expires_at' => [ '$gt' => $this->createMongoDateTime(microtime(true)), ], + ], [ + 'readPreference' => new ReadPreference(\defined(ReadPreference::PRIMARY) ? ReadPreference::PRIMARY : ReadPreference::RP_PRIMARY), ]); } @@ -315,25 +315,6 @@ private function isDuplicateKeyException(WriteException $e): bool return 11000 === $code; } - private function getDatabaseVersion(): string - { - if (null !== $this->databaseVersion) { - return $this->databaseVersion; - } - - $command = new Command([ - 'buildinfo' => 1, - ]); - $cursor = $this->getCollection()->getManager()->executeReadCommand( - $this->getCollection()->getDatabaseName(), - $command - ); - $buildInfo = $cursor->toArray()[0]; - $this->databaseVersion = $buildInfo->version; - - return $this->databaseVersion; - } - private function getCollection(): Collection { if (null !== $this->collection) { diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php index b792d4d9ca97..fb44803681ad 100644 --- a/src/Symfony/Component/Lock/Store/PdoStore.php +++ b/src/Symfony/Component/Lock/Store/PdoStore.php @@ -13,6 +13,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Schema\Schema; use Symfony\Component\Lock\Exception\InvalidArgumentException; @@ -158,10 +159,10 @@ public function putOffExpiration(Key $key, float $ttl) $stmt->bindValue(':id', $this->getHashedKey($key)); $stmt->bindValue(':token1', $uniqueToken); $stmt->bindValue(':token2', $uniqueToken); - $stmt->execute(); + $result = $stmt->execute(); // If this method is called twice in the same second, the row wouldn't be updated. We have to call exists to know if we are the owner - if (!$stmt->rowCount() && !$this->exists($key)) { + if (!($result instanceof Result ? $result : $stmt)->rowCount() && !$this->exists($key)) { throw new LockConflictedException(); } @@ -191,9 +192,9 @@ public function exists(Key $key) $stmt->bindValue(':id', $this->getHashedKey($key)); $stmt->bindValue(':token', $this->getUniqueToken($key)); - $stmt->execute(); + $result = $stmt->execute(); - return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); + return (bool) ($result instanceof Result ? $result->fetchOne() : $stmt->fetchColumn()); } /** diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php index 0a7fde06ce6d..45ccd65cdf13 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesApiTransport.php @@ -115,6 +115,9 @@ private function getPayload(Email $email, Envelope $envelope): array if ($email->getHtmlBody()) { $payload['Message.Body.Html.Data'] = $email->getHtmlBody(); } + if ($email->getReplyTo()) { + $payload['ReplyToAddresses.member'] = $this->stringifyAddresses($email->getReplyTo()); + } return $payload; } diff --git a/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php b/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php index 0472c36b6209..b06ac839c64f 100644 --- a/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php +++ b/src/Symfony/Component/Mailer/Messenger/SendEmailMessage.php @@ -22,9 +22,6 @@ class SendEmailMessage private $message; private $envelope; - /** - * @internal - */ public function __construct(RawMessage $message, Envelope $envelope = null) { $this->message = $message; diff --git a/src/Symfony/Component/Mailer/Tests/TransportTest.php b/src/Symfony/Component/Mailer/Tests/TransportTest.php index 95eb5b6ebf03..dfd8d1926e61 100644 --- a/src/Symfony/Component/Mailer/Tests/TransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/TransportTest.php @@ -60,6 +60,22 @@ public function fromStringProvider(): iterable ]; } + /** + * @dataProvider fromDsnProvider + */ + public function testFromDsn(string $dsn, TransportInterface $transport): void + { + $this->assertEquals($transport, Transport::fromDsn($dsn)); + } + + public function fromDsnProvider(): iterable + { + yield 'multiple transports' => [ + 'failover(smtp://a smtp://b)', + new FailoverTransport([new Transport\Smtp\EsmtpTransport('a'), new Transport\Smtp\EsmtpTransport('b')]), + ]; + } + /** * @dataProvider fromWrongStringProvider */ diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php index b30ba6fbdc6e..5d5c7e0b6eb8 100644 --- a/src/Symfony/Component/Mailer/Transport.php +++ b/src/Symfony/Component/Mailer/Transport.php @@ -51,7 +51,7 @@ class Transport public static function fromDsn(string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface { - $factory = new self(self::getDefaultFactories($dispatcher, $client, $logger)); + $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger))); return $factory->fromString($dsn); } diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsIntegrationTest.php index 3c47be561475..c840822abea4 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Tests/Transport/AmazonSqsIntegrationTest.php @@ -45,7 +45,7 @@ private function execute(string $dsn): void $connection->setup(); $this->clearSqs($dsn); - $connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]); + $connection->send('{"message": "Hi"}', ['type' => DummyMessage::class, DummyMessage::class => 'special']); $this->assertSame(1, $connection->getMessageCount()); $wait = 0; @@ -54,7 +54,7 @@ private function execute(string $dsn): void } $this->assertEquals('{"message": "Hi"}', $encoded['body']); - $this->assertEquals(['type' => DummyMessage::class], $encoded['headers']); + $this->assertEquals(['type' => DummyMessage::class, DummyMessage::class => 'special'], $encoded['headers']); } private function clearSqs(string $dsn): void diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php index 5d5bfb36c65c..1ae2b463210f 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php @@ -30,6 +30,7 @@ class Connection { private const AWS_SQS_FIFO_SUFFIX = '.fifo'; + private const MESSAGE_ATTRIBUTE_NAME = 'X-Symfony-Messenger'; private const DEFAULT_OPTIONS = [ 'buffer_size' => 9, @@ -200,7 +201,12 @@ private function fetchMessage(): bool foreach ($this->currentResponse->getMessages() as $message) { $headers = []; - foreach ($message->getMessageAttributes() as $name => $attribute) { + $attributes = $message->getMessageAttributes(); + if (isset($attributes[self::MESSAGE_ATTRIBUTE_NAME]) && 'String' === $attributes[self::MESSAGE_ATTRIBUTE_NAME]->getDataType()) { + $headers = json_decode($attributes[self::MESSAGE_ATTRIBUTE_NAME]->getStringValue(), true); + unset($attributes[self::MESSAGE_ATTRIBUTE_NAME]); + } + foreach ($attributes as $name => $attribute) { if ('String' !== $attribute->getDataType()) { continue; } @@ -284,13 +290,27 @@ public function send(string $body, array $headers, int $delay = 0, ?string $mess 'MessageAttributes' => [], ]; + $specialHeaders = []; foreach ($headers as $name => $value) { + if ('.' === $name[0] || self::MESSAGE_ATTRIBUTE_NAME === $name || \strlen($name) > 256 || '.' === substr($name, -1) || 'AWS.' === substr($name, 0, \strlen('AWS.')) || 'Amazon.' === substr($name, 0, \strlen('Amazon.')) || preg_match('/([^a-zA-Z0-9_\.-]+|\.\.)/', $name)) { + $specialHeaders[$name] = $value; + + continue; + } + $parameters['MessageAttributes'][$name] = new MessageAttributeValue([ 'DataType' => 'String', 'StringValue' => $value, ]); } + if (!empty($specialHeaders)) { + $parameters['MessageAttributes'][self::MESSAGE_ATTRIBUTE_NAME] = new MessageAttributeValue([ + 'DataType' => 'String', + 'StringValue' => json_encode($specialHeaders), + ]); + } + if (self::isFifoQueue($this->configuration['queue_name'])) { $parameters['MessageGroupId'] = null !== $messageGroupId ? $messageGroupId : __METHOD__; $parameters['MessageDeduplicationId'] = null !== $messageDeduplicationId ? $messageDeduplicationId : sha1(json_encode(['body' => $body, 'headers' => $headers])); 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 ef94e60f4431..88a68defeeec 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -11,15 +11,15 @@ namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; +use Doctrine\DBAL\Abstraction\Result; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\ResultStatement; -use Doctrine\DBAL\ForwardCompatibility\Driver\ResultStatement as ForwardCompatibleResultStatement; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaConfig; use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer; +use Doctrine\DBAL\Statement; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection; @@ -31,7 +31,7 @@ public function testGetAMessageWillChangeItsStatus() $queryBuilder = $this->getQueryBuilderMock(); $driverConnection = $this->getDBALConnectionMock(); $schemaSynchronizer = $this->getSchemaSynchronizerMock(); - $stmt = $this->getStatementMock([ + $stmt = $this->getResultMock([ 'id' => 1, 'body' => '{"message":"Hi"}', 'headers' => json_encode(['type' => DummyMessage::class]), @@ -65,7 +65,7 @@ public function testGetWithNoPendingMessageWillReturnNull() $queryBuilder = $this->getQueryBuilderMock(); $driverConnection = $this->getDBALConnectionMock(); $schemaSynchronizer = $this->getSchemaSynchronizerMock(); - $stmt = $this->getStatementMock(false); + $stmt = $this->getResultMock(false); $queryBuilder ->method('getParameters') @@ -144,16 +144,12 @@ private function getQueryBuilderMock() return $queryBuilder; } - private function getStatementMock($expectedResult): ResultStatement + private function getResultMock($expectedResult) { - $mockedInterface = interface_exists(ForwardCompatibleResultStatement::class) - ? ForwardCompatibleResultStatement::class - : ResultStatement::class; - - $stmt = $this->createMock($mockedInterface); + $stmt = $this->createMock(interface_exists(Result::class) ? Result::class : Statement::class); $stmt->expects($this->once()) - ->method(method_exists($mockedInterface, 'fetchAssociative') ? 'fetchAssociative' : 'fetch') + ->method(interface_exists(Result::class) ? 'fetchAssociative' : 'fetch') ->willReturn($expectedResult); return $stmt; @@ -270,7 +266,7 @@ public function testFind() $driverConnection = $this->getDBALConnectionMock(); $schemaSynchronizer = $this->getSchemaSynchronizerMock(); $id = 1; - $stmt = $this->getStatementMock([ + $stmt = $this->getResultMock([ 'id' => $id, 'body' => '{"message":"Hi"}', 'headers' => json_encode(['type' => DummyMessage::class]), @@ -315,12 +311,9 @@ public function testFindAll() 'headers' => json_encode(['type' => DummyMessage::class]), ]; - $mockedInterface = interface_exists(ForwardCompatibleResultStatement::class) - ? ForwardCompatibleResultStatement::class - : ResultStatement::class; - $stmt = $this->createMock($mockedInterface); + $stmt = $this->createMock(interface_exists(Result::class) ? Result::class : Statement::class); $stmt->expects($this->once()) - ->method(method_exists($mockedInterface, 'fetchAllAssociative') ? 'fetchAllAssociative' : 'fetchAll') + ->method(interface_exists(Result::class) ? 'fetchAllAssociative' : 'fetchAll') ->willReturn([$message1, $message2]); $driverConnection diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php index a16800464fc1..f5cd170cfbb2 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Version; use PHPUnit\Framework\TestCase; @@ -71,7 +72,7 @@ public function testSendWithDelay() ->setParameter(':body', '{"message": "Hi i am delayed"}') ->execute(); - $available_at = new \DateTime(method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); + $available_at = new \DateTime($stmt instanceof Result ? $stmt->fetchOne() : $stmt->fetchColumn()); $now = new \DateTime(); $now->modify('+60 seconds'); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 90017123e168..39398519bbee 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -13,7 +13,7 @@ use Doctrine\DBAL\Connection as DBALConnection; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Schema\Schema; @@ -175,7 +175,7 @@ public function get(): ?array $query->getParameters(), $query->getParameterTypes() ); - $doctrineEnvelope = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(); + $doctrineEnvelope = $stmt instanceof Result ? $stmt->fetchAssociative() : $stmt->fetch(); if (false === $doctrineEnvelope) { $this->driverConnection->commit(); @@ -267,7 +267,7 @@ public function getMessageCount(): int $stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes()); - return method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn(); + return $stmt instanceof Result ? $stmt->fetchOne() : $stmt->fetchColumn(); } public function findAll(int $limit = null): array @@ -278,7 +278,7 @@ public function findAll(int $limit = null): array } $stmt = $this->executeQuery($queryBuilder->getSQL(), $queryBuilder->getParameters(), $queryBuilder->getParameterTypes()); - $data = method_exists($stmt, 'fetchAllAssociative') ? $stmt->fetchAllAssociative() : $stmt->fetchAll(); + $data = $stmt instanceof Result ? $stmt->fetchAllAssociative() : $stmt->fetchAll(); return array_map(function ($doctrineEnvelope) { return $this->decodeEnvelopeHeaders($doctrineEnvelope); @@ -291,7 +291,7 @@ public function find($id): ?array ->where('m.id = ?'); $stmt = $this->executeQuery($queryBuilder->getSQL(), [$id]); - $data = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(); + $data = $stmt instanceof Result ? $stmt->fetchAssociative() : $stmt->fetch(); return false === $data ? null : $this->decodeEnvelopeHeaders($data); } @@ -350,7 +350,7 @@ private function createQueryBuilder(): QueryBuilder ->from($this->configuration['table_name'], 'm'); } - private function executeQuery(string $sql, array $parameters = [], array $types = []): ResultStatement + private function executeQuery(string $sql, array $parameters = [], array $types = []) { try { $stmt = $this->driverConnection->executeQuery($sql, $parameters, $types); @@ -390,6 +390,7 @@ private function addTableToSchema(Schema $schema): void $table->addColumn('headers', self::$useDeprecatedConstants ? Type::TEXT : Types::TEXT) ->setNotnull(true); $table->addColumn('queue_name', self::$useDeprecatedConstants ? Type::STRING : Types::STRING) + ->setLength(190) // mysql 5.6 only supports 191 characters on an indexed column in utf8mb4 mode ->setNotnull(true); $table->addColumn('created_at', self::$useDeprecatedConstants ? Type::DATETIME : Types::DATETIME_MUTABLE) ->setNotnull(true); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json index 35ddc7f09a52..9d8d8a8fef53 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json @@ -22,7 +22,6 @@ }, "require-dev": { "doctrine/dbal": "^2.6|^3.0", - "doctrine/orm": "^2.6.3", "doctrine/persistence": "^1.3", "symfony/property-access": "^4.4|^5.0", "symfony/serializer": "^4.4|^5.0" diff --git a/src/Symfony/Component/Mime/Address.php b/src/Symfony/Component/Mime/Address.php index 6d663e93b75f..9847712b1074 100644 --- a/src/Symfony/Component/Mime/Address.php +++ b/src/Symfony/Component/Mime/Address.php @@ -89,7 +89,7 @@ public static function create($address): self return $address; } if (\is_string($address)) { - return new self($address); + return self::fromString($address); } throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s") given).', get_debug_type($address))); diff --git a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php index 243aaf10da06..1b555375ce90 100644 --- a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php +++ b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php @@ -24,11 +24,6 @@ final class SMimeSigner extends SMime private $signOptions; private $extraCerts; - /** - * @var string|null - */ - private $privateKeyPassphrase; - /** * @param string $certificate The path of the file containing the signing certificate (in PEM format) * @param string $privateKey The path of the file containing the private key (in PEM format) @@ -52,7 +47,6 @@ public function __construct(string $certificate, string $privateKey, string $pri $this->signOptions = $signOptions ?? PKCS7_DETACHED; $this->extraCerts = $extraCerts ? realpath($extraCerts) : null; - $this->privateKeyPassphrase = $privateKeyPassphrase; } public function sign(Message $message): Message diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php index a667997a9b7e..baf377427d38 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClass.php @@ -69,6 +69,11 @@ public function getPublicAccessor() return $this->publicAccessor; } + public function isPublicAccessor($param) + { + throw new \LogicException('This method should never have been called.'); + } + public function getPublicAccessorWithDefaultValue() { return $this->publicAccessorWithDefaultValue; diff --git a/src/Symfony/Component/PropertyAccess/composer.json b/src/Symfony/Component/PropertyAccess/composer.json index 4cde7bacf9a1..dd7bfb1ac7f5 100644 --- a/src/Symfony/Component/PropertyAccess/composer.json +++ b/src/Symfony/Component/PropertyAccess/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "symfony/inflector": "^4.4|^5.0", "symfony/polyfill-php80": "^1.15", - "symfony/property-info": "^5.1" + "symfony/property-info": "^5.1.1" }, "require-dev": { "symfony/cache": "^4.4|^5.0" diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 8de931d9c577..1cf15345028c 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -40,7 +40,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp /** * @internal */ - public static $defaultAccessorPrefixes = ['is', 'can', 'get', 'has']; + public static $defaultAccessorPrefixes = ['get', 'is', 'has', 'can']; /** * @internal diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index bee7a4a5694c..cd137eea78be 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -171,7 +171,7 @@ protected function parseImport(RouteCollection $collection, array $config, strin $schemes = isset($config['schemes']) ? $config['schemes'] : null; $methods = isset($config['methods']) ? $config['methods'] : null; $trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true; - $namePrefix = $config['name_prefix'] ?? ''; + $namePrefix = $config['name_prefix'] ?? null; $exclude = $config['exclude'] ?? null; if (isset($config['controller'])) { diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index a3284771bb57..b661bb13a61d 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -179,8 +179,8 @@ public function addNamePrefix(string $prefix) foreach ($this->routes as $name => $route) { $prefixedRoutes[$prefix.$name] = $route; - if (null !== $name = $route->getDefault('_canonical_route')) { - $route->setDefault('_canonical_route', $prefix.$name); + if (null !== $canonicalName = $route->getDefault('_canonical_route')) { + $route->setDefault('_canonical_route', $prefix.$canonicalName); } if (isset($this->priorities[$name])) { $prefixedPriorities[$prefix.$name] = $this->priorities[$name]; diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php index 05d3d0162ad1..90a66dbc679e 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php @@ -363,4 +363,21 @@ public function testAddWithPriority() $this->assertSame($expected, $collection2->all()); } + + public function testAddWithPriorityAndPrefix() + { + $collection3 = new RouteCollection(); + $collection3->add('foo3', $foo3 = new Route('/foo'), 0); + $collection3->add('bar3', $bar3 = new Route('/bar'), 1); + $collection3->add('baz3', $baz3 = new Route('/baz')); + $collection3->addNamePrefix('prefix_'); + + $expected = [ + 'prefix_bar3' => $bar3, + 'prefix_foo3' => $foo3, + 'prefix_baz3' => $baz3, + ]; + + $this->assertSame($expected, $collection3->all()); + } } diff --git a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php index 8da2a994bf48..b218e1086c62 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php @@ -95,11 +95,13 @@ public function authenticate(RequestEvent $event) return; } - if ([self::PUBLIC_ACCESS] === $attributes) { - return; + if ([self::PUBLIC_ACCESS] !== $attributes) { + throw $this->createAccessDeniedException($request, $attributes); } + } - throw $this->createAccessDeniedException($request, $attributes); + if ([self::PUBLIC_ACCESS] === $attributes) { + return; } if (!$token->isAuthenticated()) { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php index 9748e6522c6a..154addc7c409 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php @@ -18,8 +18,10 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Http\AccessMapInterface; use Symfony\Component\Security\Http\Event\LazyResponseEvent; use Symfony\Component\Security\Http\Firewall\AccessListener; @@ -279,6 +281,33 @@ public function testHandleWhenPublicAccessIsAllowedAndExceptionOnTokenIsFalse() $this->expectNotToPerformAssertions(); } + public function testHandleWhenPublicAccessWhileAuthenticated() + { + $token = new UsernamePasswordToken(new User('Wouter', null, ['ROLE_USER']), null, 'main', ['ROLE_USER']); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + $request = new Request(); + + $accessMap = $this->createMock(AccessMapInterface::class); + $accessMap->expects($this->any()) + ->method('getPatterns') + ->with($this->equalTo($request)) + ->willReturn([[AccessListener::PUBLIC_ACCESS], null]) + ; + + $listener = new AccessListener( + $tokenStorage, + $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')->getMock(), + $accessMap, + $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(), + false + ); + + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); + + $this->expectNotToPerformAssertions(); + } + public function testHandleMWithultipleAttributesShouldBeHandledAsAnd() { $request = new Request(); diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index fa4f944cd1fe..b8c33a2fe56c 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -21,6 +21,7 @@ use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; @@ -158,7 +159,7 @@ public function normalize($data, string $format = null, array $context = []) } if (\is_array($data) || $data instanceof \Traversable) { - if ($data instanceof \Countable && 0 === $data->count()) { + if (($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) === true && $data instanceof \Countable && 0 === $data->count()) { return $data; } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index cbc40b361cbc..3158f728fbfb 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -492,6 +492,27 @@ public function testNotNormalizableValueExceptionMessageForAResource() (new Serializer())->normalize(tmpfile()); } + public function testNormalizeTransformEmptyArrayObjectToArray() + { + $serializer = new Serializer( + [ + new PropertyNormalizer(), + new ObjectNormalizer(), + new ArrayDenormalizer(), + ], + [ + 'json' => new JsonEncoder(), + ] + ); + + $object = []; + $object['foo'] = new \ArrayObject(); + $object['bar'] = new \ArrayObject(['notempty']); + $object['baz'] = new \ArrayObject(['nested' => new \ArrayObject()]); + + $this->assertSame('{"foo":[],"bar":["notempty"],"baz":{"nested":[]}}', $serializer->serialize($object, 'json')); + } + public function testNormalizePreserveEmptyArrayObject() { $serializer = new Serializer( diff --git a/src/Symfony/Component/String/AbstractString.php b/src/Symfony/Component/String/AbstractString.php index c9ba147b319c..f2754a56d0b6 100644 --- a/src/Symfony/Component/String/AbstractString.php +++ b/src/Symfony/Component/String/AbstractString.php @@ -643,7 +643,11 @@ public function truncate(int $length, string $ellipsis = '', bool $cut = true): } if (!$cut) { - $length = $ellipsisLength + ($this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1) ?? $stringLength); + if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { + return clone $this; + } + + $length += $ellipsisLength; } $str = $this->slice(0, $length - $ellipsisLength); diff --git a/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php b/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php index b333c74a252b..44f14c18af3a 100644 --- a/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php +++ b/src/Symfony/Component/String/Tests/AbstractAsciiTestCase.php @@ -1450,6 +1450,7 @@ public static function provideTruncate() ['foobar...', 'foobar foo', 6, '...', false], ['foobar...', 'foobar foo', 7, '...', false], ['foobar foo...', 'foobar foo a', 10, '...', false], + ['foobar foo aar', 'foobar foo aar', 12, '...', false], ]; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php index 7db835b333f5..677660f6c31a 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionLanguageSyntaxValidatorTest.php @@ -11,39 +11,21 @@ namespace Symfony\Component\Validator\Tests\Constraints; -use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\ExpressionLanguage\SyntaxError; use Symfony\Component\Validator\Constraints\ExpressionLanguageSyntax; use Symfony\Component\Validator\Constraints\ExpressionLanguageSyntaxValidator; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; class ExpressionLanguageSyntaxValidatorTest extends ConstraintValidatorTestCase { - /** - * @var \PHPUnit\Framework\MockObject\MockObject|ExpressionLanguage - */ - protected $expressionLanguage; - protected function createValidator() { - return new ExpressionLanguageSyntaxValidator($this->expressionLanguage); - } - - protected function setUp(): void - { - $this->expressionLanguage = $this->createExpressionLanguage(); - - parent::setUp(); + return new ExpressionLanguageSyntaxValidator(new ExpressionLanguage()); } public function testExpressionValid(): void { - $this->expressionLanguage->expects($this->once()) - ->method('lint') - ->with($this->value, []); - - $this->validator->validate($this->value, new ExpressionLanguageSyntax([ + $this->validator->validate('1 + 1', new ExpressionLanguageSyntax([ 'message' => 'myMessage', 'allowedVariables' => [], ])); @@ -53,12 +35,18 @@ public function testExpressionValid(): void public function testExpressionWithoutNames(): void { - $this->expressionLanguage->expects($this->once()) - ->method('lint') - ->with($this->value, null); + $this->validator->validate('1 + 1', new ExpressionLanguageSyntax([ + 'message' => 'myMessage', + ])); - $this->validator->validate($this->value, new ExpressionLanguageSyntax([ + $this->assertNoViolation(); + } + + public function testExpressionWithAllowedVariableName(): void + { + $this->validator->validate('a + 1', new ExpressionLanguageSyntax([ 'message' => 'myMessage', + 'allowedVariables' => ['a'], ])); $this->assertNoViolation(); @@ -66,24 +54,15 @@ public function testExpressionWithoutNames(): void public function testExpressionIsNotValid(): void { - $this->expressionLanguage->expects($this->once()) - ->method('lint') - ->with($this->value, []) - ->willThrowException(new SyntaxError('Test exception', 42)); - - $this->validator->validate($this->value, new ExpressionLanguageSyntax([ + $this->validator->validate('a + 1', new ExpressionLanguageSyntax([ 'message' => 'myMessage', 'allowedVariables' => [], ])); $this->buildViolation('myMessage') - ->setParameter('{{ syntax_error }}', '"Test exception around position 42."') + ->setParameter('{{ syntax_error }}', '"Variable "a" is not valid around position 1 for expression `a + 1`."') + ->setInvalidValue('a + 1') ->setCode(ExpressionLanguageSyntax::EXPRESSION_LANGUAGE_SYNTAX_ERROR) ->assertRaised(); } - - protected function createExpressionLanguage(): MockObject - { - return $this->getMockBuilder('\Symfony\Component\ExpressionLanguage\ExpressionLanguage')->getMock(); - } } diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index 55dfc1276366..fcb1d146944a 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -92,7 +92,7 @@ public function testExport(string $testName, $value, bool $staticValueExpected = } elseif (\PHP_VERSION_ID < 70400) { $fixtureFile = __DIR__.'/Fixtures/'.$testName.'-legacy.php'; } else { - $this->markAsSkipped('PHP >= 7.4.6 required.'); + $this->markTestSkipped('PHP >= 7.4.6 required.'); } $this->assertStringEqualsFile($fixtureFile, $dump); diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index a60d8057e0b7..2c23815bad64 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -811,6 +811,30 @@ public function testUncheckedTimeoutThrows() } } + public function testTimeoutWithActiveConcurrentStream() + { + $p1 = TestHttpServer::start(8067); + $p2 = TestHttpServer::start(8077); + + $client = $this->getHttpClient(__FUNCTION__); + $streamingResponse = $client->request('GET', 'http://localhost:8067/max-duration'); + $blockingResponse = $client->request('GET', 'http://localhost:8077/timeout-body', [ + 'timeout' => 0.25, + ]); + + $this->assertSame(200, $streamingResponse->getStatusCode()); + $this->assertSame(200, $blockingResponse->getStatusCode()); + + $this->expectException(TransportExceptionInterface::class); + + try { + $blockingResponse->getContent(); + } finally { + $p1->stop(); + $p2->stop(); + } + } + public function testDestruct() { $client = $this->getHttpClient(__FUNCTION__); diff --git a/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php b/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php index 5dae27c912a0..1af9130f964f 100644 --- a/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php +++ b/src/Symfony/Contracts/HttpClient/Test/TestHttpServer.php @@ -16,23 +16,28 @@ class TestHttpServer { - private static $process; + private static $process = []; - public static function start() + public static function start(int $port = 8057) { - if (self::$process) { - self::$process->stop(); + if (isset(self::$process[$port])) { + self::$process[$port]->stop(); + } else { + register_shutdown_function(static function () use ($port) { + self::$process[$port]->stop(); + }); } $finder = new PhpExecutableFinder(); - $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:8057'])); + $process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', '127.0.0.1:'.$port])); $process->setWorkingDirectory(__DIR__.'/Fixtures/web'); $process->start(); + self::$process[$port] = $process; do { usleep(50000); - } while (!@fopen('http://127.0.0.1:8057/', 'r')); + } while (!@fopen('http://127.0.0.1:'.$port, 'r')); - self::$process = $process; + return $process; } }