diff --git a/.github/build-packages.php b/.github/build-packages.php index e61eae51df550..5e9bcc141544e 100644 --- a/.github/build-packages.php +++ b/.github/build-packages.php @@ -57,7 +57,7 @@ $versions = @file_get_contents('https://repo.packagist.org/p/'.$package->name.'.json') ?: sprintf('{"packages":{"%s":{"dev-master":%s}}}', $package->name, file_get_contents($dir.'/composer.json')); $versions = json_decode($versions)->packages->{$package->name}; - if ($package->version === str_replace('-dev', '.x-dev', $versions->{'dev-master'}->extra->{'branch-alias'}->{'dev-master'})) { + if (isset($versions->{'dev-master'}) && $package->version === str_replace('-dev', '.x-dev', $versions->{'dev-master'}->extra->{'branch-alias'}->{'dev-master'})) { unset($versions->{'dev-master'}); } diff --git a/.travis.yml b/.travis.yml index 353a51e18077d..4626356876c0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -222,7 +222,7 @@ install: composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master - | - # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one + # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number as the next one [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]] && LEGACY=,legacy export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev diff --git a/CHANGELOG-4.2.md b/CHANGELOG-4.2.md index 7c7c018037c6d..40e3209d1d568 100644 --- a/CHANGELOG-4.2.md +++ b/CHANGELOG-4.2.md @@ -7,6 +7,41 @@ in 4.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.2.0...v4.2.1 +* 4.2.10 (2019-06-26) + + * bug #31972 Add missing rendering of form help block. (alexsegura) + * bug #32137 [HttpFoundation] fix accessing session bags (xabbuh) + * bug #32164 [EventDispatcher] collect called listeners information only once (xabbuh) + * bug #32173 [FrameworkBundle] Fix calling Client::getProfile() before sending a request (dunglas) + * bug #32163 [DoctrineBridge] Fix type error (norkunas) + * bug #32170 [Security/Core] Don't use ParagonIE_Sodium_Compat (nicolas-grekas) + * bug #32094 [Validator] Use LogicException for missing Property Access Component in comparison constraints (Lctrs) + * bug #32123 [Form] fix translation domain (xabbuh) + * bug #32116 [FrameworkBundle] tag the FileType service as a form type (xabbuh) + * bug #32090 [Debug] workaround BC break in PHP 7.3 (nicolas-grekas) + * bug #32076 [Lock] Fix PDO prune not called (jderusse) + * bug #32071 Fix expired lock not cleaned (jderusse) + * bug #32057 [HttpFoundation] Fix SA/phpdoc JsonResponse (ro0NL) + * bug #32025 SimpleCacheAdapter fails to cache any item if a namespace is used (moufmouf) + * bug #32037 [Form] validate composite constraints in all groups (xabbuh) + * bug #32007 [Serializer] Handle true and false appropriately in CSV encoder (battye) + * bug #32000 [Routing] fix absolute url generation when scheme is not known (Tobion) + * bug #32024 [VarDumper] fix dumping objects that implement __debugInfo() (nicolas-grekas) + * bug #31962 Fix reporting unsilenced deprecations from insulated tests (nicolas-grekas) + * bug #31925 [Form] fix usage of legacy TranslatorInterface (nicolas-grekas) + * bug #31908 [Validator] fix deprecation layer of ValidatorBuilder (nicolas-grekas) + * bug #31894 Fix wrong requirements for ocramius/proxy-manager in root composer.json (henrikvolmer) + * bug #31865 [Form] Fix wrong DateTime on outdated ICU library (aweelex) + * bug #31879 [Cache] Pass arg to get callback everywhere (fancyweb) + * bug #31863 [HttpFoundation] Fixed case-sensitive handling of cache-control header in RedirectResponse constructor (Ivo) + * bug #31869 Fix json-encoding when JSON_THROW_ON_ERROR is used (nicolas-grekas) + * bug #31868 [HttpKernel] Fix handling non-catchable fatal errors (nicolas-grekas) + * bug #31860 [HttpFoundation] work around PHP 7.3 bug related to json_encode() (nicolas-grekas) + * bug #31407 [Security] added support for updated "distinguished name" format in x509 authentication (Robert Kopera) + * bug #31786 [Translation] Fixed case sensitivity of lint:xliff command (javiereguiluz) + * bug #31757 [DomCrawler] Fix type error with null Form::$currentUri (chalasr) + * bug #31654 [HttpFoundation] Do not set X-Accel-Redirect for paths outside of X-Accel-Mapping (vilius-g) + * 4.2.9 (2019-05-28) * bug #31584 [Workflow] Do not trigger extra guards (lyrixx) diff --git a/CHANGELOG-4.3.md b/CHANGELOG-4.3.md index 965e52e7db2a5..15c76d1c33d3f 100644 --- a/CHANGELOG-4.3.md +++ b/CHANGELOG-4.3.md @@ -7,6 +7,64 @@ in 4.3 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.3.0...v4.3.1 +* 4.3.3 (2019-07-28) + + * bug #32726 [Messenger] Fix redis last error not cleared between calls (chalasr) + * bug #32760 [HttpKernel] clarify error handler restoring process (xabbuh) + * bug #32730 [Inflector] Fix pluralizing words ending with "son" (norkunas) + * bug #32715 [DI] fix perf issue with lazy autowire error messages (nicolas-grekas) + * bug #32503 Fix multiSelect ChoiceQuestion when answers have spaces (IceMaD) + * bug #32688 [Yaml] fix inline handling when dumping tagged values (xabbuh) + * bug #32710 [Security/Core] align defaults for sodium with PHP 7.4 (nicolas-grekas) + * bug #32644 [WebProfileBundle] Avoid getting right to left style (Arman-Hosseini) + * bug #32689 [HttpClient] rewind stream when using Psr18Client (nicolas-grekas) + * bug #32700 [Messenger] Flatten collection of stamps collected by the traceable middleware (ogizanagi) + * bug #32699 [HttpClient] fix canceling responses in a streaming loop (nicolas-grekas) + * bug #32679 [Intl] relax some date parser patterns (xabbuh) + * bug #31303 [VarDumper] Use \ReflectionReference for determining if a key is a reference (php >= 7.4) (dorumd, nicolas-grekas) + * bug #32485 [Validator] Added support for validation of giga values (kernig) + * bug #32567 [Messenger] pass transport name to factory (Tobion) + * bug #32568 [Messenger] Fix UnrecoverableExceptionInterface handling (LanaiGrunt) + * bug #32604 Properly handle optional tag attributes for !tagged_iterator (apfelbox) + * bug #32571 [HttpClient] fix debug output added to stderr at shutdown (nicolas-grekas) + * bug #32443 [PHPUnitBridge] Mute deprecations triggered from phpunit (greg0ire) + * bug #32572 Bump minimum version of symfony/phpunit-bridge (fancyweb) + * bug #32438 [Serializer] XmlEncoder: don't cast padded strings (ogizanagi) + * bug #32579 [Config] Do not use absolute path when computing the vendor freshness (lyrixx) + * bug #32563 Container*::getServiceIds() should return strings (mathroc) + * bug #32553 [Mailer] Allow register mailer configuration in xml format (Koc) + * bug #32442 Adding missing event_dispatcher wiring for messenger.middleware.send_message (weaverryan) + * bug #32466 [Config] Fix for signatures of typed properties (tvandervorm) + * bug #32501 [FrameworkBundle] Fix descriptor of routes described as callable array (ribeiropaulor) + * bug #32500 [Debug][DebugClassLoader] Include found files instead of requiring them (fancyweb) + * bug #32464 [WebProfilerBundle] Fix Twig 1.x compatibility (yceruto) + * bug #31620 [FrameworkBundle] Inform the user when save_path will be ignored (gnat42) + * bug #32096 Don't assume port 0 for X-Forwarded-Port (alexbowers, xabbuh) + * bug #31820 [SecurityBundle] Fix profiler dump for non-invokable security listeners (chalasr) + * bug #32392 [Messenger] Doctrine Transport: Support setting auto_setup from DSN (bendavies) + * bug #31267 [Translator] Load plurals from mo files properly (Stadly) + * bug #31266 [Translator] Load plurals from po files properly (Stadly) + * bug #32383 [Serializer] AbstractObjectNormalizer ignores the property types of discriminated classes (sandergo90) + * bug #32413 [Messenger] fix publishing headers set on AmqpStamp (Tobion) + * bug #32421 [EventDispatcher] Add tag kernel.rest on 'debug.event_dispatcher' service (lyrixx) + * bug #32398 [Messenger] Removes deprecated call to ReflectionType::__toString() on MessengerPass (brunowowk) + * bug #32379 [SecurityBundle] conditionally register services (xabbuh) + * bug #32380 [Messenger] fix broken key normalization (Tobion) + * bug #32363 [FrameworkBundle] reset cache pools between requests (nicolas-grekas) + * bug #32365 [DI] fix processing of regular parameter bags by MergeExtensionConfigurationPass (nicolas-grekas) + * bug #32187 [PHPUnit] Fixed composer error on Windows (misterx) + * bug #32299 [Lock] Stores must implement `putOffExpiration` (jderusse) + * bug #32302 [Mime] Remove @internal annotations for the serialize methods (francoispluchino) + * bug #32334 [Messenger] Fix authentication for redis transport (alexander-schranz) + * bug #32309 Fixing validation for messenger transports retry_strategy service key (weaverryan) + * bug #32331 [Workflow] only decorate when an event dispatcher was passed (xabbuh) + * bug #32236 [Cache] work aroung PHP memory leak (nicolas-grekas) + * bug #32206 Catch JsonException and rethrow in JsonEncode (phil-davis) + * bug #32211 [Mailer] Fix error message when connecting to a stream raises an error before connect() (fabpot) + * bug #32210 [Mailer] Fix timeout type hint (fabpot) + * bug #32199 [EventDispatcher] improve error messages in the event dispatcher (xabbuh) + * bug #32200 [Security/Core] work around sodium_compat issue (nicolas-grekas) + * 4.3.2 (2019-06-26) * bug #31954 [PhpunitBridge] Read environment variable from superglobals (greg0ire) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index fddccc428e7c9..011ad9bee0777 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -19,8 +19,8 @@ Symfony is the result of the work of many people who made the code better - Jakub Zalas (jakubzalas) - Johannes S (johannes) - Javier Eguiluz (javier.eguiluz) - - Kris Wallsmith (kriswallsmith) - Roland Franssen (ro0) + - Kris Wallsmith (kriswallsmith) - Grégoire Pineau (lyrixx) - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) @@ -31,9 +31,9 @@ Symfony is the result of the work of many people who made the code better - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) + - Yonel Ceruto (yonelceruto) - Martin Hasoň (hason) - Jeremy Mikola (jmikola) - - Yonel Ceruto (yonelceruto) - Jean-François Simon (jfsimon) - Jules Pietri (heah) - Benjamin Eberlei (beberlei) @@ -45,35 +45,35 @@ Symfony is the result of the work of many people who made the code better - Jonathan Wage (jwage) - Tobias Nyholm (tobias) - Lynn van der Berg (kjarli) + - Jérémy DERUSSÉ (jderusse) - Diego Saint Esteben (dosten) - Alexandre Salomé (alexandresalome) - William Durand (couac) - ornicar - - Jérémy DERUSSÉ (jderusse) + - Alexander M. Turek (derrabus) - Dany Maillard (maidmaid) - Francis Besset (francisbesset) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - Matthias Pigulla (mpdude) - Bulat Shakirzyanov (avalanche123) - - Alexander M. Turek (derrabus) - Saša Stamenković (umpirsky) - Peter Rehm (rpet) - - Kevin Bond (kbond) - Pierre du Plessis (pierredup) + - Kevin Bond (kbond) - Henrik Bjørnskov (henrikbjorn) - Miha Vrhovnik - Diego Saint Esteben (dii3g0) + - Grégoire Paris (greg0ire) - Konstantin Kudryashov (everzet) - Gábor Egyed (1ed) - - Grégoire Paris (greg0ire) - - Bilal Amarni (bamarni) - Titouan Galopin (tgalopin) + - Konstantin Myakshin (koc) + - Bilal Amarni (bamarni) - Mathieu Piot (mpiot) - David Maicher (dmaicher) - Florin Patan (florinpatan) - Valentin Udaltsov (vudaltsov) - - Konstantin Myakshin (koc) - Gabriel Ostrolucký (gadelat) - Vladimir Reznichenko (kalessil) - Jáchym Toušek (enumag) @@ -95,12 +95,12 @@ Symfony is the result of the work of many people who made the code better - Luis Cordova (cordoval) - Graham Campbell (graham) - Daniel Holmes (dholmes) + - Thomas Calvet (fancyweb) - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - Jérôme Tamarelle (gromnan) - John Wards (johnwards) - - Thomas Calvet (fancyweb) - Fran Moreno (franmomu) - Antoine Hérault (herzult) - Paráda József (paradajozsef) @@ -120,6 +120,7 @@ Symfony is the result of the work of many people who made the code better - Peter Kokot (maastermedia) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) + - Jan Schädlich (jschaedl) - Colin Frei - Javier Spagnoletti (phansys) - Adrien Brault (adrienbrault) @@ -152,7 +153,6 @@ Symfony is the result of the work of many people who made the code better - Arnaud Kleinpeter (nanocom) - Jannik Zschiesche (apfelbox) - Guilherme Blanco (guilhermeblanco) - - Jan Schädlich (jschaedl) - SpacePossum - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) @@ -175,13 +175,17 @@ Symfony is the result of the work of many people who made the code better - Alessandro Chitolina (alekitto) - Hiromi Hishida (77web) - Matthieu Ouellette-Vachon (maoueh) + - Massimiliano Arione (garak) - Michał Pipa (michal.pipa) - Dawid Nowak + - George Mponos (gmponos) - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) - Artur Kotyrba - Tyson Andre - GDIBass + - Samuel NELA (snela) + - Vincent Touzet (vincenttouzet) - Alexander Schranz (alexander-schranz) - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - James Halsall (jaitsu) @@ -194,15 +198,12 @@ Symfony is the result of the work of many people who made the code better - Daniel Espendiller - Possum - Dorian Villet (gnutix) - - George Mponos (gmponos) - Sergey Linnik (linniksa) - Richard Miller (mr_r_miller) - Albert Casademont (acasademont) - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) - DQNEO - - Samuel NELA (snela) - - Vincent Touzet (vincenttouzet) - Gregor Harlan (gharlan) - Gary PEGEOT (gary-p) - Ruben Gonzalez (rubenrua) @@ -242,12 +243,12 @@ Symfony is the result of the work of many people who made the code better - Sven Paulus (subsven) - Maxime Veber (nek-) - Rui Marinho (ruimarinho) - - Massimiliano Arione (garak) - Eugene Wissner - Pascal Montoya - Julien Brochet (mewt) - Leo Feyer - Tristan Darricau (nicofuma) + - Victor Bocharsky (bocharsky_bw) - Marcel Beerta (mazen) - Pavel Batanov (scaytrase) - Mantis Development @@ -300,7 +301,6 @@ Symfony is the result of the work of many people who made the code better - Chekote - Antoine Makdessi (amakdessi) - Thomas Adam - - Viktor Bocharskyi (bocharsky_bw) - Jhonny Lidfors (jhonne) - Diego Agulló (aeoris) - jdhoek @@ -374,6 +374,7 @@ Symfony is the result of the work of many people who made the code better - Berny Cantos (xphere81) - Thierry Thuon (lepiaf) - Ricard Clau (ricardclau) + - dFayet - Mark Challoner (markchalloner) - Gennady Telegin (gtelegin) - Erin Millard @@ -438,6 +439,7 @@ Symfony is the result of the work of many people who made the code better - Joe Lencioni - Daniel Tschinder - vladimir.reznichenko + - Ruud Kamphuis (ruudk) - Kai - Lee Rowlands - Krzysztof Piasecki (krzysztek) @@ -458,6 +460,7 @@ Symfony is the result of the work of many people who made the code better - Karel Souffriau - Christophe L. (christophelau) - Anthon Pang (robocoder) + - Michael Käfer (michael_kaefer) - Sébastien Santoro (dereckson) - Brian King - Michel Salib (michelsalib) @@ -539,6 +542,7 @@ Symfony is the result of the work of many people who made the code better - Sergio Santoro - Robin van der Vleuten (robinvdvleuten) - Philipp Rieber (bicpi) + - Tomas Norkūnas (norkunas) - Manuel de Ruiter (manuel) - Eduardo Oliveira (entering) - Ilya Antipenko (aivus) @@ -563,6 +567,7 @@ Symfony is the result of the work of many people who made the code better - Jakub Škvára (jskvara) - Andrew Udvare (audvare) - alexpods + - Saif Eddin G - Adam Szaraniec (mimol) - Dariusz Ruminski - Erik Trapman (eriktrapman) @@ -597,7 +602,6 @@ Symfony is the result of the work of many people who made the code better - Martin Morávek (keeo) - Steven Surowiec - Kevin Saliou (kbsali) - - Ruud Kamphuis (ruudk) - Shawn Iwinski - Gawain Lynch (gawain) - NothingWeAre @@ -630,6 +634,7 @@ Symfony is the result of the work of many people who made the code better - Baptiste Leduc (bleduc) - Jean-Christophe Cuvelier [Artack] - Simon DELICATA + - Dmitry Simushev - alcaeus - Fred Cox - vitaliytv @@ -688,7 +693,6 @@ Symfony is the result of the work of many people who made the code better - Vincent Simonin - Alex Bogomazov (alebo) - maxime.steinhausser - - dFayet - adev - Stefan Warman - Arkadius Stefanski (arkadius) @@ -722,6 +726,7 @@ Symfony is the result of the work of many people who made the code better - Jaroslav Kuba - Stephan Vock - Benjamin Zikarsky (bzikarsky) + - battye - Simon Schick (simonsimcity) - redstar504 - Tristan Roussel @@ -763,10 +768,10 @@ Symfony is the result of the work of many people who made the code better - Arturas Smorgun (asarturas) - Alexander Volochnev (exelenz) - Michael Piecko + - Toni Peric (tperic) - yclian - Alan Poulain - Aleksey Prilipko - - Tomas Norkūnas (norkunas) - Andrew Berry - twifty - Indra Gunawan (guind) @@ -816,6 +821,7 @@ Symfony is the result of the work of many people who made the code better - John Bohn (jbohn) - Marc Morera (mmoreram) - Saif Eddin Gmati (azjezz) + - BENOIT POLASZEK (bpolaszek) - Andrew Hilobok (hilobok) - Noah Heck (myesain) - Christian Soronellas (theunic) @@ -837,6 +843,7 @@ Symfony is the result of the work of many people who made the code better - Olivier Maisonneuve (olineuve) - Pedro Miguel Maymone de Resende (pedroresende) - Masterklavi + - Franco Traversaro (belinde) - Francis Turmel (fturmel) - Nikita Nefedov (nikita2206) - cgonzalez @@ -851,6 +858,7 @@ Symfony is the result of the work of many people who made the code better - Antoine Lamirault - Adrien Lucas (adrienlucas) - Zhuravlev Alexander (scif) + - Stefano Degenkamp (steef) - James Michael DuPont - Tom Klingenberg - Christopher Hall (mythmakr) @@ -904,6 +912,7 @@ Symfony is the result of the work of many people who made the code better - Benoît Bourgeois - mantulo - Stefan Kruppa + - mmokhi - corphi - grizlik - Derek ROTH @@ -912,6 +921,7 @@ Symfony is the result of the work of many people who made the code better - Dmytro Boiko (eagle) - Shin Ohno (ganchiku) - Geert De Deckere (geertdd) + - Jacek Jędrzejewski (jacek.jedrzejewski) - Jan Kramer (jankramer) - abdul malik ikhsan (samsonasik) - Henry Snoek (snoek09) @@ -922,7 +932,6 @@ Symfony is the result of the work of many people who made the code better - Morgan Auchede (mauchede) - Sascha Dens (saschadens) - Don Pinkster - - Saif Eddin G - Maksim Muruev - Emil Einarsson - Thomas Landauer @@ -933,13 +942,14 @@ Symfony is the result of the work of many people who made the code better - Tony Tran - Jacques Moati - Balazs Csaba (balazscsaba2006) + - Bill Hance (billhance) - Douglas Reith (douglas_reith) - Forfarle (forfarle) - Harry Walter (haswalt) - Johnson Page (jwpage) - - Michael Käfer (michael_kaefer) - Ruben Gonzalez (rubenruateltek) - Michael Roterman (wtfzdotnet) + - Andrii Dembitskyi - Arno Geurts - Adán Lobato (adanlobato) - Ian Jenkins (jenkoian) @@ -986,7 +996,6 @@ Symfony is the result of the work of many people who made the code better - d-ph - Renan Taranto (renan-taranto) - Thomas Talbot (ioni) - - Dmitry Simushev - Rikijs Murgs - Uladzimir Tsykun - Ben Ramsey (ramsey) @@ -1132,7 +1141,6 @@ Symfony is the result of the work of many people who made the code better - hugofonseca (fonsecas72) - Martynas Narbutas - Toon Verwerft (veewee) - - battye - Bailey Parker - Eddie Jaoude - Antanas Arvasevicius @@ -1269,6 +1277,7 @@ Symfony is the result of the work of many people who made the code better - Pablo Schläpfer - Gert de Pagter - Jelte Steijaert (jelte) + - David Négrier (moufmouf) - Quique Porta (quiqueporta) - stoccc - Andrea Quintino (dirk39) @@ -1290,7 +1299,6 @@ Symfony is the result of the work of many people who made the code better - Lars Ambrosius Wallenborn (larsborn) - Oriol Mangas Abellan (oriolman) - Sebastian Göttschkes (sgoettschkes) - - Toni Peric (tperic) - Tatsuya Tsuruoka - Ross Tuck - Kévin Gomez (kevin) @@ -1324,7 +1332,6 @@ Symfony is the result of the work of many people who made the code better - Sébastien HOUZÉ - Jingyu Wang - steveYeah - - BENOIT POLASZEK (bpolaszek) - Samy Dindane (dinduks) - Keri Henare (kerihenare) - Cédric Lahouste (rapotor) @@ -1431,6 +1438,7 @@ Symfony is the result of the work of many people who made the code better - BilgeXA - r1pp3rj4ck - phydevs + - mmokhi - Robert Queck - Peter Bouwdewijn - mlively @@ -1455,6 +1463,7 @@ Symfony is the result of the work of many people who made the code better - Mike Meier - Tim Jabs - Sebastian Ionescu + - Robert Kopera - Pablo Ogando Ferreira - Thomas Ploch - Simon Neidhold @@ -1490,6 +1499,7 @@ Symfony is the result of the work of many people who made the code better - LubenZA - Olivier - Cyril PASCAL + - Michael Bessolov - pscheit - Wybren Koelmans - Zdeněk Drahoš @@ -1651,6 +1661,7 @@ Symfony is the result of the work of many people who made the code better - downace - Aarón Nieves Fernández - Mike Meier + - Vilius Grigaliūnas - Kirill Saksin - Julien Pauli - Koalabaerchen @@ -1698,6 +1709,7 @@ Symfony is the result of the work of many people who made the code better - Nicole Cordes - Martin Kirilov - amcastror + - Alexander Li (aweelex) - Bram Van der Sype (brammm) - Guile (guile) - Julien Moulin (lizjulien) @@ -1717,6 +1729,7 @@ Symfony is the result of the work of many people who made the code better - Sam Ward - Walther Lalk - Adam + - Ivo - Sören Bernstein - devel - taiiiraaa @@ -1751,7 +1764,6 @@ Symfony is the result of the work of many people who made the code better - Hans Nilsson (hansnilsson) - Andrew Marcinkevičius (ifdattic) - Ioana Hazsda (ioana-hazsda) - - Jacek Jędrzejewski (jacek.jedrzejewski) - Jan Marek (janmarek) - Mark de Haan (markdehaan) - Dan Patrick (mdpatrick) @@ -1760,6 +1772,7 @@ Symfony is the result of the work of many people who made the code better - tante kinast (tante) - Ahmed Hannachi (tiecoders) - Vincent LEFORT (vlefort) + - Walid BOUGHDIRI (walidboughdiri) - Darryl Hein (xmmedia) - Sadicov Vladimir (xtech) - Kevin EMO (zarcox) @@ -1774,6 +1787,7 @@ Symfony is the result of the work of many people who made the code better - Vincent Chalnot - James Hudson - Tom Maguire + - Mateusz Lerczak - Richard Quadling - David Zuelke - Adrian @@ -1948,6 +1962,7 @@ Symfony is the result of the work of many people who made the code better - Ulf Reimers (ureimers) - Wotre - goohib + - Chi-teck - Tom Counsell - Xavier HAUSHERR - Ron Gähler @@ -2007,6 +2022,7 @@ Symfony is the result of the work of many people who made the code better - Cédric Bertolini - n-aleha - Anatol Belski + - Anderson Müller - Şəhriyar İmanov - Alexis BOYER - Kaipi Yann @@ -2019,7 +2035,6 @@ Symfony is the result of the work of many people who made the code better - Tammy D - Daniel STANCU - Ryan Rud - - mmokhi - Ondrej Slinták - vlechemin - Brian Corrigan @@ -2110,10 +2125,10 @@ Symfony is the result of the work of many people who made the code better - Alex Olmos (alexolmos) - Antonio Mansilla (amansilla) - Robin Kanters (anddarerobin) + - Andrii Popov (andrii-popov) - Juan Ases García (ases) - Siragusa (asiragusa) - Daniel Basten (axhm3a) - - Bill Hance (billhance) - Bernd Matzner (bmatzner) - Bram Tweedegolf (bram_tweedegolf) - Brandon Kelly (brandonkelly) @@ -2150,6 +2165,7 @@ Symfony is the result of the work of many people who made the code better - Justin Rainbow (jrainbow) - Juan Luis (juanlugb) - JuntaTom (juntatom) + - Julien Manganne (juuuuuu) - Ismail Faizi (kanafghan) - Sébastien Armand (khepin) - Pierre-Chanel Gauthier (kmecnin) diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md index 531a8e61b99b6..74357470f1990 100644 --- a/UPGRADE-4.3.md +++ b/UPGRADE-4.3.md @@ -57,6 +57,12 @@ EventDispatcher * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated * The `Event` class has been deprecated, use `Symfony\Contracts\EventDispatcher\Event` instead +Filesystem +---------- + + * Support for passing arrays to `Filesystem::dumpFile()` is deprecated. + * Support for passing arrays to `Filesystem::appendToFile()` is deprecated. + Form ---- @@ -71,6 +77,7 @@ Form FrameworkBundle --------------- + * Deprecated the `framework.templating` option, configure the Twig bundle instead. * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will be mandatory in 5.0. * Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead. @@ -234,10 +241,10 @@ Workflow ```yaml framework: workflows: - type: workflow article: + type: workflow marking_store: - type: multiple + type: multiple_state arguments: states ``` @@ -245,8 +252,8 @@ Workflow ```yaml framework: workflows: - type: workflow article: + type: workflow marking_store: type: method property: states @@ -267,8 +274,8 @@ Workflow ```yaml framework: workflows: - type: state_machine article: + type: state_machine marking_store: type: method property: state @@ -297,6 +304,8 @@ Workflow type: method ``` + * Using `DefinitionBuilder::setInitialPlace()` is deprecated, use `DefinitionBuilder::setInitialPlaces()` instead. + Yaml ---- diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index b753931008327..1cccc89664e8b 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -169,8 +169,8 @@ Form FrameworkBundle --------------- + * Removed the `framework.templating` option, configure the Twig bundle instead. * The project dir argument of the constructor of `AssetsInstallCommand` is required. - * Removed support for `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn` is either the service ID when using controllers as services or the FQCN of the controller. @@ -385,11 +385,11 @@ TwigBundle * The default value (`false`) of the `twig.strict_variables` configuration option has been changed to `%kernel.debug%`. * The `transchoice` tag and filter have been removed, use the `trans` ones instead with a `%count%` parameter. * Removed support for legacy templates directories `src/Resources/views/` and `src/Resources//views/`, use `templates/` and `templates/bundles//` instead. - + TwigBridge ---------- - * removed the `$requestStack` and `$requestContext` arguments of the + * removed the `$requestStack` and `$requestContext` arguments of the `HttpFoundationExtension`, pass a `Symfony\Component\HttpFoundation\UrlHelper` instance as the only argument instead @@ -417,6 +417,7 @@ Workflow * `MarkingStoreInterface::setMarking()` has a third argument: `array $context = []`. * Removed support of `initial_place`. Use `initial_places` instead. * `MultipleStateMarkingStore` has been removed. Use `MethodMarkingStore` instead. + * `DefinitionBuilder::setInitialPlace()` has been removed, use `DefinitionBuilder::setInitialPlaces()` instead. Before: ```yaml diff --git a/composer.json b/composer.json index 897516fd30bca..34da7655e0ca9 100644 --- a/composer.json +++ b/composer.json @@ -115,7 +115,7 @@ "psr/http-client": "^1.0", "psr/simple-cache": "^1.0", "egulias/email-validator": "~1.2,>=1.2.8|~2.0", - "symfony/phpunit-bridge": "~3.4|~4.0", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|~5.0", "symfony/security-acl": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0" }, diff --git a/phpunit b/phpunit index dc9e127b78fb7..6d5bdc279bcd7 100755 --- a/phpunit +++ b/phpunit @@ -1,7 +1,7 @@ #!/usr/bin/env php listeners[$eventName] as $hash => $listener) { if (\is_string($listener)) { diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index ee4c644f0ea8a..a25a670e80ef9 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -120,7 +120,7 @@ public function getName() return 'db'; } - private function sanitizeQueries($connectionName, $queries) + private function sanitizeQueries(string $connectionName, array $queries) { foreach ($queries as $i => $query) { $queries[$i] = $this->sanitizeQuery($connectionName, $query); @@ -129,7 +129,7 @@ private function sanitizeQueries($connectionName, $queries) return $queries; } - private function sanitizeQuery($connectionName, $query) + private function sanitizeQuery(string $connectionName, $query) { $query['explainable'] = true; if (null === $query['params']) { diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index deaa64e7c9084..6016c8ce0905f 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -109,7 +109,7 @@ private function addTaggedListeners(ContainerBuilder $container) } } - private function getEventManagerDef(ContainerBuilder $container, $name) + private function getEventManagerDef(ContainerBuilder $container, string $name) { if (!isset($this->eventManagers[$name])) { $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); @@ -128,12 +128,9 @@ private function getEventManagerDef(ContainerBuilder $container, $name) * @see https://bugs.php.net/bug.php?id=53710 * @see https://bugs.php.net/bug.php?id=60926 * - * @param string $tagName - * @param ContainerBuilder $container - * * @return array */ - private function findAndSortTags($tagName, ContainerBuilder $container) + private function findAndSortTags(string $tagName, ContainerBuilder $container) { $sortedTags = []; diff --git a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php index e7df3702ebf1f..db9db4f286f0a 100644 --- a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php @@ -50,7 +50,7 @@ public function setRepository(EntityManagerInterface $entityManager, $entityName /** * @return ObjectRepository */ - private function createRepository(EntityManagerInterface $entityManager, $entityName) + private function createRepository(EntityManagerInterface $entityManager, string $entityName) { /* @var $metadata ClassMetadata */ $metadata = $entityManager->getClassMetadata($entityName); @@ -59,7 +59,7 @@ private function createRepository(EntityManagerInterface $entityManager, $entity return new $repositoryClassName($entityManager, $metadata); } - private function getRepositoryHash(EntityManagerInterface $entityManager, $entityName) + private function getRepositoryHash(EntityManagerInterface $entityManager, string $entityName) { return $entityManager->getClassMetadata($entityName)->getName().spl_object_hash($entityManager); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 0a9bf739fc224..ab0070e3d2fd2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -848,7 +848,8 @@ public function testPreferredChoices() ]); $this->assertEquals([3 => new ChoiceView($entity3, '3', 'Baz'), 2 => new ChoiceView($entity2, '2', 'Bar')], $field->createView()->vars['preferred_choices']); - $this->assertEquals([1 => new ChoiceView($entity1, '1', 'Foo')], $field->createView()->vars['choices']); + $this->assertArrayHasKey(1, $field->createView()->vars['choices']); + $this->assertEquals(new ChoiceView($entity1, '1', 'Foo'), $field->createView()->vars['choices'][1]); } public function testOverrideChoicesWithPreferredChoices() @@ -868,7 +869,8 @@ public function testOverrideChoicesWithPreferredChoices() ]); $this->assertEquals([3 => new ChoiceView($entity3, '3', 'Baz')], $field->createView()->vars['preferred_choices']); - $this->assertEquals([2 => new ChoiceView($entity2, '2', 'Bar')], $field->createView()->vars['choices']); + $this->assertArrayHasKey(2, $field->createView()->vars['choices']); + $this->assertEquals(new ChoiceView($entity2, '2', 'Bar'), $field->createView()->vars['choices'][2]); } public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier() diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index f9457c937d531..e059fe2a9d995 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -128,6 +128,9 @@ public function handleError($type, $msg, $file, $line, $context = []) } $deprecation = new Deprecation($msg, debug_backtrace(), $file); + if ($deprecation->isMuted()) { + return; + } $group = 'other'; if ($deprecation->originatesFromAnObject()) { diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 6d5b39fd54212..2d6aa29224cb5 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -48,9 +48,9 @@ class Deprecation private $originMethod; /** - * @var string one of the PATH_TYPE_* constants + * @var string */ - private $triggeringFilePathType; + private $triggeringFile; /** @var string[] absolute paths to vendor directories */ private static $vendors; @@ -76,7 +76,7 @@ public function __construct($message, array $trace, $file) // No-op } $line = $trace[$i]; - $this->trigerringFilePathType = $this->getPathType($file); + $this->triggeringFile = $file; if (isset($line['object']) || isset($line['class'])) { if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) { $parsedMsg = unserialize($this->message); @@ -88,7 +88,7 @@ public function __construct($message, array $trace, $file) // then we need to use the serialized information to determine // if the error has been triggered from vendor code. if (isset($parsedMsg['triggering_file'])) { - $this->trigerringFilePathType = $this->getPathType($parsedMsg['triggering_file']); + $this->triggeringFile = $parsedMsg['triggering_file']; } return; @@ -169,6 +169,21 @@ public function isLegacy($utilPrefix) || \in_array('legacy', $test::getGroups($class, $method), true); } + /** + * @return bool + */ + public function isMuted() + { + if ('Function ReflectionType::__toString() is deprecated' !== $this->message) { + return false; + } + if (isset($this->trace[1]['class'])) { + return 0 === strpos($this->trace[1]['class'], 'PHPUnit\\'); + } + + return false !== strpos($this->triggeringFile, \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'phpunit'.\DIRECTORY_SEPARATOR); + } + /** * Tells whether both the calling package and the called package are vendor * packages. @@ -177,10 +192,11 @@ public function isLegacy($utilPrefix) */ public function getType() { - if (self::PATH_TYPE_SELF === $this->trigerringFilePathType) { + $triggeringFilePathType = $this->getPathType($this->triggeringFile); + if (self::PATH_TYPE_SELF === $triggeringFilePathType) { return self::TYPE_SELF; } - if (self::PATH_TYPE_UNDETERMINED === $this->trigerringFilePathType) { + if (self::PATH_TYPE_UNDETERMINED === $triggeringFilePathType) { return self::TYPE_UNDETERMINED; } $erroringFile = $erroringPackage = null; diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index 0c8708bb35f00..60b1efdfa2317 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation; class DeprecationTest extends TestCase @@ -55,6 +56,68 @@ public function testItRulesOutFilesOutsideVendorsAsIndirect() $this->assertNotSame(Deprecation::TYPE_INDIRECT, $deprecation->getType()); } + /** + * @dataProvider mutedProvider + */ + public function testItMutesOnlySpecificErrorMessagesWhenTheCallingCodeIsInPhpunit($muted, $callingClass, $message) + { + $trace = $this->debugBacktrace(); + array_unshift($trace, ['class' => $callingClass]); + array_unshift($trace, ['class' => DeprecationErrorHandler::class]); + $deprecation = new Deprecation($message, $trace, 'should_not_matter.php'); + $this->assertSame($muted, $deprecation->isMuted()); + } + + public function mutedProvider() + { + yield 'not from phpunit, and not a whitelisted message' => [ + false, + \My\Source\Code::class, + 'Self deprecating humor is deprecated by itself' + ]; + yield 'from phpunit, but not a whitelisted message' => [ + false, + \PHPUnit\Random\Piece\Of\Code::class, + 'Self deprecating humor is deprecated by itself' + ]; + yield 'whitelisted message, but not from phpunit' => [ + false, + \My\Source\Code::class, + 'Function ReflectionType::__toString() is deprecated', + ]; + yield 'from phpunit and whitelisted message' => [ + true, + \PHPUnit\Random\Piece\Of\Code::class, + 'Function ReflectionType::__toString() is deprecated', + ]; + } + + public function testNotMutedIfNotCalledFromAClassButARandomFile() + { + $deprecation = new Deprecation( + 'Function ReflectionType::__toString() is deprecated', + [ + ['file' => 'should_not_matter.php'], + ['file' => 'should_not_matter_either.php'], + ], + 'my-procedural-controller.php' + ); + $this->assertFalse($deprecation->isMuted()); + } + + public function testItTakesMutesDeprecationFromPhpUnitFiles() + { + $deprecation = new Deprecation( + 'Function ReflectionType::__toString() is deprecated', + [ + ['file' => 'should_not_matter.php'], + ['file' => 'should_not_matter_either.php'], + ], + 'random_path' . \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR . 'phpunit' . \DIRECTORY_SEPARATOR . 'whatever.php' + ); + $this->assertTrue($deprecation->isMuted()); + } + /** * This method is here to simulate the extra level from the piece of code * triggering an error to the error handler diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt index 126d23389a516..7b4625979cb0c 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -3,7 +3,9 @@ Test DeprecationErrorHandler in default mode --FILE-- true))); + $exit = proc_close(proc_open("$q$COMPOSER install --no-dev --prefer-dist --no-progress --ansi$q", array(), $p, getcwd())); putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : '')); if ($exit) { exit($exit); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index b332ff018d742..9b0ca217a8a5f 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -525,7 +525,6 @@ public function testSingleChoiceWithPreferred() /following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] ] - [count(./option)=3] ' ); } @@ -548,7 +547,6 @@ public function testSingleChoiceWithPreferredAndNoSeparator() ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] ] - [count(./option)=2] ' ); } @@ -572,7 +570,6 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() /following-sibling::option[@disabled="disabled"][not(@selected)][.=""] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] ] - [count(./option)=3] ' ); } @@ -589,7 +586,6 @@ public function testChoiceWithOnlyPreferred() $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], '/select [@class="my&class form-control"] - [count(./option)=2] ' ); } diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php index c09ed8bc8ce37..1f85a1a31696e 100644 --- a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php @@ -86,6 +86,7 @@ private function compileContainer(ContainerBuilder $container) { $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 8c877f562d38e..56be7f877781b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.3.0 ----- + * Deprecated the `framework.templating` option, configure the Twig bundle instead. * Added `WebTestAssertionsTrait` (included by default in `WebTestCase`) * Renamed `Client` to `KernelBrowser` * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index cc1b858abb337..fe0d60b5554ff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -52,6 +52,9 @@ protected function listBundles($output) } } + /** + * @return ExtensionInterface + */ protected function findExtension($name) { $bundles = $this->initializeBundles(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index b7c4fba0333fe..a21ccca8f2b9d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -225,6 +225,7 @@ protected function getContainerBuilder() $buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel)); $container = $buildContainer(); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); } else { (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 796beaee01441..18b13a215c1e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -540,7 +540,7 @@ private function formatControllerLink($controller, string $anchorText): string try { if (\is_array($controller)) { - $r = new \ReflectionMethod($controller); + $r = new \ReflectionMethod($controller[0], $controller[1]); } elseif ($controller instanceof \Closure) { $r = new \ReflectionFunction($controller); } elseif (method_exists($controller, '__invoke')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde9bc..488d56aac09c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -339,10 +339,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->defaultNull() ->end() ->arrayNode('initial_marking') - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->defaultValue([]) ->prototype('scalar')->end() ->end() @@ -525,6 +522,12 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) $rootNode ->children() ->arrayNode('session') + ->validate() + ->ifTrue(function ($v) { + return empty($v['handler_id']) && !empty($v['save_path']); + }) + ->thenInvalid('Session save path is ignored without a handler service') + ->end() ->info('session configuration') ->canBeEnabled() ->children() @@ -550,7 +553,7 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() - ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() + ->scalarNode('save_path')->end() ->integerNode('metadata_update_threshold') ->defaultValue(0) ->info('seconds to wait between 2 session metadata updates') @@ -605,6 +608,7 @@ private function addTemplatingSection(ArrayNodeDefinition $rootNode) ->arrayNode('templating') ->info('templating configuration') ->canBeEnabled() + ->setDeprecated('The "%path%.%node%" configuration is deprecated since Symfony 4.3. Configure the "twig" section provided by the Twig Bundle instead.') ->beforeNormalization() ->ifTrue(function ($v) { return false === $v || \is_array($v) && false === $v['enabled']; }) ->then(function () { return ['enabled' => false, 'engines' => false]; }) @@ -1116,6 +1120,7 @@ private function addMessengerSection(ArrayNodeDefinition $rootNode) ->end() ->children() ->arrayNode('routing') + ->normalizeKeys(false) ->useAttributeAsKey('message_class') ->beforeNormalization() ->always() @@ -1175,6 +1180,7 @@ function ($a) { ->end() ->end() ->arrayNode('transports') + ->normalizeKeys(false) ->useAttributeAsKey('name') ->arrayPrototype() ->beforeNormalization() @@ -1195,9 +1201,14 @@ function ($a) { ->end() ->arrayNode('retry_strategy') ->addDefaultsIfNotSet() - ->validate() - ->ifTrue(function ($v) { return null !== $v['service'] && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay'])); }) - ->thenInvalid('"service" cannot be used along with the other retry_strategy options.') + ->beforeNormalization() + ->always(function ($v) { + if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) { + throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.'); + } + + return $v; + }) ->end() ->children() ->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end() @@ -1217,6 +1228,7 @@ function ($a) { ->scalarNode('default_bus')->defaultNull()->end() ->arrayNode('buses') ->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]]) + ->normalizeKeys(false) ->useAttributeAsKey('name') ->arrayPrototype() ->addDefaultsIfNotSet() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a1ab515ae2148..e63d5b1affe2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -100,7 +100,6 @@ use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; -use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Stopwatch\Stopwatch; @@ -225,6 +224,10 @@ public function load(array $configs, ContainerBuilder $container) } if ($this->isConfigEnabled($container, $config['session'])) { + if (!\extension_loaded('session')) { + throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://www.php.net/session.installation for instructions.'); + } + $this->sessionConfigEnabled = true; $this->registerSessionConfiguration($config['session'], $container, $loader); if (!empty($config['test'])) { @@ -702,13 +705,10 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ } if ($validator) { - $realDefinition = (new Workflow\DefinitionBuilder($places)) - ->addTransitions(array_map(function (Reference $ref) use ($container): Workflow\Transition { - return $container->get((string) $ref); - }, $transitions)) - ->setInitialPlace($initialMarking) - ->build() - ; + $trs = array_map(function (Reference $ref) use ($container): Workflow\Transition { + return $container->get((string) $ref); + }, $transitions); + $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); $validator->validate($realDefinition, $name); } @@ -892,6 +892,11 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c // session handler (the internal callback registered with PHP session management) if (null === $config['handler_id']) { + // If the user set a save_path without using a non-default \SessionHandler, it will silently be ignored + if (isset($config['save_path'])) { + throw new LogicException('Session save path is ignored without a handler service'); + } + // Set the handler class to be null $container->getDefinition('session.storage.native')->replaceArgument(1, null); $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); @@ -899,6 +904,10 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $container->setAlias('session.handler', $config['handler_id'])->setPrivate(true); } + if (!isset($config['save_path'])) { + $config['save_path'] = ini_get('session.save_path'); + } + $container->setParameter('session.save_path', $config['save_path']); $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); @@ -1218,7 +1227,7 @@ private function registerValidationConfiguration(array $config, ContainerBuilder if (interface_exists(TranslatorInterface::class) && class_exists(LegacyTranslatorProxy::class)) { $calls = $validatorBuilder->getMethodCalls(); - $calls[1] = ['setTranslator', [new Definition(LegacyTranslatorProxy::class, [new Reference('translator')])]]; + $calls[1] = ['setTranslator', [new Definition(LegacyTranslatorProxy::class, [new Reference('translator', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])]]; $validatorBuilder->setMethodCalls($calls); } @@ -1432,10 +1441,6 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder { $loader->load('serializer.xml'); - if (!class_exists(DateIntervalNormalizer::class)) { - $container->removeDefinition('serializer.normalizer.dateinterval'); - } - if (!class_exists(ConstraintViolationListNormalizer::class)) { $container->removeDefinition('serializer.normalizer.constraint_violation_list'); } @@ -1717,7 +1722,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $transportDefinition = (new Definition(TransportInterface::class)) ->setFactory([new Reference('messenger.transport_factory'), 'createTransport']) - ->setArguments([$transport['dsn'], $transport['options'], new Reference($serializerId)]) + ->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)]) ->addTag('messenger.receiver', ['alias' => $name]) ; $container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition); @@ -1863,7 +1868,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con if (!$container->getParameter('kernel.debug')) { $propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']); - $propertyAccessDefinition->setArguments([null, null, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); + $propertyAccessDefinition->setArguments([null, 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); $propertyAccessDefinition->addTag('cache.pool', ['clearer' => 'cache.system_clearer']); $propertyAccessDefinition->addTag('monolog.logger', ['channel' => 'cache']); } else { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index a5060e2cd6b16..41264e9d1acab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -8,7 +8,7 @@ - + @@ -46,7 +46,7 @@ - + 0 @@ -56,7 +56,7 @@ - + 0 @@ -67,7 +67,7 @@ - + @@ -78,7 +78,7 @@ - + 0 @@ -90,14 +90,14 @@ - + 0 - + @@ -134,7 +134,7 @@ - + 0 diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index abd0733e0cbd3..63a61efe4bb51 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -9,6 +9,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml index a060b723b87d1..5ef47e751efd0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml @@ -15,6 +15,7 @@ + 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 04157511dc1a4..69deb492a2c84 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 @@ -33,6 +33,7 @@ + @@ -543,4 +544,8 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index 582f3951acc77..bc3fa93619f02 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -23,7 +23,7 @@ - + %validator.translation_domain% diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 3980036006b11..9cc460f749576 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -35,6 +35,9 @@ public function testDefaultConfig() ); } + /** + * @group legacy + */ public function testDoNoDuplicateDefaultFormResources() { $input = ['templating' => [ @@ -298,7 +301,6 @@ protected static function getBundleDefaultConfig() 'cookie_httponly' => true, 'cookie_samesite' => null, 'gc_probability' => 1, - 'save_path' => '%kernel.cache_dir%/sessions', 'metadata_update_threshold' => 0, ], 'request' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php new file mode 100644 index 0000000000000..74f6856c29249 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', [ + 'mailer' => [ + 'dsn' => 'smtp://example.com', + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_savepath.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_savepath.php new file mode 100644 index 0000000000000..89841bca43d51 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_savepath.php @@ -0,0 +1,8 @@ +loadFromExtension('framework', [ + 'session' => [ + 'handler_id' => null, + 'save_path' => '/some/path', + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml new file mode 100644 index 0000000000000..5faa09d36d1b0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_savepath.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_savepath.xml new file mode 100644 index 0000000000000..a9ddd8016b879 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_savepath.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml new file mode 100644 index 0000000000000..0fca3e83e0546 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml @@ -0,0 +1,3 @@ +framework: + mailer: + dsn: 'smtp://example.com' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_savepath.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_savepath.yml new file mode 100644 index 0000000000000..174ebe586947e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_savepath.yml @@ -0,0 +1,4 @@ +framework: + session: + handler_id: null + save_path: /some/path diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index fab27d4897502..427fa617ac842 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -20,7 +20,6 @@ use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\Adapter\ChainAdapter; use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\ProxyAdapter; @@ -56,7 +55,6 @@ use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Util\LegacyTranslatorProxy; -use Symfony\Component\Validator\Validation; use Symfony\Component\Workflow; abstract class FrameworkExtensionTest extends TestCase @@ -562,6 +560,14 @@ public function testNullSessionHandler() $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); } + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + */ + public function testNullSessionHandlerWithSavePath() + { + $this->createContainerFromFile('session_savepath'); + } + public function testRequest() { $container = $this->createContainerFromFile('full'); @@ -602,6 +608,9 @@ public function testTemplating() $this->assertEquals('global_hinclude_template', $container->getParameter('fragment.renderer.hinclude.global_template'), '->registerTemplatingConfiguration() registers the global hinclude.js template'); } + /** + * @group legacy + */ public function testTemplatingCanBeDisabled() { $container = $this->createContainerFromFile('templating_disabled'); @@ -695,7 +704,7 @@ public function testMessengerTransports() $this->assertEquals([new Reference('messenger.transport_factory'), 'createTransport'], $transportFactory); $this->assertCount(3, $transportArguments); $this->assertSame('amqp://localhost/%2f/messages?exchange_name=exchange_name', $transportArguments[0]); - $this->assertEquals(['queue' => ['name' => 'Queue']], $transportArguments[1]); + $this->assertEquals(['queue' => ['name' => 'Queue'], 'transport_name' => 'customised'], $transportArguments[1]); $this->assertEquals(new Reference('messenger.transport.native_php_serializer'), $transportArguments[2]); $this->assertTrue($container->hasDefinition('messenger.transport.amqp.factory')); @@ -867,6 +876,7 @@ public function testTranslatorMultipleFallbacks() } /** + * @group legacy * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testTemplatingRequiresAtLeastOneEngine() @@ -896,9 +906,9 @@ public function testValidation() $this->assertEquals([new Reference('validator.validator_factory')], $calls[0][1]); $this->assertSame('setTranslator', $calls[1][0]); if (interface_exists(TranslatorInterface::class) && class_exists(LegacyTranslatorProxy::class)) { - $this->assertEquals([new Definition(LegacyTranslatorProxy::class, [new Reference('translator')])], $calls[1][1]); + $this->assertEquals([new Definition(LegacyTranslatorProxy::class, [new Reference('translator', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])], $calls[1][1]); } else { - $this->assertEquals([new Reference('translator')], $calls[1][1]); + $this->assertEquals([new Reference('translator', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)], $calls[1][1]); } $this->assertSame('setTranslationDomain', $calls[2][0]); $this->assertSame(['%validator.translation_domain%'], $calls[2][1]); @@ -1194,10 +1204,6 @@ public function testDataUriNormalizerRegistered() public function testDateIntervalNormalizerRegistered() { - if (!class_exists(DateIntervalNormalizer::class)) { - $this->markTestSkipped('The DateIntervalNormalizer has been introduced in the Serializer Component version 3.4.'); - } - $container = $this->createContainerFromFile('full'); $definition = $container->getDefinition('serializer.normalizer.dateinterval'); @@ -1545,6 +1551,15 @@ public function testHttpClientFullDefaultOptions() ], $defaultOptions['peer_fingerprint']); } + public function testMailer(): void + { + $container = $this->createContainerFromFile('mailer'); + + $this->assertTrue($container->hasAlias('mailer')); + $this->assertTrue($container->hasDefinition('mailer.default_transport')); + $this->assertSame('smtp://example.com', $container->getDefinition('mailer.default_transport')->getArgument(0)); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new ParameterBag(array_merge([ @@ -1576,6 +1591,7 @@ protected function createContainerFromFile($file, $data = [], $resetCompilerPass if ($resetCompilerPasses) { $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); } $container->getCompilerPassConfig()->setBeforeOptimizationPasses([new LoggerPass()]); $container->getCompilerPassConfig()->setBeforeRemovingPasses([new AddConstraintValidatorsPass(), new TranslatorPass('translator.default', 'translation.reader')]); @@ -1598,6 +1614,7 @@ protected function createContainerFromClosure($closure, $data = []) $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); return $container; @@ -1658,10 +1675,6 @@ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $con $this->assertSame(DoctrineAdapter::class, $parentDefinition->getClass()); break; case 'cache.app': - if (ChainAdapter::class === $parentDefinition->getClass()) { - break; - } - // no break case 'cache.adapter.filesystem': $this->assertSame(FilesystemAdapter::class, $parentDefinition->getClass()); break; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/Controller/DefaultController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/Controller/DefaultController.php deleted file mode 100644 index c4bee6c031c89..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/Controller/DefaultController.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace TestBundle\Fabpot\FooBundle\Controller; - -/** - * DefaultController. - * - * @author Fabien Potencier - */ -class DefaultController -{ -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php deleted file mode 100644 index 17894ba34146b..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace TestBundle\Fabpot\FooBundle; - -use Symfony\Component\HttpKernel\Bundle\Bundle; - -/** - * Bundle. - * - * @author Fabien Potencier - */ -class FabpotFooBundle extends Bundle -{ - /** - * {@inheritdoc} - */ - public function getParent() - { - return 'SensioFooBundle'; - } -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php similarity index 97% rename from src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php rename to src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php index 00eda6570906d..03c6bdcbd7e11 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/WebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AbstractWebTestCase.php @@ -14,7 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Component\Filesystem\Filesystem; -class WebTestCase extends BaseWebTestCase +abstract class AbstractWebTestCase extends BaseWebTestCase { public static function assertRedirect($response, $location) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php index 51a3e7ee54247..c9ede7a9cf646 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -class AnnotatedControllerTest extends WebTestCase +class AnnotatedControllerTest extends AbstractWebTestCase { /** * @dataProvider getRoutes diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php index e4338e3746c11..e0bf9e34dfeac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AutowiringTypesTest.php @@ -19,7 +19,7 @@ use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; use Symfony\Component\Templating\EngineInterface as ComponentEngineInterface; -class AutowiringTypesTest extends WebTestCase +class AutowiringTypesTest extends AbstractWebTestCase { public function testAnnotationReaderAutowiring() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php index af57ec9ad9eca..ab3797d05d8de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -18,7 +18,7 @@ /** * @group functional */ -class CachePoolClearCommandTest extends WebTestCase +class CachePoolClearCommandTest extends AbstractWebTestCase { protected function setUp() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolListCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolListCommandTest.php index 15e7994e46002..e9b8ed5e34ee8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolListCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolListCommandTest.php @@ -18,7 +18,7 @@ /** * @group functional */ -class CachePoolListCommandTest extends WebTestCase +class CachePoolListCommandTest extends AbstractWebTestCase { protected function setUp() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php index e970e17c6ebdb..81b5d66a41270 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php @@ -16,7 +16,7 @@ use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\Exception\InvalidArgumentException; -class CachePoolsTest extends WebTestCase +class CachePoolsTest extends AbstractWebTestCase { public function testCachePools() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php index f52d48e89904f..290d9a85c1b5b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php @@ -19,7 +19,7 @@ /** * @group functional */ -class ConfigDebugCommandTest extends WebTestCase +class ConfigDebugCommandTest extends AbstractWebTestCase { private $application; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php index a4cfd6cfa9b7d..b8ac6645f6fac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php @@ -19,7 +19,7 @@ /** * @group functional */ -class ConfigDumpReferenceCommandTest extends WebTestCase +class ConfigDumpReferenceCommandTest extends AbstractWebTestCase { private $application; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php index 229f1419cfa7e..bb8fa9cfd3a35 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDebugCommandTest.php @@ -18,7 +18,7 @@ /** * @group functional */ -class ContainerDebugCommandTest extends WebTestCase +class ContainerDebugCommandTest extends AbstractWebTestCase { public function testDumpContainerIfNotExists() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php index fe0cea4d16a85..8f826714782a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ContainerDumpTest.php @@ -14,7 +14,7 @@ /** * Checks that the container compiles correctly when all the bundle features are enabled. */ -class ContainerDumpTest extends WebTestCase +class ContainerDumpTest extends AbstractWebTestCase { public function testContainerCompilationInDebug() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php index c468a2a4da70c..af6228022e5c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php @@ -17,7 +17,7 @@ /** * @group functional */ -class DebugAutowiringCommandTest extends WebTestCase +class DebugAutowiringCommandTest extends AbstractWebTestCase { public function testBasicFunctionality() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php index d3dbeb765bf6b..a4ac17238a4b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -class FragmentTest extends WebTestCase +class FragmentTest extends AbstractWebTestCase { /** * @dataProvider getConfigs diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php index 2768b59a1c3f5..ec3c47e76205c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ProfilerTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -class ProfilerTest extends WebTestCase +class ProfilerTest extends AbstractWebTestCase { /** * @dataProvider getConfigs diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php index 61669e90adbc7..d9821820c04e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php @@ -13,7 +13,7 @@ use Symfony\Component\PropertyInfo\Type; -class PropertyInfoTest extends WebTestCase +class PropertyInfoTest extends AbstractWebTestCase { public function testPhpDocPriority() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php index 73f84f842f83f..a13a4e9fc99a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php @@ -17,7 +17,7 @@ /** * @group functional */ -class RouterDebugCommandTest extends WebTestCase +class RouterDebugCommandTest extends AbstractWebTestCase { private $application; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php index 0a92fd2f91ce5..b0d774bdd55e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php @@ -14,7 +14,7 @@ /** * @author Kévin Dunglas */ -class SerializerTest extends WebTestCase +class SerializerTest extends AbstractWebTestCase { public function testDeserializeArrayOfObject() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SessionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SessionTest.php index bf1b76cdc743c..0fa8a09b4b229 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SessionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SessionTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -class SessionTest extends WebTestCase +class SessionTest extends AbstractWebTestCase { /** * Tests session attributes persist. diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.php index 9d040581db50f..d32b6b7b121c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SubRequestsTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -class SubRequestsTest extends WebTestCase +class SubRequestsTest extends AbstractWebTestCase { public function testStateAfterSubRequest() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php index baa12ab2d1675..bf4f9f8779f44 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php @@ -18,7 +18,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\UnusedPrivateService; use Symfony\Component\DependencyInjection\ContainerInterface; -class TestServiceContainerTest extends WebTestCase +class TestServiceContainerTest extends AbstractWebTestCase { public function testThatPrivateServicesAreUnavailableIfTestConfigIsDisabled() { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php index 1da49ce79c8f6..de4c293c09280 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TranslationDebugCommandTest.php @@ -17,7 +17,7 @@ /** * @group functional */ -class TranslationDebugCommandTest extends WebTestCase +class TranslationDebugCommandTest extends AbstractWebTestCase { private $application; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php index 7fb27a63ee720..2770813d8a696 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/KernelBrowserTest.php @@ -12,10 +12,10 @@ namespace Symfony\Bundle\FrameworkBundle\Tests; use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\FrameworkBundle\Tests\Functional\WebTestCase; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\AbstractWebTestCase; use Symfony\Component\HttpFoundation\Response; -class KernelBrowserTest extends WebTestCase +class KernelBrowserTest extends AbstractWebTestCase { public function testRebootKernelBetweenRequests() { diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 8182355c16603..daf4dcad1e85e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -20,6 +20,7 @@ "ext-xml": "*", "symfony/cache": "~4.3", "symfony/config": "~4.2", + "symfony/debug": "~4.0", "symfony/dependency-injection": "^4.3", "symfony/http-foundation": "^4.3", "symfony/http-kernel": "^4.3", diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index e26a56b788847..7310391d17743 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -31,6 +31,7 @@ CHANGELOG 4.1.0 ----- + * The `switch_user.stateless` firewall option is deprecated, use the `stateless` option instead. * The `logout_on_user_change` firewall option is deprecated. * deprecated `SecurityUserValueResolver`, use `Symfony\Component\Security\Http\Controller\UserValueResolver` instead. diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index 2a36e10102c27..42735a1025e1c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -39,10 +39,6 @@ final class WrappedListener implements ListenerInterface public function __construct($listener) { $this->listener = $listener; - - if (null === self::$hasVarDumper) { - self::$hasVarDumper = class_exists(ClassStub::class); - } } /** @@ -76,8 +72,25 @@ public function getWrappedListener() public function getInfo(): array { - if (null === $this->stub) { - $this->stub = self::$hasVarDumper ? new ClassStub(\get_class($this->listener)) : \get_class($this->listener); + if (null !== $this->stub) { + // no-op + } elseif (self::$hasVarDumper ?? self::$hasVarDumper = class_exists(ClassStub::class)) { + $this->stub = ClassStub::wrapCallable($this->listener); + } elseif (\is_array($this->listener)) { + $this->stub = (\is_object($this->listener[0]) ? \get_class($this->listener[0]) : $this->listener[0]).'::'.$this->listener[1]; + } elseif ($this->listener instanceof \Closure) { + $r = new \ReflectionFunction($this->listener); + if (false !== strpos($r->name, '{closure}')) { + $this->stub = 'closure'; + } elseif ($class = $r->getClosureScopeClass()) { + $this->stub = $class->name.'::'.$r->name; + } else { + $this->stub = $r->name; + } + } elseif (\is_string($this->listener)) { + $this->stub = $this->listener; + } else { + $this->stub = \get_class($this->listener).'::__invoke'; } return [ diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 4365d0432948f..4d5e6f4ae4edf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -34,7 +34,8 @@ use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Controller\UserValueResolver; -use Symfony\Component\Templating\PhpEngine; +use Symfony\Component\Templating\Helper\Helper; +use Twig\Extension\AbstractExtension; /** * SecurityExtension. @@ -100,10 +101,15 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('security.xml'); $loader->load('security_listeners.xml'); $loader->load('security_rememberme.xml'); - if (class_exists(PhpEngine::class)) { + + if (class_exists(Helper::class)) { $loader->load('templating_php.xml'); } - $loader->load('templating_twig.xml'); + + if (class_exists(AbstractExtension::class)) { + $loader->load('templating_twig.xml'); + } + $loader->load('collectors.xml'); $loader->load('guard.xml'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php index c301ead2fae1b..b7c6bcc076631 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php @@ -20,7 +20,6 @@ use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; -use Symfony\Component\VarDumper\Caster\ClassStub; /** * @group time-sensitive @@ -56,9 +55,6 @@ public function testOnKernelRequestRecordsListeners() $listeners = $firewall->getWrappedListeners(); $this->assertCount(1, $listeners); - $this->assertSame($response, $listeners[0]['response']); - $this->assertInstanceOf(ClassStub::class, $listeners[0]['stub']); - $this->assertSame(\get_class($listener), (string) $listeners[0]['stub']); - $this->assertSame(1, $listenerCalled); + $this->assertSame($listener, $listeners[0]['stub']); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php index 93d412155385f..2b8809fe2a202 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSecurityVotersPassTest.php @@ -148,10 +148,3 @@ public function testVoterMissingInterface() $compilerPass->process($container); } } - -class VoterWithoutInterface -{ - public function vote() - { - } -} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index ef318946ce66c..138b7025bf6d0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -659,6 +659,7 @@ protected function getContainer($file) $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); return $container; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php index cc8b15ffdd9ef..d0bd809579e89 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php @@ -1,6 +1,6 @@ load('merge_import.php', $container); +$this->load('merge_import.php'); $container->loadFromExtension('security', [ 'providers' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_encoder.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_encoder.php index 5fdef4b8046fa..ec0851bdfaa34 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_encoder.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/sodium_encoder.php @@ -1,6 +1,6 @@ load('container1.php', $container); +$this->load('container1.php'); $container->loadFromExtension('security', [ 'encoders' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 3145d035720fd..5d55295b5eb63 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -410,6 +410,7 @@ protected function getRawContainer() $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); return $container; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php similarity index 97% rename from src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php rename to src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php index 9bcbc0532481d..678c937f7d422 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/WebTestCase.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AbstractWebTestCase.php @@ -14,7 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Component\Filesystem\Filesystem; -class WebTestCase extends BaseWebTestCase +abstract class AbstractWebTestCase extends BaseWebTestCase { public static function assertRedirect($response, $location) { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php index 2a31f2a27a5f2..dcfd6f29e8fea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AuthenticationCommencingTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class AuthenticationCommencingTest extends WebTestCase +class AuthenticationCommencingTest extends AbstractWebTestCase { public function testAuthenticationIsCommencingIfAccessDeniedExceptionIsWrapped() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php index 31d296e48ad89..d0a6e0674802c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/AutowiringTypesTest.php @@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; -class AutowiringTypesTest extends WebTestCase +class AutowiringTypesTest extends AbstractWebTestCase { public function testAccessDecisionManagerAutowiring() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php index 98b52a5f05058..a701c8e4ea1b0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/CsrfFormLoginTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class CsrfFormLoginTest extends WebTestCase +class CsrfFormLoginTest extends AbstractWebTestCase { /** * @dataProvider getConfigs diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php index 8afedc42e44d3..77011409cfaa4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FirewallEntryPointTest.php @@ -13,7 +13,7 @@ use Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FirewallEntryPointBundle\Security\EntryPointStub; -class FirewallEntryPointTest extends WebTestCase +class FirewallEntryPointTest extends AbstractWebTestCase { public function testItUsesTheConfiguredEntryPointWhenUsingUnknownCredentials() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php index ec1722188af25..af932a3ce42b3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/FormLoginTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class FormLoginTest extends WebTestCase +class FormLoginTest extends AbstractWebTestCase { /** * @dataProvider getConfigs diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php index 6b7dca4b422f2..583e153695fed 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Kernel; -class JsonLoginLdapTest extends WebTestCase +class JsonLoginLdapTest extends AbstractWebTestCase { public function testKernelBoot() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php index c7e9e2aab71b1..2859693a17c28 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php @@ -16,7 +16,7 @@ /** * @author Kévin Dunglas */ -class JsonLoginTest extends WebTestCase +class JsonLoginTest extends AbstractWebTestCase { public function testDefaultJsonLoginSuccess() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php index c874ada34a4e3..2a45fc00de38f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LocalizedRoutesAsPathTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class LocalizedRoutesAsPathTest extends WebTestCase +class LocalizedRoutesAsPathTest extends AbstractWebTestCase { /** * @dataProvider getLocales diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php index ef417923d2df0..cb7868f3256ef 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class LogoutTest extends WebTestCase +class LogoutTest extends AbstractWebTestCase { public function testSessionLessRememberMeLogout() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php index 378ff26b381e4..d645f97f5b2d2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class MissingUserProviderTest extends WebTestCase +class MissingUserProviderTest extends AbstractWebTestCase { /** * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php index c38bc1a4bf5d3..52be5813d681d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityRoutingIntegrationTest.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\Functional; -class SecurityRoutingIntegrationTest extends WebTestCase +class SecurityRoutingIntegrationTest extends AbstractWebTestCase { /** * @dataProvider getConfigs diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php index ff687d0792716..eb83e75d8cc64 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php @@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\User; -class SecurityTest extends WebTestCase +class SecurityTest extends AbstractWebTestCase { public function testServiceIsFunctional() { diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php index ddbfd629c8fb7..31f99da2a08f2 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SwitchUserTest.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Security\Http\Firewall\SwitchUserListener; -class SwitchUserTest extends WebTestCase +class SwitchUserTest extends AbstractWebTestCase { /** * @dataProvider getTestParameters diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php index 7e90562246228..43951615e40e3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php @@ -27,7 +27,7 @@ * * @author Sarah Khalil */ -class UserPasswordEncoderCommandTest extends WebTestCase +class UserPasswordEncoderCommandTest extends AbstractWebTestCase { /** @var CommandTester */ private $passwordEncoderCommandTester; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php index d015165739324..177308277d08f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/SecurityUserValueResolverTest.php @@ -94,11 +94,3 @@ public function testIntegrationNoUser() $this->assertSame([null], $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user = null) {})); } } - -abstract class DummyUser implements UserInterface -{ -} - -abstract class DummySubUser extends DummyUser -{ -} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 1057540dccaac..f3c0bc8503f15 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -299,6 +299,7 @@ public function testRuntimeLoader() $container->register('foo', '%foo%')->addTag('twig.runtime'); $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); $loader = $container->getDefinition('twig.runtime_loader'); @@ -335,6 +336,7 @@ private function compileContainer(ContainerBuilder $container) { $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index f9a9bf709a743..cda53ad1140bb 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -18,6 +18,7 @@ "require": { "php": "^7.1.3", "symfony/config": "~4.2", + "symfony/debug": "~4.0", "symfony/twig-bridge": "^4.3", "symfony/http-foundation": "~4.3", "symfony/http-kernel": "~4.1", diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index bc40f4f8dd391..fe7b3e4efd47d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -60,7 +60,7 @@ public function showAction($token) $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); $template = $this->getTemplate(); - if (!$this->twig->getLoader()->exists($template)) { + if (!$this->templateExists($template)) { $handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat); return new Response($handler->getContent($exception), 200, ['Content-Type' => 'text/html']); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig index 4ca49e7c5ff90..14df36be2802d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig @@ -663,7 +663,7 @@ {% else %}
-

No options where passed when constructing this form.

+

No options were passed when constructing this form.

{% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index dd09415568d70..6669cd721fe73 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -54,6 +54,7 @@ text-align: left; text-transform: none; z-index: 99999; + direction: ltr; /* neutralize the aliasing defined by external CSS styles */ -webkit-font-smoothing: subpixel-antialiased; diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index 6afdd8c3a5425..5ed4d6023ef80 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -50,12 +50,15 @@ public function prune() { $time = time(); $pruned = true; + $getExpiry = true; set_error_handler($this->includeHandler); try { foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { try { - list($expiresAt) = include $file; + if (\is_array($expiresAt = include $file)) { + $expiresAt = $expiresAt[0]; + } } catch (\ErrorException $e) { $expiresAt = $time; } @@ -87,15 +90,21 @@ protected function doFetch(array $ids) $values = []; begin: + $getExpiry = false; + foreach ($ids as $id) { if (null === $value = $this->values[$id] ?? null) { $missingIds[] = $id; } elseif ('N;' === $value) { $values[$id] = null; - } elseif ($value instanceof \Closure) { - $values[$id] = $value(); - } else { + } elseif (!\is_object($value)) { $values[$id] = $value; + } elseif (!$value instanceof LazyValue) { + // calling a Closure is for @deprecated BC and should be removed in Symfony 5.0 + $values[$id] = $value(); + } elseif (false === $values[$id] = include $value->file) { + unset($values[$id], $this->values[$id]); + $missingIds[] = $id; } if (!$this->appendOnly) { unset($this->values[$id]); @@ -108,10 +117,18 @@ protected function doFetch(array $ids) set_error_handler($this->includeHandler); try { + $getExpiry = true; + foreach ($missingIds as $k => $id) { try { $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); - list($expiresAt, $this->values[$id]) = include $file; + + if (\is_array($expiresAt = include $file)) { + [$expiresAt, $this->values[$id]] = $expiresAt; + } elseif ($now < $expiresAt) { + $this->values[$id] = new LazyValue($file); + } + if ($now >= $expiresAt) { unset($this->values[$id], $missingIds[$k]); } @@ -140,7 +157,13 @@ protected function doHave($id) set_error_handler($this->includeHandler); try { $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id); - list($expiresAt, $value) = include $file; + $getExpiry = true; + + if (\is_array($expiresAt = include $file)) { + [$expiresAt, $value] = $expiresAt; + } elseif ($this->appendOnly) { + $value = new LazyValue($file); + } } catch (\ErrorException $e) { return false; } finally { @@ -189,13 +212,16 @@ protected function doSave(array $values, $lifetime) } if (!$isStaticValue) { - $value = str_replace("\n", "\n ", $value); - $value = "static function () {\n\n return {$value};\n\n}"; + // We cannot use a closure here because of https://bugs.php.net/76982 + $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); + $value = "files[$key] = $this->getFile($key, true); // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past - $ok = $this->write($file, "write($file, $value, self::$startTime - 10) && $ok; if ($allowCompile) { @opcache_invalidate($file, true); @@ -241,3 +267,16 @@ protected function doUnlink($file) return @unlink($file); } } + +/** + * @internal + */ +class LazyValue +{ + public $file; + + public function __construct($file) + { + $this->file = $file; + } +} diff --git a/src/Symfony/Component/Config/Definition/ArrayNode.php b/src/Symfony/Component/Config/Definition/ArrayNode.php index 43697ef27943c..e741b8a02de2f 100644 --- a/src/Symfony/Component/Config/Definition/ArrayNode.php +++ b/src/Symfony/Component/Config/Definition/ArrayNode.php @@ -141,7 +141,7 @@ public function setPerformDeepMerging($boolean) } /** - * Whether extra keys should just be ignore without an exception. + * Whether extra keys should just be ignored without an exception. * * @param bool $boolean To allow extra keys * @param bool $remove To remove extra keys diff --git a/src/Symfony/Component/Config/Resource/ComposerResource.php b/src/Symfony/Component/Config/Resource/ComposerResource.php index db6b04c812c57..183ffabebd9b3 100644 --- a/src/Symfony/Component/Config/Resource/ComposerResource.php +++ b/src/Symfony/Component/Config/Resource/ComposerResource.php @@ -50,7 +50,7 @@ public function isFresh($timestamp) { self::refresh(); - return self::$runtimeVendors === $this->vendors; + return array_values(self::$runtimeVendors) === array_values($this->vendors); } private static function refresh() diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php index 940b24d79b951..9633ef47c7aaa 100644 --- a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -136,7 +136,7 @@ private function generateSignature(\ReflectionClass $class) foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { yield $p->getDocComment().$p; - yield print_r($defaults[$p->name], true); + yield print_r(isset($defaults[$p->name]) ? $defaults[$p->name] : null, true); } } diff --git a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php index e22933245d252..d853d0ea5a69c 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php @@ -134,6 +134,14 @@ public function provideHashedSignature() yield [1, 13, 'protected function prot($a = [123]) {}']; yield [0, 14, '/** priv docblock */']; yield [0, 15, '']; + + if (\PHP_VERSION_ID >= 70400) { + // PHP7.4 typed properties without default value are + // undefined, make sure this doesn't throw an error + yield [1, 5, 'public array $pub;']; + yield [0, 7, 'protected int $prot;']; + yield [0, 9, 'private string $priv;']; + } } public function testEventSubscriber() diff --git a/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php b/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php index 9462996f6d2af..ca1029cb60042 100644 --- a/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php +++ b/src/Symfony/Component/Console/CommandLoader/CommandLoaderInterface.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Console\CommandLoader; use Symfony\Component\Console\Command\Command; diff --git a/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php b/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php index 753ad0fb705c2..8000c7d5eca82 100644 --- a/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php +++ b/src/Symfony/Component/Console/CommandLoader/ContainerCommandLoader.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Console\CommandLoader; use Psr\Container\ContainerInterface; diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php index 655bdd083e50e..204b46fbb8973 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyle.php @@ -77,11 +77,7 @@ public function __construct(string $foreground = null, string $background = null } /** - * Sets style foreground color. - * - * @param string|null $color The color name - * - * @throws InvalidArgumentException When the color name isn't defined + * {@inheritdoc} */ public function setForeground($color = null) { @@ -99,11 +95,7 @@ public function setForeground($color = null) } /** - * Sets style background color. - * - * @param string|null $color The color name - * - * @throws InvalidArgumentException When the color name isn't defined + * {@inheritdoc} */ public function setBackground($color = null) { @@ -126,11 +118,7 @@ public function setHref(string $url): void } /** - * Sets some specific style option. - * - * @param string $option The option name - * - * @throws InvalidArgumentException When the option name isn't defined + * {@inheritdoc} */ public function setOption($option) { @@ -144,11 +132,7 @@ public function setOption($option) } /** - * Unsets some specific style option. - * - * @param string $option The option name - * - * @throws InvalidArgumentException When the option name isn't defined + * {@inheritdoc} */ public function unsetOption($option) { @@ -175,11 +159,7 @@ public function setOptions(array $options) } /** - * Applies the style to a given text. - * - * @param string $text The text to style - * - * @return string + * {@inheritdoc} */ public function apply($text) { diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php index 4c7dc4134d723..af171c27020c9 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php @@ -21,7 +21,7 @@ interface OutputFormatterStyleInterface /** * Sets style foreground color. * - * @param string $color The color name + * @param string|null $color The color name */ public function setForeground($color = null); diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index e30fe786e3ab6..cb2bc21379c54 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -248,7 +248,7 @@ public function setRedrawFrequency(int $freq) * * @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable */ - public function iterate(iterable $iterable, ?int $max = null): iterable + public function iterate(iterable $iterable, int $max = null): iterable { $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); diff --git a/src/Symfony/Component/Console/Question/ChoiceQuestion.php b/src/Symfony/Component/Console/Question/ChoiceQuestion.php index 612e406a8ed7f..44b70abf11928 100644 --- a/src/Symfony/Component/Console/Question/ChoiceQuestion.php +++ b/src/Symfony/Component/Console/Question/ChoiceQuestion.php @@ -129,17 +129,15 @@ private function getDefaultValidator(): callable $isAssoc = $this->isAssoc($choices); return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { - // Collapse all spaces. - $selectedChoices = str_replace(' ', '', $selected); - if ($multiselect) { // Check for a separated comma values - if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) { + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) { throw new InvalidArgumentException(sprintf($errorMessage, $selected)); } - $selectedChoices = explode(',', $selectedChoices); + + $selectedChoices = array_map('trim', explode(',', $selected)); } else { - $selectedChoices = [$selected]; + $selectedChoices = [trim($selected)]; } $multiselectChoices = []; diff --git a/src/Symfony/Component/Console/Tests/Question/ChoiceQuestionTest.php b/src/Symfony/Component/Console/Tests/Question/ChoiceQuestionTest.php new file mode 100644 index 0000000000000..5ec7a9cacb4de --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Question/ChoiceQuestionTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Question; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Question\ChoiceQuestion; + +class ChoiceQuestionTest extends TestCase +{ + /** + * @dataProvider selectUseCases + */ + public function testSelectUseCases($multiSelect, $answers, $expected, $message) + { + $question = new ChoiceQuestion('A question', [ + 'First response', + 'Second response', + 'Third response', + 'Fourth response', + ]); + + $question->setMultiselect($multiSelect); + + foreach ($answers as $answer) { + $validator = $question->getValidator(); + $actual = $validator($answer); + + $this->assertEquals($actual, $expected, $message); + } + } + + public function selectUseCases() + { + return [ + [ + false, + ['First response', 'First response ', ' First response', ' First response '], + 'First response', + 'When passed single answer on singleSelect, the defaultValidator must return this answer as a string', + ], + [ + true, + ['First response', 'First response ', ' First response', ' First response '], + ['First response'], + 'When passed single answer on MultiSelect, the defaultValidator must return this answer as an array', + ], + [ + true, + ['First response,Second response', ' First response , Second response '], + ['First response', 'Second response'], + 'When passed multiple answers on MultiSelect, the defaultValidator must return these answers as an array', + ], + ]; + } +} diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index ff9a8d72f903f..463a43e2ae593 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -153,11 +153,11 @@ public function loadClass($class) if (!$file = $this->classLoader[0]->findFile($class) ?: false) { // no-op } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { - require $file; + include $file; return; - } else { - require $file; + } elseif (false === include $file) { + return; } } else { ($this->classLoader)($class); diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index f758d21e0e989..9ceeb0f097e74 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -70,8 +70,8 @@ public function testRegister() public function testErrorGetLast() { - $handler = ErrorHandler::register(); $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $handler = ErrorHandler::register(); $handler->setDefaultLogger($logger); $handler->screamAt(E_ALL); @@ -143,9 +143,8 @@ public function testConstruct() public function testDefaultLogger() { try { - $handler = ErrorHandler::register(); - $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $handler = ErrorHandler::register(); $handler->setDefaultLogger($logger, E_NOTICE); $handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]); @@ -331,12 +330,11 @@ public function testHandleDeprecation() public function testHandleException() { try { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $handler = ErrorHandler::register(); $exception = new \Exception('foo'); - $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); - $logArgCheck = function ($level, $message, $context) { $this->assertSame('Uncaught Exception: foo', $message); $this->assertArrayHasKey('exception', $context); @@ -442,6 +440,7 @@ public function testSettingLoggerWhenExceptionIsBuffered() public function testHandleFatalError() { try { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $handler = ErrorHandler::register(); $error = [ @@ -451,8 +450,6 @@ public function testHandleFatalError() 'line' => 123, ]; - $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); - $logArgCheck = function ($level, $message, $context) { $this->assertEquals('Fatal Parse Error: foo', $message); $this->assertArrayHasKey('exception', $context); diff --git a/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php index 4910fe5ec96be..3a9af8b6b8c1d 100644 --- a/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php @@ -85,7 +85,7 @@ public function testHeaders() ob_start(); $handler->sendPhpResponse(new MethodNotAllowedHttpException(['POST'])); - $response = ob_get_clean(); + ob_get_clean(); $expectedHeaders = [ ['HTTP/1.0 405', true, null], @@ -108,35 +108,65 @@ public function testNestedExceptions() public function testHandle() { - $exception = new \Exception('foo'); + $handler = new ExceptionHandler(true); + ob_start(); - $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock(); - $handler - ->expects($this->exactly(2)) - ->method('sendPhpResponse'); + $handler->handle(new \Exception('foo')); - $handler->handle($exception); + $this->assertThatTheExceptionWasOutput(ob_get_clean(), \Exception::class, 'Exception', 'foo'); + } - $handler->setHandler(function ($e) use ($exception) { - $this->assertSame($exception, $e); + public function testHandleWithACustomHandlerThatOutputsSomething() + { + $handler = new ExceptionHandler(true); + ob_start(); + $handler->setHandler(function () { + echo 'ccc'; }); - $handler->handle($exception); + $handler->handle(new \Exception()); + ob_end_flush(); // Necessary because of this PHP bug : https://bugs.php.net/bug.php?id=76563 + $this->assertSame('ccc', ob_get_clean()); } - public function testHandleOutOfMemoryException() + public function testHandleWithACustomHandlerThatOutputsNothing() + { + $handler = new ExceptionHandler(true); + $handler->setHandler(function () {}); + + $handler->handle(new \Exception('ccc')); + + $this->assertThatTheExceptionWasOutput(ob_get_clean(), \Exception::class, 'Exception', 'ccc'); + } + + public function testHandleWithACustomHandlerThatFails() { - $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__); + $handler = new ExceptionHandler(true); + $handler->setHandler(function () { + throw new \RuntimeException(); + }); - $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock(); - $handler - ->expects($this->once()) - ->method('sendPhpResponse'); + $handler->handle(new \Exception('ccc')); - $handler->setHandler(function ($e) { + $this->assertThatTheExceptionWasOutput(ob_get_clean(), \Exception::class, 'Exception', 'ccc'); + } + + public function testHandleOutOfMemoryException() + { + $handler = new ExceptionHandler(true); + ob_start(); + $handler->setHandler(function () { $this->fail('OutOfMemoryException should bypass the handler'); }); - $handler->handle($exception); + $handler->handle(new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__)); + + $this->assertThatTheExceptionWasOutput(ob_get_clean(), OutOfMemoryException::class, 'OutOfMemoryException', 'foo'); + } + + private function assertThatTheExceptionWasOutput($content, $expectedClass, $expectedTitle, $expectedMessage) + { + $this->assertContains(sprintf('%s', $expectedClass, $expectedTitle), $content); + $this->assertContains(sprintf('

%s

', $expectedMessage), $content); } } diff --git a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php index 8e615ac640be9..9a56b3b4ec8fc 100644 --- a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php @@ -61,7 +61,7 @@ public function testHandleClassNotFound($error, $translatedMessage, $autoloader } $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); - $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertRegExp($translatedMessage, $exception->getMessage()); $this->assertSame($error['type'], $exception->getSeverity()); $this->assertSame($error['file'], $exception->getFile()); $this->assertSame($error['line'], $exception->getLine()); @@ -71,6 +71,7 @@ public function provideClassNotFoundData() { $autoloader = new ComposerClassLoader(); $autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception')); + $autoloader->add('Symfony_Component_Debug_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures')); $debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']); @@ -82,7 +83,7 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'WhizBangFactory\' not found', ], - "Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?", + "/^Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement\?$/", ], [ [ @@ -91,7 +92,7 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found', ], - "Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + "/^Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for another namespace\?$/", ], [ [ @@ -100,7 +101,8 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'UndefinedFunctionException\' not found', ], - "Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + "/^Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for .*\"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?$/", + [$debugClassLoader, 'loadClass'], ], [ [ @@ -109,7 +111,8 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'PEARClass\' not found', ], - "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?", + "/^Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"\?$/", + [$debugClassLoader, 'loadClass'], ], [ [ @@ -118,7 +121,8 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', ], - "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + "/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for .*\"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?$/", + [$debugClassLoader, 'loadClass'], ], [ [ @@ -127,7 +131,7 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', ], - "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + "/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for \"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?$/", [$autoloader, 'loadClass'], ], [ @@ -137,7 +141,7 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', ], - "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + "/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for \"Symfony\\\\Component\\\\Debug\\\\Exception\\\\UndefinedFunctionException\"\?/", [$debugClassLoader, 'loadClass'], ], [ @@ -147,7 +151,7 @@ public function provideClassNotFoundData() 'file' => 'foo.php', 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', ], - "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + "/^Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\\\Bar\".\nDid you forget a \"use\" statement for another namespace\?$/", function ($className) { /* do nothing here */ }, ], ]; diff --git a/src/Symfony/Component/Debug/Tests/MockExceptionHandler.php b/src/Symfony/Component/Debug/Tests/MockExceptionHandler.php deleted file mode 100644 index 2d6ce564d23a4..0000000000000 --- a/src/Symfony/Component/Debug/Tests/MockExceptionHandler.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\Tests; - -use Symfony\Component\Debug\ExceptionHandler; - -class MockExceptionHandler extends ExceptionHandler -{ - public $e; - - public function handle(\Exception $e) - { - $this->e = $e; - } -} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index d10ee4575bfe8..be94ffe92b454 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -34,7 +34,6 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe private $onlyConstructorArguments; private $hasProxyDumper; private $lazy; - private $expressionLanguage; private $byConstructor; private $definitions; private $aliases; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 3ae264f5c8bc5..ced4d827dc34d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -37,6 +37,7 @@ class AutowirePass extends AbstractRecursivePass private $getPreviousValue; private $decoratedMethodIndex; private $decoratedMethodArgumentIndex; + private $typesClone; public function __construct(bool $throwOnAutowireException = true) { @@ -49,16 +50,16 @@ public function __construct(bool $throwOnAutowireException = true) public function process(ContainerBuilder $container) { try { + $this->typesClone = clone $this; parent::process($container); } finally { - $this->types = null; - $this->ambiguousServiceTypes = null; $this->decoratedClass = null; $this->decoratedId = null; $this->methodCalls = null; $this->getPreviousValue = null; $this->decoratedMethodIndex = null; $this->decoratedMethodArgumentIndex = null; + $this->typesClone = null; } } @@ -375,20 +376,22 @@ private function set(string $type, string $id) private function createTypeNotFoundMessageCallback(TypedReference $reference, $label) { - $container = new ContainerBuilder($this->container->getParameterBag()); - $container->setAliases($this->container->getAliases()); - $container->setDefinitions($this->container->getDefinitions()); - $container->setResourceTracking(false); + if (null === $this->typesClone->container) { + $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag()); + $this->typesClone->container->setAliases($this->container->getAliases()); + $this->typesClone->container->setDefinitions($this->container->getDefinitions()); + $this->typesClone->container->setResourceTracking(false); + } $currentId = $this->currentId; - return function () use ($container, $reference, $label, $currentId) { - return $this->createTypeNotFoundMessage($container, $reference, $label, $currentId); - }; + return (function () use ($reference, $label, $currentId) { + return $this->createTypeNotFoundMessage($reference, $label, $currentId); + })->bindTo($this->typesClone); } - private function createTypeNotFoundMessage(ContainerBuilder $container, TypedReference $reference, $label, string $currentId) + private function createTypeNotFoundMessage(TypedReference $reference, $label, string $currentId) { - if (!$r = $container->getReflectionClass($type = $reference->getType(), false)) { + if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { // either $type does not exist or a parent class does not exist try { $resource = new ClassExistenceResource($type, false); @@ -401,8 +404,8 @@ private function createTypeNotFoundMessage(ContainerBuilder $container, TypedRef $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); } else { - $alternatives = $this->createTypeAlternatives($container, $reference); - $message = $container->has($type) ? 'this service is abstract' : 'no such service exists'; + $alternatives = $this->createTypeAlternatives($this->container, $reference); + $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); if ($r->isInterface() && !$alternatives) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php index 822dfe795c9b7..76e6d35a795d4 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php @@ -200,6 +200,10 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs $bag = $this->getParameterBag(); $value = $bag->resolveValue($value); + if (!$bag instanceof EnvPlaceholderParameterBag) { + return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); + } + foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { if (false === strpos($env, ':')) { continue; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php index f0b9bcfe207e4..fec142426e058 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php @@ -81,7 +81,7 @@ public function getId() /** * Returns the in edges. * - * @return array The in ServiceReferenceGraphEdge array + * @return ServiceReferenceGraphEdge[] */ public function getInEdges() { @@ -91,7 +91,7 @@ public function getInEdges() /** * Returns the out edges. * - * @return array The out ServiceReferenceGraphEdge array + * @return ServiceReferenceGraphEdge[] */ public function getOutEdges() { diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 9d16f9c27c4a8..740150bf2aa61 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -320,11 +320,11 @@ public function reset() /** * Gets all service ids. * - * @return array An array of all defined service ids + * @return string[] An array of all defined service ids */ public function getServiceIds() { - return array_unique(array_merge(['service_container'], array_keys($this->fileMap), array_keys($this->methodMap), array_keys($this->services))); + return array_map('strval', array_unique(array_merge(['service_container'], array_keys($this->fileMap), array_keys($this->methodMap), array_keys($this->services)))); } /** diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index e2638dc74991b..ca2de2dbcb49f 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -781,13 +781,11 @@ public function compile(bool $resolveEnvPlaceholders = false) } /** - * Gets all service ids. - * - * @return array An array of all defined service ids + * {@inheritdoc} */ public function getServiceIds() { - return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds())); + return array_map('strval', array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds()))); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 703620c8a67e3..2da8347a4bba0 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -736,7 +736,7 @@ private function resolveServices($value, $file, $isParameter = false) throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', $value->getTag(), implode('"", "', $diff))); } - $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'], $argument['default_index_method'] ?? null, $forLocator); + $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator); if ($forLocator) { $argument = new ServiceLocatorArgument($argument); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index e5f08e4f62c21..fd8fc3e6653c8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -41,6 +41,7 @@ use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; +use Symfony\Component\DependencyInjection\Tests\Fixtures\ScalarFactory; use Symfony\Component\DependencyInjection\Tests\Fixtures\SimilarArgumentsDummy; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\ExpressionLanguage\Expression; @@ -1601,6 +1602,20 @@ public function testDecoratedSelfReferenceInvolvingPrivateServices() $this->assertSame(['service_container'], array_keys($container->getDefinitions())); } + public function testScalarService() + { + $c = new ContainerBuilder(); + $c->register('foo', 'string') + ->setPublic(true) + ->setFactory([ScalarFactory::class, 'getSomeValue']) + ; + + $c->compile(); + + $this->assertTrue($c->has('foo')); + $this->assertSame('some value', $c->get('foo')); + } + public function testWither() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index e81bf5636fc82..47e59ab7143c1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -295,6 +295,16 @@ public function testHas() $this->assertTrue($sc->has('foo.baz'), '->has() returns true if a get*Method() is defined'); } + public function testScalarService() + { + $c = new Container(); + + $c->set('foo', 'some value'); + + $this->assertTrue($c->has('foo')); + $this->assertSame('some value', $c->get('foo')); + } + public function testInitialized() { $sc = new ProjectServiceContainer(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 56d29c9f9c671..b8686ba2fbc5f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -33,6 +33,7 @@ use Symfony\Component\DependencyInjection\Tests\Compiler\Foo; use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; +use Symfony\Component\DependencyInjection\Tests\Fixtures\ScalarFactory; use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator; use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber; use Symfony\Component\DependencyInjection\TypedReference; @@ -1226,6 +1227,25 @@ public function testServiceLocatorArgument() $this->assertSame($foo5, $locator->get('foo5')); } + public function testScalarService() + { + $container = new ContainerBuilder(); + $container->register('foo', 'string') + ->setPublic(true) + ->setFactory([ScalarFactory::class, 'getSomeValue']) + ; + + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Scalar_Service'])); + + $container = new \Symfony_DI_PhpDumper_Test_Scalar_Service(); + + $this->assertTrue($container->has('foo')); + $this->assertSame('some value', $container->get('foo')); + } + public function testWither() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/OtherDir/Component1/Dir3/Service3.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/OtherDir/Component1/Dir3/Service3.php deleted file mode 100644 index ee6498c9d50c5..0000000000000 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/OtherDir/Component1/Dir3/Service3.php +++ /dev/null @@ -1,8 +0,0 @@ -assertSame('overridden', $container->get('bar')->quz); } + + /** + * When creating a tagged iterator using the array syntax, all optional parameters should be properly handled. + */ + public function testDefaultValueOfTagged() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('tagged_iterator_optional.yml'); + + $iteratorArgument = $container->getDefinition('iterator_service')->getArgument(0); + $this->assertInstanceOf(TaggedIteratorArgument::class, $iteratorArgument); + $this->assertNull($iteratorArgument->getIndexAttribute()); + } } diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php index 304caec1d1a79..a5f0b7eda5433 100644 --- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -54,15 +54,13 @@ public function dispatch($event/*, string $eventName = null*/) if (\is_object($event)) { $eventName = $eventName ?? \get_class($event); - } else { - @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED); + } elseif (\is_string($event) && (null === $eventName || $eventName instanceof Event)) { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), E_USER_DEPRECATED); $swap = $event; $event = $eventName ?? new Event(); $eventName = $swap; - - if (!$event instanceof Event) { - throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event))); - } + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, %s given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); } if (null !== $this->optimized && null !== $eventName) { diff --git a/src/Symfony/Component/EventDispatcher/LegacyEventDispatcherProxy.php b/src/Symfony/Component/EventDispatcher/LegacyEventDispatcherProxy.php index 534eec98a5b63..afa3e988d0057 100644 --- a/src/Symfony/Component/EventDispatcher/LegacyEventDispatcherProxy.php +++ b/src/Symfony/Component/EventDispatcher/LegacyEventDispatcherProxy.php @@ -16,7 +16,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; /** - * An helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). + * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). * * This class should be deprecated in Symfony 5.1 * @@ -57,15 +57,13 @@ public function dispatch($event/*, string $eventName = null*/) if (\is_object($event)) { $eventName = $eventName ?? \get_class($event); - } else { - @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', ContractsEventDispatcherInterface::class), E_USER_DEPRECATED); + } elseif (\is_string($event) && (null === $eventName || $eventName instanceof Event)) { + @trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', ContractsEventDispatcherInterface::class), E_USER_DEPRECATED); $swap = $event; $event = $eventName ?? new Event(); $eventName = $swap; - - if (!$event instanceof Event) { - throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', ContractsEventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event))); - } + } else { + throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, %s given.', ContractsEventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event))); } $listeners = $this->getListeners($eventName); diff --git a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php index e89f78cda8e89..658a94123933e 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php @@ -412,6 +412,33 @@ public function testMutatingWhilePropagationIsStopped() $this->assertTrue($testLoaded); } + + /** + * @group legacy + * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. + */ + public function testLegacySignatureWithoutEvent() + { + $this->dispatcher->dispatch('foo'); + } + + /** + * @group legacy + * @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. + */ + public function testLegacySignatureWithEvent() + { + $this->dispatcher->dispatch('foo', new Event()); + } + + /** + * @expectedException \TypeError + * @expectedExceptionMessage Argument 1 passed to "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" must be an object, string given. + */ + public function testLegacySignatureWithNewEventObject() + { + $this->dispatcher->dispatch('foo', new ContractsEvent()); + } } class CallableClass diff --git a/src/Symfony/Component/EventDispatcher/Tests/LegacyEventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/LegacyEventDispatcherTest.php index 49aa2f9ff3f92..ffa767cf8b45c 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/LegacyEventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/LegacyEventDispatcherTest.php @@ -14,12 +14,42 @@ use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy; +use Symfony\Contracts\EventDispatcher\Event as ContractsEvent; /** * @group legacy */ class LegacyEventDispatcherTest extends EventDispatcherTest { + /** + * @group legacy + * @expectedDeprecation The signature of the "Symfony\Component\EventDispatcher\Tests\TestLegacyEventDispatcher::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3. + * @expectedDeprecation Calling the "Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. + */ + public function testLegacySignatureWithoutEvent() + { + $this->createEventDispatcher()->dispatch('foo'); + } + + /** + * @group legacy + * @expectedDeprecation The signature of the "Symfony\Component\EventDispatcher\Tests\TestLegacyEventDispatcher::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3. + * @expectedDeprecation Calling the "Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead. + */ + public function testLegacySignatureWithEvent() + { + $this->createEventDispatcher()->dispatch('foo', new Event()); + } + + /** + * @expectedException \TypeError + * @expectedExceptionMessage Argument 1 passed to "Symfony\Contracts\EventDispatcher\EventDispatcherInterface::dispatch()" must be an object, string given. + */ + public function testLegacySignatureWithNewEventObject() + { + $this->createEventDispatcher()->dispatch('foo', new ContractsEvent()); + } + protected function createEventDispatcher() { return LegacyEventDispatcherProxy::decorate(new TestLegacyEventDispatcher()); diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 2fdf21ae4a0b3..3a0c3105ad0a9 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -582,7 +582,7 @@ public function ignoreUnreadableDirs($ignore = true) /** * Searches files and directories which match defined rules. * - * @param string|array $dirs A directory path or an array of directories + * @param string|string[] $dirs A directory path or an array of directories * * @return $this * diff --git a/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php b/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php index 3c5a3f0e7ca38..eb37f1310e428 100644 --- a/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -25,7 +25,7 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi /** * @param \Iterator $iterator The Iterator to filter - * @param array $directories An array of directories to exclude + * @param string[] $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index aba3b0fae2a5f..64743c9513b96 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -146,9 +146,9 @@ class Form implements \IteratorAggregate, FormInterface, ClearableErrorsInterfac private $lockSetData = false; /** - * @var string|int|null + * @var string */ - private $name; + private $name = ''; /** * @var bool Whether the form inherits its underlying data from its parent @@ -217,7 +217,7 @@ public function getPropertyPath() return $this->propertyPath; } - if (null === $this->name || '' === $this->name) { + if ('' === $this->name) { return null; } diff --git a/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf b/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf index 468d2f6fdd8dd..1c784c24a0e2d 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.fa.xlf @@ -4,15 +4,15 @@ This form should not contain extra fields. - این فرم نباید فیلد اضافی داشته باشد. + این فرم نباید شامل فیلد اضافه ای باشد. The uploaded file was too large. Please try to upload a smaller file. - فایل بارگذاری شده بسیار بزرگ است. لطفا فایل کوچکتری را بارگزاری کنید. + فایل بارگذاری شده بسیار بزرگ می باشد. لطفا فایل کوچکتری را بارگذاری نمایید. The CSRF token is invalid. Please try to resubmit the form. - مقدار CSRF نامعتبر است. لطفا فرم را مجددا ارسال فرمایید.. + توکن CSRF نامعتبر می باشد. لطفا فرم را مجددا ارسال نمایید. diff --git a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php index 28e1fde10f126..5d40c270b073e 100644 --- a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php +++ b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php @@ -18,7 +18,6 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; -use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Command\DebugCommand; use Symfony\Component\Form\DependencyInjection\FormPass; @@ -418,14 +417,6 @@ private function createContainerBuilder() } } -class FormPassTest_Type1 extends AbstractType -{ -} - -class FormPassTest_Type2 extends AbstractType -{ -} - class Type1TypeExtension extends AbstractTypeExtension { public static function getExtendedTypes(): iterable diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 199a3b4ea17f7..c235ddcedd386 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -299,6 +299,12 @@ public function __destruct() if (\defined('CURLMOPT_PUSHFUNCTION')) { curl_multi_setopt($this->multi->handle, CURLMOPT_PUSHFUNCTION, null); } + + while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)); + + foreach ($this->multi->openHandles as $ch) { + curl_setopt($ch, CURLOPT_VERBOSE, false); + } } private static function handlePush($parent, $pushed, array $requestHeaders, CurlClientState $multi, int $maxPendingPushes, ?LoggerInterface $logger): int diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index a438b7ce94ee4..9ec7a0849028b 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -65,9 +65,15 @@ public function __construct(HttpClientInterface $client = null, ResponseFactoryI public function sendRequest(RequestInterface $request): ResponseInterface { try { + $body = $request->getBody(); + + if ($body->isSeekable()) { + $body->seek(0); + } + $response = $this->client->request($request->getMethod(), (string) $request->getUri(), [ 'headers' => $request->getHeaders(), - 'body' => (string) $request->getBody(), + 'body' => $body->getContents(), 'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null, ]); @@ -79,7 +85,13 @@ public function sendRequest(RequestInterface $request): ResponseInterface } } - return $psrResponse->withBody($this->streamFactory->createStream($response->getContent(false))); + $body = $this->streamFactory->createStream($response->getContent(false)); + + if ($body->isSeekable()) { + $body->seek(0); + } + + return $psrResponse->withBody($body); } catch (TransportExceptionInterface $e) { if ($e instanceof \InvalidArgumentException) { throw new Psr18RequestException($e, $request); diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 47fc084d376f3..90bb0df339672 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -78,6 +78,15 @@ public function getInfo(string $type = null) return null !== $type ? $this->info[$type] ?? null : $this->info; } + /** + * {@inheritdoc} + */ + public function cancel(): void + { + $this->info['error'] = 'Response has been canceled.'; + $this->body = null; + } + /** * {@inheritdoc} */ @@ -150,8 +159,11 @@ protected static function perform(ClientState $multi, array &$responses): void foreach ($responses as $response) { $id = $response->id; - if (!$response->body) { - // Last chunk + if (null === $response->body) { + // Canceled response + $response->body = []; + } elseif ([] === $response->body) { + // Error chunk $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; } elseif (null === $chunk = array_shift($response->body)) { @@ -242,7 +254,7 @@ private static function readResponse(self $response, array $options, ResponseInt // populate info related to headers $info = $mock->getInfo() ?: []; - $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode(false) ?: 200; + $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); $dlSize = isset($response->headers['content-encoding']) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index cd444439926a7..bf7ba3a746db2 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -327,7 +327,7 @@ public static function stream(iterable $responses, float $timeout = null): \Gene unset($multi->handlesActivity[$j]); - if ($chunk instanceof FirstChunk && null === $response->initializer) { + if ($chunk instanceof FirstChunk && null === $response->initializer && null === $response->info['error']) { // Ensure the HTTP status code is always checked $response->getHeaders(true); } elseif ($chunk instanceof ErrorChunk && !$chunk->didThrow()) { diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index fffe2ab81ec0e..27d5f43b6707f 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -913,8 +913,8 @@ public function getPort() $pos = strrpos($host, ':'); } - if (false !== $pos) { - return (int) substr($host, $pos + 1); + if (false !== $pos && $port = substr($host, $pos + 1)) { + return (int) $port; } return 'https' === $this->getScheme() ? 443 : 80; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 904dc1b523d23..4efaf412a3bef 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -17,7 +17,7 @@ * @author Markus Bachmann * * @see https://packagist.org/packages/mongodb/mongodb - * @see http://php.net/manual/en/set.mongodb.php + * @see https://php.net/mongodb */ class MongoDbSessionHandler extends AbstractSessionHandler { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php index f962965a82a6d..fbc25c3ef3217 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -23,7 +23,7 @@ class NativeFileSessionHandler extends \SessionHandler * Default null will leave setting as defined by PHP. * '/path', 'N;/path', or 'N;octal-mode;/path * - * @see http://php.net/session.configuration.php#ini.session.save-path for further details. + * @see https://php.net/manual/session.configuration.php#ini.session.save-path for further details. * * @throws \InvalidArgumentException On invalid $savePath * @throws \RuntimeException When failing to create the save directory diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index ce7027954e24d..9e03bfa41be63 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -103,6 +103,10 @@ class NativeSessionStorage implements SessionStorageInterface */ public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null) { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + $options += [ 'cache_limiter' => '', 'cache_expire' => 0, diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php index 662ed5015adec..8969e609aaf95 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php @@ -24,6 +24,10 @@ class PhpBridgeSessionStorage extends NativeSessionStorage */ public function __construct($handler = null, MetadataBag $metaBag = null) { + if (!\extension_loaded('session')) { + throw new \LogicException('PHP extension "session" is required.'); + } + $this->setMetadataBag($metaBag); $this->setSaveHandler($handler); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index ab0dcf6818168..20883221d3585 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -2303,6 +2303,18 @@ public function testTrustedPort() $this->assertSame(443, $request->getPort()); } + + public function testTrustedPortDoesNotDefaultToZero() + { + Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_ALL); + + $request = Request::create('/'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('X-Forwarded-Host', 'test.example.com'); + $request->headers->set('X-Forwarded-Port', ''); + + $this->assertSame(80, $request->getPort()); + } } class RequestContentProxy extends Request diff --git a/src/Symfony/Component/HttpFoundation/UrlHelper.php b/src/Symfony/Component/HttpFoundation/UrlHelper.php index 3c06e9321734f..f114c0a9fb838 100644 --- a/src/Symfony/Component/HttpFoundation/UrlHelper.php +++ b/src/Symfony/Component/HttpFoundation/UrlHelper.php @@ -23,7 +23,7 @@ final class UrlHelper private $requestStack; private $requestContext; - public function __construct(RequestStack $requestStack, ?RequestContext $requestContext = null) + public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) { $this->requestStack = $requestStack; $this->requestContext = $requestContext; diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php index dd527708bee0f..f28fbd60cc952 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -50,10 +50,9 @@ public function enableOnlyOptionalWarmers() */ public function warmUp($cacheDir) { - if ($this->debug) { + if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { $collectedLogs = []; - $previousHandler = \defined('PHPUNIT_COMPOSER_INSTALL'); - $previousHandler = $previousHandler ?: set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; } @@ -96,7 +95,7 @@ public function warmUp($cacheDir) $warmer->warmUp($cacheDir); } } finally { - if ($this->debug && true !== $previousHandler) { + if ($collectDeprecations) { restore_error_handler(); if (file_exists($this->deprecationLogsFilepath)) { diff --git a/src/Symfony/Component/HttpKernel/Client.php b/src/Symfony/Component/HttpKernel/Client.php index 66ce0649f06ca..d4ae5f8e65073 100644 --- a/src/Symfony/Component/HttpKernel/Client.php +++ b/src/Symfony/Component/HttpKernel/Client.php @@ -23,6 +23,9 @@ /** * Client simulates a browser and makes requests to an HttpKernel instance. * + * @method Request getRequest() A Request instance + * @method Response getResponse() A Response instance + * * @deprecated since Symfony 4.3, use HttpKernelBrowser instead. */ class Client extends AbstractBrowser diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index 4091b6760f4d1..405a951526bde 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -149,7 +149,7 @@ private function getContainerDeprecationLogs() return $logs; } - private function getContainerCompilerLogs(?string $compilerLogsFilepath = null): array + private function getContainerCompilerLogs(string $compilerLogsFilepath = null): array { if (!file_exists($compilerLogsFilepath)) { return []; diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php index 325b8cbc0d569..fb8e67e7ed492 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php @@ -63,7 +63,7 @@ public static function getSubscribedEvents() ]; } - private function setLocale(string $locale, ?string $defaultLocale = null): void + private function setLocale(string $locale, string $defaultLocale = null): void { foreach ($this->localeAwareServices as $service) { try { diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 4752759004acf..65476f80e39f6 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 $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.3.2'; - const VERSION_ID = 40302; + const VERSION = '4.3.3'; + const VERSION_ID = 40303; const MAJOR_VERSION = 4; const MINOR_VERSION = 3; - const RELEASE_VERSION = 2; + const RELEASE_VERSION = 3; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2020'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php index bd70de9c3b9c8..eb16d6e988f1d 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -26,7 +26,7 @@ class TraceableEventDispatcherTest extends TestCase public function testStopwatchSections() { $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); - $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response('', 200, ['X-Debug-Token' => '292e1e']); }); $request = Request::create('/'); $response = $kernel->handle($request); $kernel->terminate($request, $response); diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php deleted file mode 100644 index c8bfd36e662f5..0000000000000 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle; - -use Symfony\Component\HttpKernel\Bundle\Bundle; - -class ExtensionAbsentBundle extends Bundle -{ -} diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php deleted file mode 100644 index b43bc665a843e..0000000000000 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle\DependencyInjection; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\Extension; - -class ExtensionLoadedExtension extends Extension -{ - public function load(array $configs, ContainerBuilder $container) - { - } -} diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php deleted file mode 100644 index 3af81cb07396d..0000000000000 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle; - -use Symfony\Component\HttpKernel\Bundle\Bundle; - -class ExtensionLoadedBundle extends Bundle -{ -} diff --git a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php b/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php deleted file mode 100644 index 977976b75f88b..0000000000000 --- a/src/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command; - -use Symfony\Component\Console\Command\Command; - -class FooCommand extends Command -{ - protected function configure() - { - $this->setName('foo'); - } -} diff --git a/src/Symfony/Component/Inflector/Inflector.php b/src/Symfony/Component/Inflector/Inflector.php index 15198293cf535..58399b6322c54 100644 --- a/src/Symfony/Component/Inflector/Inflector.php +++ b/src/Symfony/Component/Inflector/Inflector.php @@ -228,6 +228,9 @@ final class Inflector // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) ['noi', 3, true, true, 'ions'], + // seasons (season), treasons (treason), poisons (poison), lessons (lesson) + ['nos', 3, true, true, 'sons'], + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) ['no', 2, true, true, 'a'], diff --git a/src/Symfony/Component/Inflector/Tests/InflectorTest.php b/src/Symfony/Component/Inflector/Tests/InflectorTest.php index 1178edf35b5bc..38df846ba667f 100644 --- a/src/Symfony/Component/Inflector/Tests/InflectorTest.php +++ b/src/Symfony/Component/Inflector/Tests/InflectorTest.php @@ -93,6 +93,7 @@ public function singularizeProvider() ['kisses', 'kiss'], ['knives', 'knife'], ['lamps', 'lamp'], + ['lessons', 'lesson'], ['leaves', ['leaf', 'leave', 'leaff']], ['lice', 'louse'], ['lives', 'life'], @@ -115,6 +116,7 @@ public function singularizeProvider() ['photos', 'photo'], ['pianos', 'piano'], ['plateaux', 'plateau'], + ['poisons', 'poison'], ['poppies', 'poppy'], ['prices', ['prex', 'prix', 'price']], ['quizzes', 'quiz'], @@ -124,6 +126,7 @@ public function singularizeProvider() ['sandwiches', ['sandwich', 'sandwiche']], ['scarves', ['scarf', 'scarve', 'scarff']], ['schemas', 'schema'], //schemata + ['seasons', 'season'], ['selfies', 'selfie'], ['series', 'series'], ['services', 'service'], @@ -139,6 +142,7 @@ public function singularizeProvider() ['teeth', 'tooth'], ['theses', ['thes', 'these', 'thesis']], ['thieves', ['thief', 'thieve', 'thieff']], + ['treasons', 'treason'], ['trees', ['tre', 'tree']], ['waltzes', ['waltz', 'waltze']], ['wives', 'wife'], @@ -226,6 +230,7 @@ public function pluralizeProvider() ['knife', 'knives'], ['lamp', 'lamps'], ['leaf', ['leafs', 'leaves']], + ['lesson', 'lessons'], ['life', 'lives'], ['louse', 'lice'], ['man', 'men'], @@ -245,6 +250,7 @@ public function pluralizeProvider() ['photo', 'photos'], ['piano', 'pianos'], ['plateau', ['plateaus', 'plateaux']], + ['poison', 'poisons'], ['poppy', 'poppies'], ['price', 'prices'], ['quiz', 'quizzes'], @@ -254,6 +260,7 @@ public function pluralizeProvider() ['sandwich', 'sandwiches'], ['scarf', ['scarfs', 'scarves']], ['schema', 'schemas'], //schemata + ['season', 'seasons'], ['selfie', 'selfies'], ['series', 'series'], ['service', 'services'], @@ -268,6 +275,7 @@ public function pluralizeProvider() ['tag', 'tags'], ['thief', ['thiefs', 'thieves']], ['tooth', 'teeth'], + ['treason', 'treasons'], ['tree', 'trees'], ['waltz', 'waltzes'], ['wife', 'wives'], diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php index 02453719a14eb..91676d3e58f19 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/DayTransformer.php @@ -33,7 +33,7 @@ public function format(\DateTime $dateTime, int $length): string */ public function getReverseMatchingRegExp(int $length): string { - return 1 === $length ? '\d{1,2}' : '\d{'.$length.'}'; + return 1 === $length ? '\d{1,2}' : '\d{1,'.$length.'}'; } /** diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php index f66ffac481e57..fcc4c4fee9fe5 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/MonthTransformer.php @@ -104,7 +104,7 @@ public function getReverseMatchingRegExp(int $length): string $regExp = '[JFMASOND]'; break; default: - $regExp = '\d{'.$length.'}'; + $regExp = '\d{1,'.$length.'}'; break; } diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php index 51c2eca827efb..8718094c06df0 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/YearTransformer.php @@ -37,7 +37,7 @@ public function format(\DateTime $dateTime, int $length): string */ public function getReverseMatchingRegExp(int $length): string { - return 2 === $length ? '\d{2}' : '\d{4}'; + return 2 === $length ? '\d{2}' : '\d{1,4}'; } /** diff --git a/src/Symfony/Component/Intl/Resources/bin/autoload.php b/src/Symfony/Component/Intl/Resources/bin/autoload.php index 13e056478168c..14167a5f4abf5 100644 --- a/src/Symfony/Component/Intl/Resources/bin/autoload.php +++ b/src/Symfony/Component/Intl/Resources/bin/autoload.php @@ -12,7 +12,7 @@ $autoload = __DIR__.'/../../vendor/autoload.php'; if (!file_exists($autoload)) { - bailout('You should run "composer install --dev" in the component before running this script.'); + bailout('You should run "composer install" in the component before running this script.'); } require_once $autoload; diff --git a/src/Symfony/Component/Intl/Resources/bin/compile b/src/Symfony/Component/Intl/Resources/bin/compile index a59380960441a..d66558395d1ad 100755 --- a/src/Symfony/Component/Intl/Resources/bin/compile +++ b/src/Symfony/Component/Intl/Resources/bin/compile @@ -1,8 +1,7 @@ #!/usr/bin/env bash -if [[ $1 == force ]]; then - docker pull jakzal/php-intl -fi; +[[ $1 == force ]] && docker pull jakzal/php-intl +[[ ! -d /tmp/symfony/icu ]] && mkdir -p /tmp/symfony/icu docker run \ -it --rm --name symfony-intl \ diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php index b2caafa27a465..110fd63f7a817 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php @@ -600,6 +600,7 @@ public function parseMonthProvider() { return [ ['y-M-d', '1970-1-1', 0], + ['y-MM-d', '1970-1-1', 0], ['y-MMM-d', '1970-Jan-1', 0], ['y-MMMM-d', '1970-January-1', 0], ]; @@ -618,6 +619,7 @@ public function parseDayProvider() { return [ ['y-M-d', '1970-1-1', 0], + ['y-M-dd', '1970-1-1', 0], ['y-M-dd', '1970-1-01', 0], ['y-M-ddd', '1970-1-001', 0], ]; diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php index 9905e326e0026..cda1935eb5545 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/IntlDateFormatterTest.php @@ -183,6 +183,17 @@ public function parseQuarterProvider() return $this->notImplemented(parent::parseQuarterProvider()); } + public function testParseThreeDigitsYears() + { + if (PHP_INT_SIZE < 8) { + $this->markTestSkipped('Parsing three digits years requires a 64bit PHP.'); + } + + $formatter = $this->getDefaultDateFormatter('yyyy-M-d'); + $this->assertSame(-32157648000, $formatter->parse('950-12-19')); + $this->assertIsIntlSuccess($formatter, 'U_ZERO_ERROR', IntlGlobals::U_ZERO_ERROR); + } + protected function getDateFormatter($locale, $datetype, $timetype, $timezone = null, $calendar = IntlDateFormatter::GREGORIAN, $pattern = null) { return new IntlDateFormatter($locale, $datetype, $timetype, $timezone, $calendar, $pattern); diff --git a/src/Symfony/Component/Lock/Store/ZookeeperStore.php b/src/Symfony/Component/Lock/Store/ZookeeperStore.php index ce3cb4bab7049..10987c5f72932 100644 --- a/src/Symfony/Component/Lock/Store/ZookeeperStore.php +++ b/src/Symfony/Component/Lock/Store/ZookeeperStore.php @@ -95,7 +95,7 @@ public function waitAndSave(Key $key) */ public function putOffExpiration(Key $key, $ttl) { - throw new NotSupportedException(); + // do nothing, zookeeper locks forever. } /** diff --git a/src/Symfony/Component/Lock/StoreInterface.php b/src/Symfony/Component/Lock/StoreInterface.php index 519c3e4731aa2..e5a1dfa58870c 100644 --- a/src/Symfony/Component/Lock/StoreInterface.php +++ b/src/Symfony/Component/Lock/StoreInterface.php @@ -49,7 +49,6 @@ public function waitAndSave(Key $key); * @param float $ttl amount of seconds to keep the lock in the store * * @throws LockConflictedException - * @throws NotSupportedException */ public function putOffExpiration(Key $key, $ttl); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php index a177e664b62a2..9217eaa49f46b 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/Api/MandrillTransport.php @@ -82,7 +82,7 @@ private function getPayload(Email $email, SmtpEnvelope $envelope): array } $headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'content-type']; - foreach ($email->getHeaders()->getAll() as $name => $header) { + foreach ($email->getHeaders()->all() as $name => $header) { if (\in_array($name, $headersToBypass, true)) { continue; } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/composer.json b/src/Symfony/Component/Mailer/Bridge/Mailchimp/composer.json index 761ec6989a0a8..5134ec8717595 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/mailer": "^4.3" + "symfony/mailer": "^4.3.3" }, "require-dev": { "symfony/http-client": "^4.3" diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php index f546537831e85..9da64c4e2be17 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/Api/MailgunTransport.php @@ -46,7 +46,7 @@ protected function doSendEmail(Email $email, SmtpEnvelope $envelope): void { $body = new FormDataPart($this->getPayload($email, $envelope)); $headers = []; - foreach ($body->getPreparedHeaders()->getAll() as $header) { + foreach ($body->getPreparedHeaders()->all() as $header) { $headers[] = $header->toString(); } @@ -97,7 +97,7 @@ private function getPayload(Email $email, SmtpEnvelope $envelope): array } $headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'content-type']; - foreach ($headers->getAll() as $name => $header) { + foreach ($headers->all() as $name => $header) { if (\in_array($name, $headersToBypass, true)) { continue; } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/MailgunTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/MailgunTransport.php index 913ed705d9ab0..1cc0f25dec940 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/MailgunTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Http/MailgunTransport.php @@ -48,7 +48,7 @@ protected function doSend(SentMessage $message): void 'message' => new DataPart($message->toString(), 'message.mime'), ]); $headers = []; - foreach ($body->getPreparedHeaders()->getAll() as $header) { + foreach ($body->getPreparedHeaders()->all() as $header) { $headers[] = $header->toString(); } $endpoint = str_replace(['%domain%', '%region_dot%'], [urlencode($this->domain), 'us' !== ($this->region ?: 'us') ? $this->region.'.' : ''], self::ENDPOINT); diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/composer.json b/src/Symfony/Component/Mailer/Bridge/Mailgun/composer.json index 6f00d507ebe60..6fc40d9091876 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailgun/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/mailer": "^4.3" + "symfony/mailer": "^4.3.3" }, "require-dev": { "symfony/http-client": "^4.3" diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Http/Api/PostmarkTransport.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Http/Api/PostmarkTransport.php index 3ec9c640a655d..8a046c9f9553d 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/Http/Api/PostmarkTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Http/Api/PostmarkTransport.php @@ -68,7 +68,7 @@ private function getPayload(Email $email, SmtpEnvelope $envelope): array ]; $headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'content-type', 'sender']; - foreach ($email->getHeaders()->getAll() as $name => $header) { + foreach ($email->getHeaders()->all() as $name => $header) { if (\in_array($name, $headersToBypass, true)) { continue; } diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/composer.json b/src/Symfony/Component/Mailer/Bridge/Postmark/composer.json index 0493f1dfb0853..8534c36eb4c25 100644 --- a/src/Symfony/Component/Mailer/Bridge/Postmark/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/mailer": "^4.3" + "symfony/mailer": "^4.3.3" }, "require-dev": { "symfony/http-client": "^4.3" diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php index 8369d4e2775a0..894fac158f6e1 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Http/Api/SendgridTransport.php @@ -82,7 +82,7 @@ private function getPayload(Email $email, SmtpEnvelope $envelope): array // these headers can't be overwritten according to Sendgrid docs // see https://developers.pepipost.com/migration-api/new-subpage/email-send $headersToBypass = ['x-sg-id', 'x-sg-eid', 'received', 'dkim-signature', 'content-transfer-encoding', 'from', 'to', 'cc', 'bcc', 'subject', 'content-type', 'reply-to']; - foreach ($email->getHeaders()->getAll() as $name => $header) { + foreach ($email->getHeaders()->all() as $name => $header) { if (\in_array($name, $headersToBypass, true)) { continue; } diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/composer.json b/src/Symfony/Component/Mailer/Bridge/Sendgrid/composer.json index 5630f5d3f40f8..1bfb16286d55b 100644 --- a/src/Symfony/Component/Mailer/Bridge/Sendgrid/composer.json +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/mailer": "^4.3" + "symfony/mailer": "^4.3.3" }, "require-dev": { "symfony/http-client": "^4.3" diff --git a/src/Symfony/Component/Mailer/DelayedSmtpEnvelope.php b/src/Symfony/Component/Mailer/DelayedSmtpEnvelope.php index 479fc5be42161..b923626fcbb5a 100644 --- a/src/Symfony/Component/Mailer/DelayedSmtpEnvelope.php +++ b/src/Symfony/Component/Mailer/DelayedSmtpEnvelope.php @@ -73,7 +73,7 @@ private static function getRecipientsFromHeaders(Headers $headers): array { $recipients = []; foreach (['to', 'cc', 'bcc'] as $name) { - foreach ($headers->getAll($name) as $header) { + foreach ($headers->all($name) as $header) { foreach ($header->getAddresses() as $address) { $recipients[] = new Address($address->getAddress()); } diff --git a/src/Symfony/Component/Mailer/EventListener/MessageListener.php b/src/Symfony/Component/Mailer/EventListener/MessageListener.php index c63595ada02fe..94b6f2a9ee87f 100644 --- a/src/Symfony/Component/Mailer/EventListener/MessageListener.php +++ b/src/Symfony/Component/Mailer/EventListener/MessageListener.php @@ -53,7 +53,7 @@ private function setHeaders(Message $message): void } $headers = $message->getHeaders(); - foreach ($this->headers->getAll() as $name => $header) { + foreach ($this->headers->all() as $name => $header) { if (!$headers->has($name)) { $headers->add($header); } else { diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php new file mode 100644 index 0000000000000..b825d1d19a7b9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/Stream/SocketStreamTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests\Transport\Smtp\Stream; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; + +class SocketStreamTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\Mailer\Exception\TransportException + * @expectedExceptionMessageRegExp /Connection refused|unable to connect/ + */ + public function testSocketErrorNoConnection() + { + $s = new SocketStream(); + $s->setTimeout(0.1); + $s->setPort(9999); + $s->initialize(); + } + + /** + * @expectedException \Symfony\Component\Mailer\Exception\TransportException + * @expectedExceptionMessageRegExp /no valid certs found cafile stream|Unable to find the socket transport "ssl"/ + */ + public function testSocketErrorBeforeConnectError() + { + $s = new SocketStream(); + $s->setStreamOptions([ + 'ssl' => [ + // not a CA file :) + 'cafile' => __FILE__, + ], + ]); + $s->setEncryption('ssl'); + $s->setHost('smtp.gmail.com'); + $s->setPort(465); + $s->initialize(); + } +} diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php index 308201d538f0e..a502c85e822aa 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/Stream/SocketStream.php @@ -34,14 +34,14 @@ final class SocketStream extends AbstractStream private $sourceIp; private $streamContextOptions = []; - public function setTimeout(int $timeout): self + public function setTimeout(float $timeout): self { $this->timeout = $timeout; return $this; } - public function getTimeout(): int + public function getTimeout(): float { return $this->timeout; } @@ -144,10 +144,16 @@ public function initialize(): void $options['ssl']['crypto_method'] = $options['ssl']['crypto_method'] ?? STREAM_CRYPTO_METHOD_TLS_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; } $streamContext = stream_context_create($options); - $this->stream = @stream_socket_client($this->url, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $streamContext); - if (false === $this->stream) { - throw new TransportException(sprintf('Connection could not be established with host "%s": %s (%s)', $this->url, $errstr, $errno)); + + set_error_handler(function ($type, $msg) { + throw new TransportException(sprintf('Connection could not be established with host "%s": %s.', $this->url, $msg)); + }); + try { + $this->stream = stream_socket_client($this->url, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $streamContext); + } finally { + restore_error_handler(); } + stream_set_blocking($this->stream, true); stream_set_timeout($this->stream, $this->timeout); $this->in = &$this->stream; diff --git a/src/Symfony/Component/Mailer/composer.json b/src/Symfony/Component/Mailer/composer.json index 91bfb1031d937..e41350c88b6cf 100644 --- a/src/Symfony/Component/Mailer/composer.json +++ b/src/Symfony/Component/Mailer/composer.json @@ -20,16 +20,16 @@ "egulias/email-validator": "^2.0", "psr/log": "~1.0", "symfony/event-dispatcher": "^4.3", - "symfony/mime": "^4.3" + "symfony/mime": "^4.3.3" }, "require-dev": { "symfony/amazon-mailer": "^4.3", "symfony/google-mailer": "^4.3", "symfony/http-client-contracts": "^1.1", - "symfony/mailgun-mailer": "^4.3.2", - "symfony/mailchimp-mailer": "^4.3", - "symfony/postmark-mailer": "^4.3", - "symfony/sendgrid-mailer": "^4.3" + "symfony/mailgun-mailer": "^4.3.3", + "symfony/mailchimp-mailer": "^4.3.3", + "symfony/postmark-mailer": "^4.3.3", + "symfony/sendgrid-mailer": "^4.3.3" }, "autoload": { "psr-4": { "Symfony\\Component\\Mailer\\": "" }, diff --git a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php index ce7abe1d25fa9..b83272792f4d0 100644 --- a/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php +++ b/src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php @@ -218,10 +218,10 @@ private function guessHandledClasses(\ReflectionClass $handlerClass, string $ser } if ($type->isBuiltin()) { - throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), $type)); + throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type)); } - return [(string) $parameters[0]->getType()]; + return [$parameters[0]->getType()->getName()]; } private function registerReceivers(ContainerBuilder $container, array $busIds) diff --git a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php index 77bba7b9be842..87ec875cb3c8d 100644 --- a/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php +++ b/src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php @@ -494,10 +494,8 @@ public function testNeedsToHandleAtLeastOneMessage() public function testRegistersTraceableBusesToCollector() { - $dataCollector = $this->getMockBuilder(MessengerDataCollector::class)->getMock(); - $container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo'); - $container->register('data_collector.messenger', $dataCollector); + $container->register('data_collector.messenger', MessengerDataCollector::class); $container->setParameter('kernel.debug', true); (new MessengerPass())->process($container); @@ -689,14 +687,6 @@ public function reject(Envelope $envelope): void } } -class InvalidReceiver -{ -} - -class InvalidSender -{ -} - class UndefinedMessageHandler { public function __invoke(UndefinedMessage $message) diff --git a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php index bcddcffd8aac7..c8644623b7b5f 100644 --- a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Stamp\BusNameStamp; use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp; @@ -148,4 +149,44 @@ public function testItAddsTheStampsToEnvelope() $finalEnvelope = (new MessageBus())->dispatch(new Envelope(new \stdClass()), [new DelayStamp(5), new BusNameStamp('bar')]); $this->assertCount(2, $finalEnvelope->all()); } + + public function provideConstructorDataStucture() + { + yield 'iterator' => [new \ArrayObject([ + new SimpleMiddleware(), + new SimpleMiddleware(), + ])]; + + yield 'array' => [[ + new SimpleMiddleware(), + new SimpleMiddleware(), + ]]; + + yield 'generator' => [(function (): \Generator { + yield new SimpleMiddleware(); + yield new SimpleMiddleware(); + })()]; + } + + /** @dataProvider provideConstructorDataStucture */ + public function testConstructDataStructure($dataStructure) + { + $bus = new MessageBus($dataStructure); + $envelope = new Envelope(new DummyMessage('Hello')); + $newEnvelope = $bus->dispatch($envelope); + $this->assertSame($envelope->getMessage(), $newEnvelope->getMessage()); + + // Test rewindable capacity + $envelope = new Envelope(new DummyMessage('Hello')); + $newEnvelope = $bus->dispatch($envelope); + $this->assertSame($envelope->getMessage(), $newEnvelope->getMessage()); + } +} + +class SimpleMiddleware implements MiddlewareInterface +{ + public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + return $envelope; + } } diff --git a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php index 13bf06b012517..b87fffa82f339 100644 --- a/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php @@ -37,7 +37,7 @@ public function testItTracesDispatch() unset($actualTracedMessage['callTime']); // don't check, too variable $this->assertEquals([ 'message' => $message, - 'stamps' => [[$stamp]], + 'stamps' => [$stamp], 'caller' => [ 'name' => 'TraceableMessageBusTest.php', 'file' => __FILE__, @@ -62,7 +62,7 @@ public function testItTracesDispatchWithEnvelope() unset($actualTracedMessage['callTime']); // don't check, too variable $this->assertEquals([ 'message' => $message, - 'stamps' => [[$stamp]], + 'stamps' => [$stamp], 'caller' => [ 'name' => 'TraceableMessageBusTest.php', 'file' => __FILE__, diff --git a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php index e7dee84219e3f..a81f54c4bc347 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/AmqpExt/ConnectionTest.php @@ -418,6 +418,21 @@ public function testObfuscatePasswordInDsn() $connection->channel(); } + public function testAmqpStampHeadersAreUsed() + { + $factory = new TestAmqpFactory( + $this->createMock(\AMQPConnection::class), + $this->createMock(\AMQPChannel::class), + $this->createMock(\AMQPQueue::class), + $amqpExchange = $this->createMock(\AMQPExchange::class) + ); + + $amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => ['Foo' => 'X', 'Bar' => 'Y']]); + + $connection = Connection::fromDsn('amqp://localhost', [], $factory); + $connection->publish('body', ['Foo' => 'X'], 0, new AmqpStamp(null, AMQP_NOPARAM, ['headers' => ['Bar' => 'Y']])); + } + public function testItCanPublishWithTheDefaultRoutingKey() { $factory = new TestAmqpFactory( diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php index 83708c5085a75..d2caf2dfab10f 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/ConnectionTest.php @@ -157,61 +157,86 @@ private function getSchemaSynchronizerMock() /** * @dataProvider buildConfigurationProvider */ - public function testBuildConfiguration($dsn, $options, $expectedManager, $expectedTableName, $expectedRedeliverTimeout, $expectedQueue) + public function testBuildConfiguration($dsn, $options, $expectedConnection, $expectedTableName, $expectedRedeliverTimeout, $expectedQueue, $expectedAutoSetup) { $config = Connection::buildConfiguration($dsn, $options); - $this->assertEquals($expectedManager, $config['connection']); + $this->assertEquals($expectedConnection, $config['connection']); $this->assertEquals($expectedTableName, $config['table_name']); $this->assertEquals($expectedRedeliverTimeout, $config['redeliver_timeout']); $this->assertEquals($expectedQueue, $config['queue_name']); + $this->assertEquals($expectedAutoSetup, $config['auto_setup']); } public function buildConfigurationProvider() { - return [ - [ - 'dsn' => 'doctrine://default', - 'options' => [], - 'expectedManager' => 'default', - 'expectedTableName' => 'messenger_messages', - 'expectedRedeliverTimeout' => 3600, - 'expectedQueue' => 'default', - ], - // test options from options array - [ - 'dsn' => 'doctrine://default', - 'options' => [ - 'table_name' => 'name_from_options', - 'redeliver_timeout' => 1800, - 'queue_name' => 'important', - ], - 'expectedManager' => 'default', - 'expectedTableName' => 'name_from_options', - 'expectedRedeliverTimeout' => 1800, - 'expectedQueue' => 'important', - ], - // tests options from dsn - [ - 'dsn' => 'doctrine://default?table_name=name_from_dsn&redeliver_timeout=1200&queue_name=normal', - 'options' => [], - 'expectedManager' => 'default', - 'expectedTableName' => 'name_from_dsn', - 'expectedRedeliverTimeout' => 1200, - 'expectedQueue' => 'normal', + yield 'no options' => [ + 'dsn' => 'doctrine://default', + 'options' => [], + 'expectedConnection' => 'default', + 'expectedTableName' => 'messenger_messages', + 'expectedRedeliverTimeout' => 3600, + 'expectedQueue' => 'default', + 'expectedAutoSetup' => true, + ]; + + yield 'test options array' => [ + 'dsn' => 'doctrine://default', + 'options' => [ + 'table_name' => 'name_from_options', + 'redeliver_timeout' => 1800, + 'queue_name' => 'important', + 'auto_setup' => false, ], - // test options from options array wins over options from dsn - [ - 'dsn' => 'doctrine://default?table_name=name_from_dsn&redeliver_timeout=1200&queue_name=normal', - 'options' => [ - 'table_name' => 'name_from_options', - 'redeliver_timeout' => 1800, - 'queue_name' => 'important', - ], - 'expectedManager' => 'default', - 'expectedTableName' => 'name_from_options', - 'expectedRedeliverTimeout' => 1800, - 'expectedQueue' => 'important', + 'expectedConnection' => 'default', + 'expectedTableName' => 'name_from_options', + 'expectedRedeliverTimeout' => 1800, + 'expectedQueue' => 'important', + 'expectedAutoSetup' => false, + ]; + + yield 'options from dsn' => [ + 'dsn' => 'doctrine://default?table_name=name_from_dsn&redeliver_timeout=1200&queue_name=normal&auto_setup=false', + 'options' => [], + 'expectedConnection' => 'default', + 'expectedTableName' => 'name_from_dsn', + 'expectedRedeliverTimeout' => 1200, + 'expectedQueue' => 'normal', + 'expectedAutoSetup' => false, + ]; + + yield 'options from options array wins over options from dsn' => [ + 'dsn' => 'doctrine://default?table_name=name_from_dsn&redeliver_timeout=1200&queue_name=normal&auto_setup=true', + 'options' => [ + 'table_name' => 'name_from_options', + 'redeliver_timeout' => 1800, + 'queue_name' => 'important', + 'auto_setup' => false, ], + 'expectedConnection' => 'default', + 'expectedTableName' => 'name_from_options', + 'expectedRedeliverTimeout' => 1800, + 'expectedQueue' => 'important', + 'expectedAutoSetup' => false, + ]; + + yield 'options from dsn with falsey boolean' => [ + 'dsn' => 'doctrine://default?auto_setup=0', + 'options' => [], + 'expectedConnection' => 'default', + 'expectedTableName' => 'messenger_messages', + 'expectedRedeliverTimeout' => 3600, + 'expectedQueue' => 'default', + 'expectedAutoSetup' => false, + ]; + + yield 'options from dsn with thruthy boolean' => [ + 'dsn' => 'doctrine://default?auto_setup=1', + 'options' => [], + 'expectedConnection' => 'default', + 'expectedTableName' => 'messenger_messages', + 'expectedRedeliverTimeout' => 3600, + 'expectedQueue' => 'default', + 'expectedAutoSetup' => true, ]; } diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php index 177f6b90384c5..066b4c17887a7 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php @@ -16,7 +16,7 @@ use Symfony\Component\Messenger\Transport\RedisExt\Connection; /** - * @requires extension redis + * @requires extension redis >= 4.3.0 */ class ConnectionTest extends TestCase { @@ -79,6 +79,16 @@ public function testKeepGettingPendingMessages() $this->assertNotNull($connection->get()); } + public function testAuth() + { + $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); + + $redis->expects($this->exactly(1))->method('auth') + ->with('password'); + + Connection::fromDsn('redis://password@localhost/queue', [], $redis); + } + public function testFirstGetPendingMessagesThenNewMessages() { $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); @@ -109,7 +119,7 @@ public function testUnexpectedRedisError() $redis->expects($this->once())->method('xreadgroup')->willReturn(false); $redis->expects($this->once())->method('getLastError')->willReturn('Redis error happens'); - $connection = Connection::fromDsn('redis://localhost/queue', [], $redis); + $connection = Connection::fromDsn('redis://localhost/queue', ['auto_setup' => false], $redis); $connection->get(); } @@ -142,4 +152,31 @@ public function testGetNonBlocking() $connection->reject($message['id']); $redis->del('messenger-getnonblocking'); } + + public function testLastErrorGetsCleared() + { + $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); + + $redis->expects($this->once())->method('xadd')->willReturn(0); + $redis->expects($this->once())->method('xack')->willReturn(0); + + $redis->method('getLastError')->willReturnOnConsecutiveCalls('xadd error', 'xack error'); + $redis->expects($this->exactly(2))->method('clearLastError'); + + $connection = Connection::fromDsn('redis://localhost/messenger-clearlasterror', ['auto_setup' => false], $redis); + + try { + $connection->add('message', []); + } catch (TransportException $e) { + } + + $this->assertSame('xadd error', $e->getMessage()); + + try { + $connection->ack('1'); + } catch (TransportException $e) { + } + + $this->assertSame('xack error', $e->getMessage()); + } } diff --git a/src/Symfony/Component/Messenger/Tests/WorkerTest.php b/src/Symfony/Component/Messenger/Tests/WorkerTest.php index c82652fe1d29f..ac8c2a7ad8402 100644 --- a/src/Symfony/Component/Messenger/Tests/WorkerTest.php +++ b/src/Symfony/Component/Messenger/Tests/WorkerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; use Symfony\Component\Messenger\Event\WorkerStoppedEvent; +use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Retry\RetryStrategyInterface; @@ -120,6 +121,37 @@ public function testDispatchCausesRetry() $this->assertSame(1, $receiver->getAcknowledgeCount()); } + public function testUnrecoverableMessageHandlingExceptionPreventsRetries() + { + $envelope1 = new Envelope(new DummyMessage('Unwrapped Exception'), [new SentStamp('Some\Sender', 'transport1')]); + $envelope2 = new Envelope(new DummyMessage('Wrapped Exception'), [new SentStamp('Some\Sender', 'transport1')]); + + $receiver = new DummyReceiver([ + [$envelope1], + [$envelope2], + ]); + + $bus = $this->getMockBuilder(MessageBusInterface::class)->getMock(); + $bus->expects($this->at(0))->method('dispatch')->willThrowException(new UnrecoverableMessageHandlingException()); + $bus->expects($this->at(1))->method('dispatch')->willThrowException( + new HandlerFailedException($envelope2, [new UnrecoverableMessageHandlingException()]) + ); + + $retryStrategy = $this->getMockBuilder(RetryStrategyInterface::class)->getMock(); + $retryStrategy->expects($this->never())->method('isRetryable')->willReturn(true); + + $worker = new Worker(['transport1' => $receiver], $bus, ['transport1' => $retryStrategy]); + $worker->run([], function (?Envelope $envelope) use ($worker) { + // stop after the messages finish + if (null === $envelope) { + $worker->stop(); + } + }); + + // message was rejected + $this->assertSame(2, $receiver->getRejectCount()); + } + public function testDispatchCausesRejectWhenNoRetry() { $receiver = new DummyReceiver([ diff --git a/src/Symfony/Component/Messenger/TraceableMessageBus.php b/src/Symfony/Component/Messenger/TraceableMessageBus.php index e8ef5b0907d6b..774e2aa035849 100644 --- a/src/Symfony/Component/Messenger/TraceableMessageBus.php +++ b/src/Symfony/Component/Messenger/TraceableMessageBus.php @@ -33,7 +33,7 @@ public function dispatch($message, array $stamps = []): Envelope { $envelope = Envelope::wrap($message, $stamps); $context = [ - 'stamps' => array_values($envelope->all()), + 'stamps' => array_merge([], ...array_values($envelope->all())), 'message' => $envelope->getMessage(), 'caller' => $this->getCaller(), 'callTime' => microtime(true), diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php index d7a00c09b4436..b8298d5697d96 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpStamp.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Messenger\Transport\AmqpExt; -use Symfony\Component\Messenger\Stamp\StampInterface; +use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; /** * @author Guillaume Gammelin @@ -19,7 +19,7 @@ * * @experimental in 4.3 */ -final class AmqpStamp implements StampInterface +final class AmqpStamp implements NonSendableStampInterface { private $routingKey; private $flags; diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php index 35cb4eb1c4e98..8f209a9db4bb0 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/AmqpTransportFactory.php @@ -24,6 +24,8 @@ class AmqpTransportFactory implements TransportFactoryInterface { public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface { + unset($options['transport_name']); + return new AmqpTransport(Connection::fromDsn($dsn, $options), $serializer); } diff --git a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php index ecd72a7be9d90..3c7a91f62186a 100644 --- a/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/AmqpExt/Connection.php @@ -191,9 +191,7 @@ public function publish(string $body, array $headers = [], int $delay = 0, AmqpS $this->exchange(), $body, $this->getRoutingKeyForMessage($amqpStamp), - [ - 'headers' => $headers, - ], + $headers, $amqpStamp ); } @@ -223,20 +221,21 @@ private function publishWithDelay(string $body, array $headers, int $delay, Amqp $this->getDelayExchange(), $body, $this->getRoutingKeyForDelay($delay, $routingKey), - [ - 'headers' => $headers, - ], + $headers, $amqpStamp ); } - private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $attributes = [], AmqpStamp $amqpStamp = null) + private function publishOnExchange(\AMQPExchange $exchange, string $body, string $routingKey = null, array $headers = [], AmqpStamp $amqpStamp = null) { + $attributes = $amqpStamp ? $amqpStamp->getAttributes() : []; + $attributes['headers'] = array_merge($headers, $attributes['headers'] ?? []); + $exchange->publish( $body, $routingKey, $amqpStamp ? $amqpStamp->getFlags() : AMQP_NOPARAM, - array_merge($amqpStamp ? $amqpStamp->getAttributes() : [], $attributes) + $attributes ); } diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 74a85bbfbe1e9..c8692ba3486c6 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -76,22 +76,19 @@ public static function buildConfiguration($dsn, array $options = []) parse_str($components['query'], $query); } - $configuration = [ - 'connection' => $components['host'], - 'table_name' => $options['table_name'] ?? ($query['table_name'] ?? self::DEFAULT_OPTIONS['table_name']), - 'queue_name' => $options['queue_name'] ?? ($query['queue_name'] ?? self::DEFAULT_OPTIONS['queue_name']), - 'redeliver_timeout' => $options['redeliver_timeout'] ?? ($query['redeliver_timeout'] ?? self::DEFAULT_OPTIONS['redeliver_timeout']), - 'auto_setup' => $options['auto_setup'] ?? ($query['auto_setup'] ?? self::DEFAULT_OPTIONS['auto_setup']), - ]; + $configuration = ['connection' => $components['host']]; + $configuration += $options + $query + self::DEFAULT_OPTIONS; + + $configuration['auto_setup'] = filter_var($configuration['auto_setup'], FILTER_VALIDATE_BOOLEAN); // check for extra keys in options - $optionsExtraKeys = array_diff(array_keys($options), array_keys($configuration)); + $optionsExtraKeys = array_diff(array_keys($options), array_keys(self::DEFAULT_OPTIONS)); if (0 < \count($optionsExtraKeys)) { throw new InvalidArgumentException(sprintf('Unknown option found : [%s]. Allowed options are [%s]', implode(', ', $optionsExtraKeys), implode(', ', self::DEFAULT_OPTIONS))); } // check for extra keys in options - $queryExtraKeys = array_diff(array_keys($query), array_keys($configuration)); + $queryExtraKeys = array_diff(array_keys($query), array_keys(self::DEFAULT_OPTIONS)); if (0 < \count($queryExtraKeys)) { throw new InvalidArgumentException(sprintf('Unknown option found in DSN: [%s]. Allowed options are [%s]', implode(', ', $queryExtraKeys), implode(', ', self::DEFAULT_OPTIONS))); } diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php index 3f9aa7981ab1b..959a4ec7a2c3b 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php @@ -33,6 +33,7 @@ public function __construct(RegistryInterface $registry) public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface { + unset($options['transport_name']); $configuration = Connection::buildConfiguration($dsn, $options); try { diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php index 18b6091e9c64f..59ec9f029d6a4 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php @@ -51,6 +51,11 @@ public function __construct(array $configuration, array $connectionCredentials = $this->connection = $redis ?: new \Redis(); $this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379); $this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP); + + if (isset($connectionCredentials['auth'])) { + $this->connection->auth($connectionCredentials['auth']); + } + $this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream']; $this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group']; $this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer']; @@ -72,6 +77,7 @@ public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $re $connectionCredentials = [ 'host' => $parsedUrl['host'] ?? '127.0.0.1', 'port' => $parsedUrl['port'] ?? 6379, + 'auth' => $parsedUrl['pass'] ?? $parsedUrl['user'] ?? null, ]; if (isset($parsedUrl['query'])) { @@ -108,12 +114,15 @@ public function get(): ?array 1 ); } catch (\RedisException $e) { + throw new TransportException($e->getMessage(), 0, $e); } - if ($e || false === $messages) { - throw new TransportException( - ($e ? $e->getMessage() : $this->connection->getLastError()) ?? 'Could not read messages from the redis stream.' - ); + if (false === $messages) { + if ($error = $this->connection->getLastError() ?: null) { + $this->connection->clearLastError(); + } + + throw new TransportException($error ?? 'Could not read messages from the redis stream.'); } if ($this->couldHavePendingMessages && empty($messages[$this->stream])) { @@ -138,28 +147,34 @@ public function get(): ?array public function ack(string $id): void { - $e = null; try { $acknowledged = $this->connection->xack($this->stream, $this->group, [$id]); } catch (\RedisException $e) { + throw new TransportException($e->getMessage(), 0, $e); } - if ($e || !$acknowledged) { - throw new TransportException(($e ? $e->getMessage() : $this->connection->getLastError()) ?? sprintf('Could not acknowledge redis message "%s".', $id), 0, $e); + if (!$acknowledged) { + if ($error = $this->connection->getLastError() ?: null) { + $this->connection->clearLastError(); + } + throw new TransportException($error ?? sprintf('Could not acknowledge redis message "%s".', $id)); } } public function reject(string $id): void { - $e = null; try { $deleted = $this->connection->xack($this->stream, $this->group, [$id]); $deleted = $this->connection->xdel($this->stream, [$id]) && $deleted; } catch (\RedisException $e) { + throw new TransportException($e->getMessage(), 0, $e); } - if ($e || !$deleted) { - throw new TransportException(($e ? $e->getMessage() : $this->connection->getLastError()) ?? sprintf('Could not delete message "%s" from the redis stream.', $id), 0, $e); + if (!$deleted) { + if ($error = $this->connection->getLastError() ?: null) { + $this->connection->clearLastError(); + } + throw new TransportException($error ?? sprintf('Could not delete message "%s" from the redis stream.', $id)); } } @@ -169,16 +184,19 @@ public function add(string $body, array $headers): void $this->setup(); } - $e = null; try { $added = $this->connection->xadd($this->stream, '*', ['message' => json_encode( ['body' => $body, 'headers' => $headers] )]); } catch (\RedisException $e) { + throw new TransportException($e->getMessage(), 0, $e); } - if ($e || !$added) { - throw new TransportException(($e ? $e->getMessage() : $this->connection->getLastError()) ?? 'Could not add a message to the redis stream.', 0, $e); + if (!$added) { + if ($error = $this->connection->getLastError() ?: null) { + $this->connection->clearLastError(); + } + throw new TransportException($error ?? 'Could not add a message to the redis stream.'); } } @@ -190,6 +208,11 @@ public function setup(): void throw new TransportException($e->getMessage(), 0, $e); } + // group might already exist, ignore + if ($this->connection->getLastError()) { + $this->connection->clearLastError(); + } + $this->autoSetup = false; } } diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php index acb2d1f59160c..45e4b8b144973 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/RedisTransportFactory.php @@ -25,6 +25,8 @@ class RedisTransportFactory implements TransportFactoryInterface { public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface { + unset($options['transport_name']); + return new RedisTransport(Connection::fromDsn($dsn, $options), $serializer); } diff --git a/src/Symfony/Component/Messenger/Worker.php b/src/Symfony/Component/Messenger/Worker.php index a51cd3506fe50..d0c98b76ff05f 100644 --- a/src/Symfony/Component/Messenger/Worker.php +++ b/src/Symfony/Component/Messenger/Worker.php @@ -191,6 +191,20 @@ private function dispatchEvent($event) private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool { + // if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry + if ($e instanceof HandlerFailedException) { + $shouldNotRetry = true; + foreach ($e->getNestedExceptions() as $nestedException) { + if (!$nestedException instanceof UnrecoverableExceptionInterface) { + $shouldNotRetry = false; + break; + } + } + if ($shouldNotRetry) { + return false; + } + } + if ($e instanceof UnrecoverableExceptionInterface) { return false; } diff --git a/src/Symfony/Component/Mime/CHANGELOG.md b/src/Symfony/Component/Mime/CHANGELOG.md new file mode 100644 index 0000000000000..796cfdd155626 --- /dev/null +++ b/src/Symfony/Component/Mime/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +4.3.3 +----- + + * [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`. + +4.3.0 +----- + + * Introduced the component as experimental diff --git a/src/Symfony/Component/Mime/Email.php b/src/Symfony/Component/Mime/Email.php index 7812372d5372f..3fbebc461f1eb 100644 --- a/src/Symfony/Component/Mime/Email.php +++ b/src/Symfony/Component/Mime/Email.php @@ -103,7 +103,7 @@ public function getSender(): ?Address } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -113,7 +113,7 @@ public function addFrom(...$addresses) } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -131,7 +131,7 @@ public function getFrom(): array } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ @@ -141,7 +141,7 @@ public function addReplyTo(...$addresses) } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ @@ -159,7 +159,7 @@ public function getReplyTo(): array } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -169,7 +169,7 @@ public function addTo(...$addresses) } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -187,7 +187,7 @@ public function getTo(): array } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -197,7 +197,7 @@ public function addCc(...$addresses) } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ @@ -215,7 +215,7 @@ public function getCc(): array } /** - * @param Address|NamedAddress|string $addresses + * @param Address|NamedAddress|string ...$addresses * * @return $this */ @@ -225,7 +225,7 @@ public function addBcc(...$addresses) } /** - * @param Address|string $addresses + * @param Address|string ...$addresses * * @return $this */ diff --git a/src/Symfony/Component/Mime/Header/Headers.php b/src/Symfony/Component/Mime/Header/Headers.php index c0e45e0d0a852..57f02b25a3ef0 100644 --- a/src/Symfony/Component/Mime/Header/Headers.php +++ b/src/Symfony/Component/Mime/Header/Headers.php @@ -51,7 +51,7 @@ public function __clone() public function setMaxLineLength(int $lineLength) { $this->lineLength = $lineLength; - foreach ($this->getAll() as $header) { + foreach ($this->all() as $header) { $header->setMaxLineLength($lineLength); } } @@ -177,7 +177,7 @@ public function get(string $name): ?HeaderInterface return array_shift($values); } - public function getAll(string $name = null): iterable + public function all(string $name = null): iterable { if (null === $name) { foreach ($this->headers as $name => $collection) { @@ -220,7 +220,7 @@ public function toString(): string public function toArray(): array { $arr = []; - foreach ($this->getAll() as $header) { + foreach ($this->all() as $header) { if ('' !== $header->getBodyAsString()) { $arr[] = $header->toString(); } diff --git a/src/Symfony/Component/Mime/Message.php b/src/Symfony/Component/Mime/Message.php index db6e13fae6a4d..4d6af1c94d4b4 100644 --- a/src/Symfony/Component/Mime/Message.php +++ b/src/Symfony/Component/Mime/Message.php @@ -130,17 +130,11 @@ private function generateMessageId(string $email): string return bin2hex(random_bytes(16)).strstr($email, '@'); } - /** - * @internal - */ public function __serialize(): array { return [$this->headers, $this->body]; } - /** - * @internal - */ public function __unserialize(array $data): void { [$this->headers, $this->body] = $data; diff --git a/src/Symfony/Component/Mime/RawMessage.php b/src/Symfony/Component/Mime/RawMessage.php index 16b090c95474e..40a2795e2369e 100644 --- a/src/Symfony/Component/Mime/RawMessage.php +++ b/src/Symfony/Component/Mime/RawMessage.php @@ -69,17 +69,11 @@ final public function unserialize($serialized) $this->__unserialize(unserialize($serialized)); } - /** - * @internal - */ public function __serialize(): array { return [$this->message]; } - /** - * @internal - */ public function __unserialize(array $data): void { [$this->message] = $data; diff --git a/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php b/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php index 2f4a1dd6358a8..3568c9a6e45d6 100644 --- a/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php +++ b/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php @@ -141,7 +141,7 @@ public function testGetReturnsNullIfHeaderNotSet() $this->assertNull($headers->get('Message-ID')); } - public function testGetAllReturnsAllHeadersMatchingName() + public function testAllReturnsAllHeadersMatchingName() { $header0 = new UnstructuredHeader('X-Test', 'some@id'); $header1 = new UnstructuredHeader('X-Test', 'other@id'); @@ -150,10 +150,10 @@ public function testGetAllReturnsAllHeadersMatchingName() $headers->addTextHeader('X-Test', 'some@id'); $headers->addTextHeader('X-Test', 'other@id'); $headers->addTextHeader('X-Test', 'more@id'); - $this->assertEquals([$header0, $header1, $header2], iterator_to_array($headers->getAll('X-Test'))); + $this->assertEquals([$header0, $header1, $header2], iterator_to_array($headers->all('X-Test'))); } - public function testGetAllReturnsAllHeadersIfNoArguments() + public function testAllReturnsAllHeadersIfNoArguments() { $header0 = new IdentificationHeader('Message-ID', 'some@id'); $header1 = new UnstructuredHeader('Subject', 'thing'); @@ -162,13 +162,13 @@ public function testGetAllReturnsAllHeadersIfNoArguments() $headers->addIdHeader('Message-ID', 'some@id'); $headers->addTextHeader('Subject', 'thing'); $headers->addMailboxListHeader('To', [new Address('person@example.org')]); - $this->assertEquals(['message-id' => $header0, 'subject' => $header1, 'to' => $header2], iterator_to_array($headers->getAll())); + $this->assertEquals(['message-id' => $header0, 'subject' => $header1, 'to' => $header2], iterator_to_array($headers->all())); } - public function testGetAllReturnsEmptyArrayIfNoneSet() + public function testAllReturnsEmptyArrayIfNoneSet() { $headers = new Headers(); - $this->assertEquals([], iterator_to_array($headers->getAll('Received'))); + $this->assertEquals([], iterator_to_array($headers->all('Received'))); } public function testRemoveRemovesAllHeadersWithName() @@ -199,12 +199,12 @@ public function testGetIsNotCaseSensitive() $this->assertEquals($header, $headers->get('message-id')); } - public function testGetAllIsNotCaseSensitive() + public function testAllIsNotCaseSensitive() { $header = new IdentificationHeader('Message-ID', 'some@id'); $headers = new Headers(); $headers->addIdHeader('Message-ID', 'some@id'); - $this->assertEquals([$header], iterator_to_array($headers->getAll('message-id'))); + $this->assertEquals([$header], iterator_to_array($headers->all('message-id'))); } public function testRemoveIsNotCaseSensitive() diff --git a/src/Symfony/Component/Mime/Tests/MessageTest.php b/src/Symfony/Component/Mime/Tests/MessageTest.php index dbeb0a55443c0..cc806b919e3f9 100644 --- a/src/Symfony/Component/Mime/Tests/MessageTest.php +++ b/src/Symfony/Component/Mime/Tests/MessageTest.php @@ -68,7 +68,7 @@ public function testGetPreparedHeaders() $message = new Message(); $message->getHeaders()->addMailboxListHeader('From', ['fabien@symfony.com']); $h = $message->getPreparedHeaders(); - $this->assertCount(4, iterator_to_array($h->getAll())); + $this->assertCount(4, iterator_to_array($h->all())); $this->assertEquals(new MailboxListHeader('From', [new Address('fabien@symfony.com')]), $h->get('From')); $this->assertEquals(new UnstructuredHeader('MIME-Version', '1.0'), $h->get('mime-version')); $this->assertTrue($h->has('Message-Id')); diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php index b91d1e62ebb95..f23239193e1ea 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php @@ -165,25 +165,25 @@ public function testSetValueFailsIfNoAdderNorRemoverFound() public function testIsWritableReturnsTrueIfAdderAndRemoverExists() { - $car = $this->getMockBuilder(__CLASS__.'_Car')->getMock(); + $car = new PropertyAccessorCollectionTest_Car(); $this->assertTrue($this->propertyAccessor->isWritable($car, 'axes')); } public function testIsWritableReturnsFalseIfOnlyAdderExists() { - $car = $this->getMockBuilder(__CLASS__.'_CarOnlyAdder')->getMock(); + $car = new PropertyAccessorCollectionTest_CarOnlyAdder(); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes')); } public function testIsWritableReturnsFalseIfOnlyRemoverExists() { - $car = $this->getMockBuilder(__CLASS__.'_CarOnlyRemover')->getMock(); + $car = new PropertyAccessorCollectionTest_CarOnlyRemover(); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes')); } public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists() { - $car = $this->getMockBuilder(__CLASS__.'_CarNoAdderAndRemover')->getMock(); + $car = new PropertyAccessorCollectionTest_CarNoAdderAndRemover(); $this->assertFalse($this->propertyAccessor->isWritable($car, 'axes')); } @@ -193,7 +193,7 @@ public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists() */ public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable() { - $car = $this->getMockBuilder(__CLASS__.'_Car')->getMock(); + $car = new PropertyAccessorCollectionTest_Car(); $this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable'); } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php71DummyChild.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php71DummyChild.php deleted file mode 100644 index be26a53220dcb..0000000000000 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php71DummyChild.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\PropertyInfo\Tests\Fixtures; - -class Php71DummyParent -{ - public $string; - - public function __construct(string $string) - { - $this->string = $string; - } -} - -class Php71DummyChild extends Php71DummyParent -{ - public function __construct(string $string) - { - parent::__construct($string); - } -} - -class Php71DummyChild2 extends Php71DummyParent -{ -} - -class Php71DummyChild3 extends Php71DummyParent -{ - public function __construct() - { - parent::__construct('hello'); - } -} diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 05ae44b5f110c..5f133efd6ef9c 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -12,7 +12,7 @@ CHANGELOG Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible with the new serialization methods in PHP 7.4. * exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators - * added support for invokable route loader services + * added support for invokable service route loaders 4.2.0 ----- diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php index cba6a8708243e..bc057445da501 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php @@ -25,7 +25,7 @@ class AuthenticationTrustResolver implements AuthenticationTrustResolverInterfac private $anonymousClass; private $rememberMeClass; - public function __construct(?string $anonymousClass = null, ?string $rememberMeClass = null) + public function __construct(string $anonymousClass = null, string $rememberMeClass = null) { $this->anonymousClass = $anonymousClass; $this->rememberMeClass = $rememberMeClass; diff --git a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php index 4c568985e180f..67a0127066de0 100644 --- a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php @@ -51,7 +51,7 @@ public static function isSupported() return true; } - return \function_exists('sodium_crypto_pwhash_str') || \extension_loaded('libsodium'); + return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.9', '>='); } /** diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index 94d9f5ca51e6e..1e09992afed4e 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -30,7 +30,7 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti public function __construct(int $opsLimit = null, int $memLimit = null, int $cost = null) { $cost = $cost ?? 13; - $opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); + $opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 1024); if (3 > $opsLimit) { diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 01b8b95c8b8f5..934a3fdfca528 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -34,7 +34,7 @@ public function __construct(int $opsLimit = null, int $memLimit = null) throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.'); } - $this->opsLimit = $opsLimit ?? max(6, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE : 6); + $this->opsLimit = $opsLimit ?? max(4, \defined('SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE : 4); $this->memLimit = $memLimit ?? max(64 * 1024 * 1024, \defined('SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE') ? \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE : 64 * 1024 * 2014); if (3 > $this->opsLimit) { @@ -48,7 +48,7 @@ public function __construct(int $opsLimit = null, int $memLimit = null) public static function isSupported(): bool { - return \function_exists('sodium_crypto_pwhash_str_needs_rehash') || \function_exists('Sodium\crypto_pwhash_str_needs_rehash'); + return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>='); } /** diff --git a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php index cdfb613593928..d2804de857749 100644 --- a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php @@ -25,8 +25,7 @@ abstract class AbstractGuardAuthenticator implements AuthenticatorInterface * Shortcut to create a PostAuthenticationGuardToken for you, if you don't really * care about which authenticated token you're using. * - * @param UserInterface $user - * @param string $providerKey + * @param string $providerKey * * @return PostAuthenticationGuardToken */ diff --git a/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php index 851241f08a486..3ff5b13b8337a 100644 --- a/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php @@ -37,8 +37,6 @@ interface AuthenticatorInterface extends AuthenticationEntryPointInterface * * If this returns false, the authenticator will be skipped. * - * @param Request $request - * * @return bool */ public function supports(Request $request); @@ -60,8 +58,6 @@ public function supports(Request $request); * * return ['api_key' => $request->headers->get('X-API-TOKEN')]; * - * @param Request $request - * * @return mixed Any non-null value * * @throws \UnexpectedValueException If null is returned diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php index 58e188cc4c567..6b358990733b3 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php @@ -48,8 +48,6 @@ * * @author Fabien Potencier * @author Johannes M. Schmitt - * - * @internal since Symfony 4.3 */ abstract class AbstractAuthenticationListener implements ListenerInterface { diff --git a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php index b037aaaaa8bd5..a3fb526a2490b 100644 --- a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php @@ -89,11 +89,3 @@ public function testIntegrationNoUser() $this->assertSame([null], $argumentResolver->getArguments(Request::create('/'), function (UserInterface $user = null) {})); } } - -abstract class DummyUser implements UserInterface -{ -} - -abstract class DummySubUser extends DummyUser -{ -} diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index 681e3c4941dc4..ea25c95597f31 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -47,14 +47,19 @@ public function __construct($defaultContext = []) */ public function encode($data, $format, array $context = []) { - $jsonEncodeOptions = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; - $encodedJson = json_encode($data, $jsonEncodeOptions); + $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; - if (\PHP_VERSION_ID >= 70300 && (JSON_THROW_ON_ERROR & $jsonEncodeOptions)) { + try { + $encodedJson = json_encode($data, $options); + } catch (\JsonException $e) { + throw new NotEncodableValueException($e->getMessage(), 0, $e); + } + + if (\PHP_VERSION_ID >= 70300 && (JSON_THROW_ON_ERROR & $options)) { return $encodedJson; } - if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) { + if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($options & JSON_PARTIAL_OUTPUT_ON_ERROR))) { throw new NotEncodableValueException(json_last_error_msg()); } diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index 9c7c01b42aa7d..cbfc5b8df5b28 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -339,7 +339,7 @@ private function parseXmlAttributes(\DOMNode $node, array $context = []): array $typeCastAttributes = (bool) ($context[self::TYPE_CASE_ATTRIBUTES] ?? $this->defaultContext[self::TYPE_CASE_ATTRIBUTES]); foreach ($node->attributes as $attr) { - if (!is_numeric($attr->nodeValue) || !$typeCastAttributes) { + if (!is_numeric($attr->nodeValue) || !$typeCastAttributes || (isset($attr->nodeValue[1]) && '0' === $attr->nodeValue[0])) { $data['@'.$attr->nodeName] = $attr->nodeValue; continue; diff --git a/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php b/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php index 2ea19d28faa20..c3e117945e2f7 100644 --- a/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php +++ b/src/Symfony/Component/Serializer/Extractor/ObjectPropertyListExtractor.php @@ -23,7 +23,7 @@ final class ObjectPropertyListExtractor implements ObjectPropertyListExtractorIn private $propertyListExtractor; private $objectClassResolver; - public function __construct(PropertyListExtractorInterface $propertyListExtractor, ?callable $objectClassResolver = null) + public function __construct(PropertyListExtractorInterface $propertyListExtractor, callable $objectClassResolver = null) { $this->propertyListExtractor = $propertyListExtractor; $this->objectClassResolver = $objectClassResolver; diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 7b66ee9aeda7b..e32d14fc4ab55 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -330,13 +330,14 @@ public function denormalize($data, $class, $format = null, array $context = []) $reflectionClass = new \ReflectionClass($class); $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format); + $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); foreach ($normalizedData as $attribute => $value) { if ($this->nameConverter) { - $attribute = $this->nameConverter->denormalize($attribute, $class, $format, $context); + $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); } - if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) { + if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($resolvedClass, $attribute, $format, $context)) { if (!($context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES])) { $extraAttributes[] = $attribute; } @@ -351,7 +352,7 @@ public function denormalize($data, $class, $format = null, array $context = []) } } - $value = $this->validateAndDenormalize($class, $attribute, $value, $format, $context); + $value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $context); try { $this->setAttributeValue($object, $attribute, $value, $format, $context); } catch (InvalidArgumentException $e) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php b/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php index 31e27a85cb024..3e7021b130876 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php +++ b/src/Symfony/Component/Serializer/Normalizer/DenormalizableInterface.php @@ -34,7 +34,7 @@ interface DenormalizableInterface * differently based on different input formats * @param array $context Options for denormalizing * - * @return object + * @return object|object[] */ public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = []); } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index 74692783ea5e6..98021d4f47b08 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -309,6 +309,17 @@ public function testNoTypeCastAttribute() $this->assertSame($expected, $data); } + public function testDoesNotTypeCastStringsStartingWith0() + { + $source = << + +XML; + + $data = $this->encoder->decode($source, 'xml'); + $this->assertSame('018', $data['@a']); + } + public function testEncode() { $source = $this->getXmlSource(); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummyFirstChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummyFirstChild.php index 645c307c35735..20672c39b5fe7 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummyFirstChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummyFirstChild.php @@ -15,10 +15,23 @@ class AbstractDummyFirstChild extends AbstractDummy { public $bar; + /** @var DummyFirstChildQuux|null */ + public $quux; + public function __construct($foo = null, $bar = null) { parent::__construct($foo); $this->bar = $bar; } + + public function getQuux(): ?DummyFirstChildQuux + { + return $this->quux; + } + + public function setQuux(DummyFirstChildQuux $quux): void + { + $this->quux = $quux; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummySecondChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummySecondChild.php index 5a41b9441ad8b..67a72977e2c09 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummySecondChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/AbstractDummySecondChild.php @@ -15,10 +15,23 @@ class AbstractDummySecondChild extends AbstractDummy { public $baz; + /** @var DummySecondChildQuux|null */ + public $quux; + public function __construct($foo = null, $baz = null) { parent::__construct($foo); $this->baz = $baz; } + + public function getQuux(): ?DummySecondChildQuux + { + return $this->quux; + } + + public function setQuux(DummySecondChildQuux $quux): void + { + $this->quux = $quux; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyFirstChildQuux.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyFirstChildQuux.php new file mode 100644 index 0000000000000..7ba39fc346b1f --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyFirstChildQuux.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +class DummyFirstChildQuux +{ + /** + * @var string + */ + private $value; + + public function __construct(string $value) + { + $this->value = $value; + } + + public function getValue(): string + { + return $this->value; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummySecondChildQuux.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummySecondChildQuux.php new file mode 100644 index 0000000000000..11c4d0eccdf77 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummySecondChildQuux.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +class DummySecondChildQuux +{ + /** + * @var string + */ + private $value; + + public function __construct(string $value) + { + $this->value = $value; + } + + public function getValue(): string + { + return $this->value; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 70939567f55c7..af60cb99dcefe 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -16,13 +16,22 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; +use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy; +use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; class AbstractObjectNormalizerTest extends TestCase { @@ -147,6 +156,39 @@ private function getDenormalizerForDummyCollection() return $denormalizer; } + public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $loaderMock = $this->getMockBuilder(ClassMetadataFactoryInterface::class)->getMock(); + $loaderMock->method('hasMetadataFor')->willReturnMap([ + [ + AbstractDummy::class, + true, + ], + ]); + + $loaderMock->method('getMetadataFor')->willReturnMap([ + [ + AbstractDummy::class, + new ClassMetadata( + AbstractDummy::class, + new ClassDiscriminatorMapping('type', [ + 'first' => AbstractDummyFirstChild::class, + 'second' => AbstractDummySecondChild::class, + ]) + ), + ], + ]); + + $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); + $normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver); + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + $normalizedData = $normalizer->denormalize(['foo' => 'foo', 'baz' => 'baz', 'quux' => ['value' => 'quux'], 'type' => 'second'], AbstractDummy::class); + + $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux); + } + /** * Test that additional attributes throw an exception if no metadata factory is specified. * @@ -180,7 +222,7 @@ protected function setAttributeValue($object, $attribute, $value, $format = null protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = []) { - return \in_array($attribute, ['foo', 'baz']); + return \in_array($attribute, ['foo', 'baz', 'quux', 'value']); } public function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) @@ -338,7 +380,3 @@ public function setSerializer(SerializerInterface $serializer) $this->serializer = $serializer; } } - -abstract class ObjectSerializerDenormalizer implements SerializerInterface, DenormalizerInterface -{ -} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index c3240cd966aef..0e7bde61e4ecf 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -687,43 +687,6 @@ public function otherMethod() } } -class GetCamelizedDummy -{ - private $kevinDunglas; - private $fooBar; - private $bar_foo; - - public function __construct($kevinDunglas = null) - { - $this->kevinDunglas = $kevinDunglas; - } - - public function getKevinDunglas() - { - return $this->kevinDunglas; - } - - public function setFooBar($fooBar) - { - $this->fooBar = $fooBar; - } - - public function getFooBar() - { - return $this->fooBar; - } - - public function setBar_foo($bar_foo) - { - $this->bar_foo = $bar_foo; - } - - public function getBar_foo() - { - return $this->bar_foo; - } -} - class ObjectConstructorArgsWithPrivateMutatorDummy { private $foo; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index b1c9fcef003b8..cda30077390fb 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -461,18 +461,6 @@ public function getBar() } } -class PropertyCamelizedDummy -{ - private $kevinDunglas; - public $fooBar; - public $bar_foo; - - public function __construct($kevinDunglas = null) - { - $this->kevinDunglas = $kevinDunglas; - } -} - class StaticPropertyDummy { private static $property = 'value'; diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index a3b931b5243c5..26a94b2f6fd02 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -13,6 +13,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; @@ -34,6 +35,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo; @@ -382,6 +384,7 @@ public function testDeserializeObjectConstructorWithObjectTypeHint() public function testDeserializeAndSerializeAbstractObjectsWithTheClassMetadataDiscriminatorResolver() { $example = new AbstractDummyFirstChild('foo-value', 'bar-value'); + $example->setQuux(new DummyFirstChildQuux('quux')); $loaderMock = $this->getMockBuilder(ClassMetadataFactoryInterface::class)->getMock(); $loaderMock->method('hasMetadataFor')->willReturnMap([ @@ -405,9 +408,9 @@ public function testDeserializeAndSerializeAbstractObjectsWithTheClassMetadataDi ]); $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); - $serializer = new Serializer([new ObjectNormalizer(null, null, null, null, $discriminatorResolver)], ['json' => new JsonEncoder()]); + $serializer = new Serializer([new ObjectNormalizer(null, null, null, new PhpDocExtractor(), $discriminatorResolver)], ['json' => new JsonEncoder()]); - $jsonData = '{"type":"first","bar":"bar-value","foo":"foo-value"}'; + $jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","foo":"foo-value"}'; $deserialized = $serializer->deserialize($jsonData, AbstractDummy::class, 'json'); $this->assertEquals($example, $deserialized); diff --git a/src/Symfony/Component/Translation/Loader/MoFileLoader.php b/src/Symfony/Component/Translation/Loader/MoFileLoader.php index 16a3dfc618b13..d344c6e21af1c 100644 --- a/src/Symfony/Component/Translation/Loader/MoFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/MoFileLoader.php @@ -111,17 +111,12 @@ protected function loadResource($resource) $ids = ['singular' => $singularId, 'plural' => $pluralId]; $item = compact('ids', 'translated'); - if (\is_array($item['translated'])) { - $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); + if (!empty($item['ids']['singular'])) { + $id = $item['ids']['singular']; if (isset($item['ids']['plural'])) { - $plurals = []; - foreach ($item['translated'] as $plural => $translated) { - $plurals[] = sprintf('{%d} %s', $plural, $translated); - } - $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); + $id .= '|'.$item['ids']['plural']; } - } elseif (!empty($item['ids']['singular'])) { - $messages[$item['ids']['singular']] = stripcslashes($item['translated']); + $messages[$id] = stripcslashes(implode('|', (array) $item['translated'])); } } diff --git a/src/Symfony/Component/Translation/Loader/PoFileLoader.php b/src/Symfony/Component/Translation/Loader/PoFileLoader.php index 1412a786a79b7..5e460fbfb84ff 100644 --- a/src/Symfony/Component/Translation/Loader/PoFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/PoFileLoader.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Translation\Loader; /** - * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium * @copyright Copyright (c) 2012, Clemens Tolboom */ class PoFileLoader extends FileLoader @@ -20,7 +20,7 @@ class PoFileLoader extends FileLoader /** * Parses portable object (PO) format. * - * From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files * we should be able to parse files having: * * white-space @@ -126,23 +126,24 @@ protected function loadResource($resource) */ private function addMessage(array &$messages, array $item) { - if (\is_array($item['translated'])) { - $messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated'][0]); + if (!empty($item['ids']['singular'])) { + $id = stripcslashes($item['ids']['singular']); if (isset($item['ids']['plural'])) { - $plurals = $item['translated']; - // PO are by definition indexed so sort by index. - ksort($plurals); - // Make sure every index is filled. - end($plurals); - $count = key($plurals); - // Fill missing spots with '-'. - $empties = array_fill(0, $count + 1, '-'); - $plurals += $empties; - ksort($plurals); - $messages[stripcslashes($item['ids']['plural'])] = stripcslashes(implode('|', $plurals)); + $id .= '|'.stripcslashes($item['ids']['plural']); } - } elseif (!empty($item['ids']['singular'])) { - $messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated']); + + $translated = (array) $item['translated']; + // PO are by definition indexed so sort by index. + ksort($translated); + // Make sure every index is filled. + end($translated); + $count = key($translated); + // Fill missing spots with '-'. + $empties = array_fill(0, $count + 1, '-'); + $translated += $empties; + ksort($translated); + + $messages[$id] = stripcslashes(implode('|', $translated)); } } } diff --git a/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php index 63de5cebaa4dc..d6adecb1736fd 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php @@ -34,7 +34,10 @@ public function testLoadPlurals() $resource = __DIR__.'/../fixtures/plurals.mo'; $catalogue = $loader->load($resource, 'en', 'domain1'); - $this->assertEquals(['foo' => 'bar', 'foos' => '{0} bar|{1} bars'], $catalogue->all('domain1')); + $this->assertEquals([ + 'foo|foos' => 'bar|bars', + '{0} no foos|one foo|%count% foos' => '{0} no bars|one bar|%count% bars', + ], $catalogue->all('domain1')); $this->assertEquals('en', $catalogue->getLocale()); $this->assertEquals([new FileResource($resource)], $catalogue->getResources()); } diff --git a/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php index d8e2c1993ba1c..cb94e90a9408f 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php @@ -34,7 +34,10 @@ public function testLoadPlurals() $resource = __DIR__.'/../fixtures/plurals.po'; $catalogue = $loader->load($resource, 'en', 'domain1'); - $this->assertEquals(['foo' => 'bar', 'foos' => 'bar|bars'], $catalogue->all('domain1')); + $this->assertEquals([ + 'foo|foos' => 'bar|bars', + '{0} no foos|one foo|%count% foos' => '{0} no bars|one bar|%count% bars', + ], $catalogue->all('domain1')); $this->assertEquals('en', $catalogue->getLocale()); $this->assertEquals([new FileResource($resource)], $catalogue->getResources()); } @@ -89,10 +92,8 @@ public function testEscapedIdPlurals() $catalogue = $loader->load($resource, 'en', 'domain1'); $messages = $catalogue->all('domain1'); - $this->assertArrayHasKey('escaped "foo"', $messages); - $this->assertArrayHasKey('escaped "foos"', $messages); - $this->assertEquals('escaped "bar"', $messages['escaped "foo"']); - $this->assertEquals('escaped "bar"|escaped "bars"', $messages['escaped "foos"']); + $this->assertArrayHasKey('escaped "foo"|escaped "foos"', $messages); + $this->assertEquals('escaped "bar"|escaped "bars"', $messages['escaped "foo"|escaped "foos"']); } public function testSkipFuzzyTranslations() @@ -106,4 +107,16 @@ public function testSkipFuzzyTranslations() $this->assertArrayNotHasKey('foo2', $messages); $this->assertArrayHasKey('foo3', $messages); } + + public function testMissingPlurals() + { + $loader = new PoFileLoader(); + $resource = __DIR__.'/../fixtures/missing-plurals.po'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals([ + 'foo|foos' => '-|bar|-|bars', + ], $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + } } diff --git a/src/Symfony/Component/Translation/Tests/fixtures/missing-plurals.po b/src/Symfony/Component/Translation/Tests/fixtures/missing-plurals.po new file mode 100644 index 0000000000000..3b47fca805b91 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/missing-plurals.po @@ -0,0 +1,4 @@ +msgid "foo" +msgid_plural "foos" +msgstr[3] "bars" +msgstr[1] "bar" diff --git a/src/Symfony/Component/Translation/Tests/fixtures/plurals.mo b/src/Symfony/Component/Translation/Tests/fixtures/plurals.mo index 6445e77beab59..3945ad95beae6 100644 Binary files a/src/Symfony/Component/Translation/Tests/fixtures/plurals.mo and b/src/Symfony/Component/Translation/Tests/fixtures/plurals.mo differ diff --git a/src/Symfony/Component/Translation/Tests/fixtures/plurals.po b/src/Symfony/Component/Translation/Tests/fixtures/plurals.po index 439c41ad7fef4..61d1ba42b41a1 100644 --- a/src/Symfony/Component/Translation/Tests/fixtures/plurals.po +++ b/src/Symfony/Component/Translation/Tests/fixtures/plurals.po @@ -3,3 +3,5 @@ msgid_plural "foos" msgstr[0] "bar" msgstr[1] "bars" +msgid "{0} no foos|one foo|%count% foos" +msgstr "{0} no bars|one bar|%count% bars" diff --git a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php index 50b015baad04e..6737e57bccc09 100644 --- a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php @@ -21,9 +21,8 @@ * @author Tim Nagel * @author Bernhard Schussek * - * @see http://en.wikipedia.org/wiki/Bank_card_number - * @see http://www.regular-expressions.info/creditcard.html - * @see http://www.barclaycard.co.uk/business/files/Ranges_and_Rules_September_2014.pdf + * @see https://en.wikipedia.org/wiki/Payment_card_number + * @see https://www.regular-expressions.info/creditcard.html */ class CardSchemeValidator extends ConstraintValidator { diff --git a/src/Symfony/Component/Validator/Constraints/File.php b/src/Symfony/Component/Validator/Constraints/File.php index 1f4e26bfcf2fa..801f5cceb12bd 100644 --- a/src/Symfony/Component/Validator/Constraints/File.php +++ b/src/Symfony/Component/Validator/Constraints/File.php @@ -105,8 +105,10 @@ private function normalizeBinaryFormat($maxSize) $factors = [ 'k' => 1000, 'ki' => 1 << 10, - 'm' => 1000000, + 'm' => 1000 * 1000, 'mi' => 1 << 20, + 'g' => 1000 * 1000 * 1000, + 'gi' => 1 << 30, ]; if (ctype_digit((string) $maxSize)) { $this->maxSize = (int) $maxSize; diff --git a/src/Symfony/Component/Validator/README.md b/src/Symfony/Component/Validator/README.md index 3ccb2901adeac..410a4213eef07 100644 --- a/src/Symfony/Component/Validator/README.md +++ b/src/Symfony/Component/Validator/README.md @@ -13,4 +13,4 @@ Resources [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) -[1]: http://jcp.org/en/jsr/detail?id=303 +[1]: https://jcp.org/en/jsr/detail?id=303 diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf index 3a545c80b6409..e27bb2930f676 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf @@ -246,6 +246,10 @@ Error Fejl + + This value should be between {{ min }} and {{ max }}. + Værdien skal være mellem {{ min }} og {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf index f33e4d602ca15..8ee3120482267 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. Dieses Passwort ist Teil eines Datenlecks, es darf nicht verwendet werden. + + This value should be between {{ min }} and {{ max }}. + Dieser Wert sollte zwischen {{ min }} und {{ max }} sein. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index d5d9d20997fc0..100d552076f2c 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. This password has been leaked in a data breach, it must not be used. Please use another password. + + This value should be between {{ min }} and {{ max }}. + This value should be between {{ min }} and {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index f248f1cf3f20b..75cb60605a6b3 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. Esta contraseña no se puede utilizar porque está incluida en un listado de contraseñas públicas obtenido gracias a fallos de seguridad de otros sitios y aplicaciones. Por favor utilice otra contraseña. + + This value should be between {{ min }} and {{ max }}. + Este valor debe estar entre {{ min }} y {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf index ff1aa7c0b1ec0..2cfcbea4b086c 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf @@ -16,19 +16,19 @@ This value should be blank. - این فیلد باید خالی باشد. + این مقدار باید خالی باشد. The value you selected is not a valid choice. - گزینه انتخابی معتبر نیست. + مقدار انتخاب شده شامل گزینه های معتبر نمی باشد. You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. - باید حداقل {{ limit }} گزینه انتخاب کنید.|باید حداقل {{ limit }} گزینه انتخاب کنید. + باید حداقل {{ limit }} گزینه انتخاب نمایید.|باید حداقل {{ limit }} گزینه انتخاب نمایید. You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. - حداکثر {{ limit }} گزینه می توانید انتخاب کنید.|حداکثر {{ limit }} گزینه می توانید انتخاب کنید. + حداکثر {{ limit }} گزینه می توانید انتخاب نمایید.|حداکثر {{ limit }} گزینه می توانید انتخاب نمایید. One or more of the given values is invalid. @@ -44,87 +44,87 @@ This value is not a valid date. - این مقدار یک تاریخ معتبر نیست. + این مقدار یک تاریخ معتبر نمی باشد. This value is not a valid datetime. - این مقدار یک تاریخ و زمان معتبر نیست. + این مقدار یک تاریخ و زمان معتبر نمی باشد. This value is not a valid email address. - این یک رایانامه معتبر نیست. + این یک رایانامه معتبر نمی باشد. The file could not be found. - فایل پیدا نشد. + فایل یافت نشد. The file is not readable. - فایل قابلیت خواندن ندارد. + فایل قابلیت خوانده شدن ندارد. The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. - فایل بیش از اندازه بزرگ است({{ size }} {{ suffix }}). حداکثر اندازه مجاز برابر {{ limit }} {{ suffix }} است. + فایل بیش از اندازه بزرگ است({{ size }} {{ suffix }}). حداکثر اندازه مجاز برابر با {{ limit }} {{ suffix }} می باشد. The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. - این نوع فایل مجاز نیست({{ type }}). نوع های مجاز {{ types }} هستند. + این نوع فایل مجاز نمی باشد({{ type }}). نوع های مجاز شامل {{ types }} می باشند. This value should be {{ limit }} or less. - این مقدار باید کوچکتر یا مساوی {{ limit }} باشد. + این مقدار باید کوچکتر و یا مساوی {{ limit }} باشد. This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. - بسیار طولانی است.حداکثر تعداد حروف مجاز برابر {{ limit }} است.|بسیار طولانی است.حداکثر تعداد حروف مجاز برابر {{ limit }} است. + بسیار طولانی است.حداکثر تعداد حروف مجاز برابر {{ limit }} می باشد.|بسیار طولانی است.حداکثر تعداد حروف مجاز برابر {{ limit }} می باشد. This value should be {{ limit }} or more. - این مقدار باید برابر و یا بیشتر از {{ limit }} باشد. + این مقدار باید بزرگتر و یا مساوی {{ limit }} باشد. This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. - بسیار کوتاه است.تعداد حروف باید حداقل {{ limit }} باشد.|بسیار کوتاه است.تعداد حروف باید حداقل {{ limit }} باشد. + مقدار وارد شده بسیار کوتاه است.تعداد حروف وارد شده، باید حداقل شامل {{ limit }} کاراکتر باشد.|مقدار وارد شده بسیار کوتاه است.تعداد حروف وارد شده، باید حداقل شامل {{ limit }} کاراکتر باشد. This value should not be blank. - این مقدار نباید تهی باشد. + این مقدار نباید خالی باشد. This value should not be null. - باید مقداری داشته باشد.. + این مقدار باید شامل چیزی باشد. This value should be null. - نباید مقداری داشته باشد. + این مقدار باید شامل چیزی نباشد. This value is not valid. - این مقدار معتبر نیست. + این مقدار معتبر نمی باشد. This value is not a valid time. - این مقدار یک زمان صحیح نیست. + این مقدار یک زمان صحیح نمی باشد. This value is not a valid URL. - این یک URL معتبر نیست. + این مقدار شامل یک URL معتبر نمی باشد. The two values should be equal. - دو مقدار باید برابر باشند. + دو مقدار باید با یکدیگر برابر باشند. The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. - فایل بیش از اندازه بزرگ است. حداکثر اندازه مجاز برابر {{ limit }} {{ suffix }} است. + فایل بیش از اندازه بزرگ است. حداکثر اندازه مجاز برابر با {{ limit }} {{ suffix }} می باشد. The file is too large. - فایل بیش از اندازه بزرگ است. + فایل بیش از اندازه بزرگ می باشد. The file could not be uploaded. - بارگذاری فایل با شکست مواجه شد. + بارگذاری فایل با شکست مواجه گردید. This value should be a valid number. @@ -132,23 +132,23 @@ This file is not a valid image. - این فایل یک تصویر نیست. + این فایل یک تصویر نمی باشد. This is not a valid IP address. - این مقدار یک IP معتبر نیست. + این مقدار یک IP معتبر نمی باشد. This value is not a valid language. - این مقدار یک زبان صحیح نیست. + این مقدار یک زبان صحیح نمی باشد. This value is not a valid locale. - این مقدار یک محل صحیح نیست. + این مقدار یک محل صحیح نمی باشد. This value is not a valid country. - این مقدار یک کشور صحیح نیست. + این مقدار یک کشور صحیح نمی باشد. This value is already used. @@ -156,23 +156,23 @@ The size of the image could not be detected. - اندازه تصویر قابل شناسایی نیست. + اندازه تصویر قابل شناسایی نمی باشد. The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. - طول تصویر بسیار بزرگ است ({{ width }}px). بشینه طول مجاز {{ max_width }}px است. + طول تصویر بسیار بزرگ است ({{ width }}px). بشینه طول مجاز {{ max_width }}px می باشد. The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. - طول تصویر بسیار کوچک است ({{ width }}px). کمینه طول موردنظر {{ min_width }}px است. + طول تصویر بسیار کوچک است ({{ width }}px). کمینه طول موردنظر {{ min_width }}px می باشد. The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. - ارتفاع تصویر بسیار بزرگ است ({{ height }}px). بشینه ارتفاع مجاز {{ max_height }}px است. + ارتفاع تصویر بسیار بزرگ است ({{ height }}px). بشینه ارتفاع مجاز {{ max_height }}px می باشد. The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. - ارتفاع تصویر بسیار کوچک است ({{ height }}px). کمینه ارتفاع موردنظر {{ min_height }}px است. + ارتفاع تصویر بسیار کوچک است ({{ height }}px). کمینه ارتفاع موردنظر {{ min_height }}px می باشد. This value should be the user's current password. @@ -184,67 +184,67 @@ The file was only partially uploaded. - فایل به صورت جزیی بارگذاری شده است. + پرونده به صورت جزیی بارگذاری گردیده است. No file was uploaded. - هیچ فایلی بارگذاری نشد. + هیچ پرونده ای بارگذاری نگردیده است. No temporary folder was configured in php.ini. - فولدر موقت در php.ini پیکربندی نشده است. + فولدر موقت در php.ini پیکربندی نگردیده است. Cannot write temporary file to disk. - فایل موقت را نمی توان در دیسک نوشت. + فایل موقتی را نمی توان در دیسک نوشت. A PHP extension caused the upload to fail. - اکستنشن PHP موجب شد که بارگذاری فایل با شکست مواجه شود. + یک اکستنشن PHP موجب شد که بارگذاری فایل با شکست مواجه گردد. This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. - این مجموعه می بایست دارای {{ limit }} عنصر یا بیشتر باشد.|این مجموعه می بایست دارای {{ limit }} عنصر یا بیشتر باشد. + این مجموعه می بایست دارای حداقل {{ limit }} عنصر یا بیشتر باشد.|این مجموعه می بایست دارای حداقل {{ limit }} عنصر یا بیشتر باشد. This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. - این مجموعه می بایست دارای حداقل {{ limit }} عنصر یا کمتر باشد.|این مجموعه می بایست دارای {{ limit }} عنصر یا کمتر باشد. + این مجموعه می بایست دارای حداکثر {{ limit }} عنصر یا کمتر باشد.|این مجموعه می بایست دارای حداکثر {{ limit }} عنصر یا کمتر باشد. This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. - این مجموعه می بایست به طور دقیق دارا {{ limit }} عنصر باشد.|این مجموعه می بایست به طور دقیق دارای {{ limit }} قلم باشد. + این مجموعه می بایست به طور دقیق دارای {{ limit }} عنصر باشد.|این مجموعه می بایست به طور دقیق دارای {{ limit }} عنصر باشد. Invalid card number. - شماره کارت نامعتبر است. + شماره کارت نامعتبر می باشد. Unsupported card type or invalid card number. - نوع کارت پشتیبانی نمی شود یا شماره کارت نامعتبر است. + نوع کارت پشتیبانی نمی شود و یا شماره کارت نامعتبر می باشد. This is not a valid International Bank Account Number (IBAN). - این یک شماره حساب بین المللی بانک (IBAN) درست نیست. + این یک شماره حساب بانک بین المللی معتبر نمی باشد (IBAN). This value is not a valid ISBN-10. - این مقدار یک ISBN-10 درست نیست. + این مقدار یک ISBN-10 معتبر نمی باشد. This value is not a valid ISBN-13. - این مقدار یک ISBN-13 درست نیست. + این مقدار یک ISBN-13 معتبر نمی باشد. This value is neither a valid ISBN-10 nor a valid ISBN-13. - این مقدار یک ISBN-10 درست یا ISBN-13 درست نیست. + این مقدار یک ISBN-10 صحیح و یا ISBN-13 معتبر نمی باشد. This value is not a valid ISSN. - این مقدار یک ISSN درست نیست. + این مقدار یک ISSN معتبر نمی باشد. This value is not a valid currency. - این مقدار یک یکای پول درست نیست. + این مقدار یک واحد پول معتبر نمی باشد. This value should be equal to {{ compared_value }}. @@ -256,11 +256,11 @@ This value should be greater than or equal to {{ compared_value }}. - این مقدار باید بزرگتر یا مساوی با {{ compared_value }} باشد. + این مقدار باید بزرگتر و یا مساوی با {{ compared_value }} باشد. This value should be identical to {{ compared_value_type }} {{ compared_value }}. - این مقدار باید با {{ compared_value_type }} {{ compared_value }} یکی باشد. + این مقدار باید با {{ compared_value_type }} {{ compared_value }} یکسان باشد. This value should be less than {{ compared_value }}. @@ -268,7 +268,7 @@ This value should be less than or equal to {{ compared_value }}. - این مقدار باید کمتر یا مساوی با {{ compared_value }} باشد. + این مقدار باید کمتر و یا مساوی با {{ compared_value }} باشد. This value should not be equal to {{ compared_value }}. @@ -276,43 +276,43 @@ This value should not be identical to {{ compared_value_type }} {{ compared_value }}. - این مقدار نباید {{ compared_value_type }} {{ compared_value }} یکی باشد. + این مقدار نباید با {{ compared_value_type }} {{ compared_value }} یکسان باشد. The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. - ابعاد {{ ratio }} عکس بیش از حد بزرگ است.حداکثر ابعاد مجاز {{ max_ratio }} است. + ابعاد {{ ratio }} عکس بیش از حد بزرگ است.حداکثر ابعاد مجاز {{ max_ratio }} می باشد. The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. - ابعاد {{ ratio }} عکس بیش از حد کوچک است.حداقل ابعاد مجاز {{ min_ratio }} است. + ابعاد {{ ratio }} عکس بیش از حد کوچک است.حداقل ابعاد مجاز {{ min_ratio }} می باشد. The image is square ({{ width }}x{{ height }}px). Square images are not allowed. - این عکس مربع width }}x{{ height }}px}} می باشد.عکس مربع مجاز نمی باشد. + این تصویر یک مربع width }}x{{ height }}px}} می باشد.تصویر مربع مجاز نمی باشد. The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. - این عکس افقی width }}x{{ height }}px}} می باشد.عکس افقی مجاز نمی باشد. + این تصویر افقی width }}x{{ height }}px}} می باشد.تصویر افقی مجاز نمی باشد. The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. - این عکس عمودی width }}x{{ height }}px}} می باشد.عکس عمودی مجاز نمی باشد. + این تصویر عمودی width }}x{{ height }}px}} می باشد.تصویر عمودی مجاز نمی باشد. An empty file is not allowed. - فایل خالی مجاز نمی باشد. + پرونده خالی مجاز نمی باشد. The host could not be resolved. - هاست قابل حل نیست. + میزبان قابل حل نمی باشد. This value does not match the expected {{ charset }} charset. - این مقدار مورد نظر نمی باشد. مقدار مورد نظر {{ charset }} می باشد. + این مقدار مطابق با مقدار مورد انتظار {{ charset }} نمی باشد. This is not a valid Business Identifier Code (BIC). - این مقدار یک BIC درست نیست. + این مقدار یک BIC معتبر نمی باشد. Error @@ -320,7 +320,7 @@ This is not a valid UUID. - این مقدار یک UUID درست نیست. + این مقدار یک UUID معتبر نمی باشد. This value should be a multiple of {{ compared_value }}. @@ -328,7 +328,7 @@ This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. - این BIC با IBAN ارتباط ندارد. + این BIC با IBAN ارتباطی ندارد. diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 9b021cd68214f..dc7e73e3c7581 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. Ce mot de passe a été divulgué lors d'une fuite de données, il ne doit plus être utilisé. Veuillez utiliser un autre mot de passe. + + This value should be between {{ min }} and {{ max }}. + Cette valeur doit être comprise entre {{ min }} et {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf index 300eb5f68fb97..96ae6fe54ea6a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf @@ -358,6 +358,14 @@ This value is not a valid timezone. Ez az érték nem egy érvényes időzóna. + + This password has been leaked in a data breach, it must not be used. Please use another password. + Ez a jelszó korábban egy adatvédelmi incidens során illetéktelenek kezébe került, így nem használható. Kérjük, használjon másik jelszót. + + + This value should be between {{ min }} and {{ max }}. + Ennek az értéknek {{ min }} és {{ max }} között kell lennie. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf index 79171bc0dfa6e..2a079aacbf9b1 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. Slaptažodis yra nutekėjęs duomenų saugumo pažeidime, jo naudoti negalima. Prašome naudoti kitą slaptažodį. + + This value should be between {{ min }} and {{ max }}. + Ši reikšmė turi būti tarp {{ min }} ir {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf index 478ca19753a64..3b2eb4131bd3a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. Dit wachtwoord is gelekt vanwege een data-inbreuk, het moet niet worden gebruikt. Kies een ander wachtwoord. + + This value should be between {{ min }} and {{ max }}. + Deze waarde moet zich tussen {{ min }} en {{ max }} bevinden. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf index 888e73b157007..f1910c99d5751 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf @@ -330,6 +330,10 @@ This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. Ten kod BIC (Business Identifier Code) nie jest powiązany z międzynarodowym numerem rachunku bankowego (IBAN) {{ iban }}. + + This value should be between {{ min }} and {{ max }}. + Ta wartość powinna być pomiędzy {{ min }} a {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf index b77e04e8471c8..361be20f796f8 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf @@ -330,6 +330,42 @@ This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. Данный BIC не связан с IBAN {{ iban }}. + + This value should be valid JSON. + Значение должно быть корректным JSON. + + + This collection should contain only unique elements. + Эта коллекция должна содержать только уникальные элементы. + + + This value should be positive. + Значение должно быть положительным. + + + This value should be either positive or zero. + Значение должно быть положительным или равным нулю. + + + This value should be negative. + Значение должно быть отрицательным. + + + This value should be either negative or zero. + Значение должно быть отрицательным или равным нулю. + + + This value is not a valid timezone. + Значение не является корректным часовым поясом. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Данный пароль был скомпрометирован в результате утечки данных и не должен быть использован. Пожалуйста, используйте другой пароль. + + + This value should be between {{ min }} and {{ max }}. + Значение должно быть между {{ min }} и {{ max }}. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf index 5ddc429854e54..cba61915544a3 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf @@ -330,6 +330,42 @@ This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}. Банківський код (BIC) не пов’язаний із міжнародним номером банківського рахунку (IBAN) {{ iban }}. + + This value should be valid JSON. + Значення має бути корректним JSON. + + + This collection should contain only unique elements. + Ця колекція повинна мати тільки унікальни значення. + + + This value should be positive. + Значення має бути позитивним. + + + This value should be either positive or zero. + Значення має бути позитивним або дорівнювати нулю. + + + This value should be negative. + Значення має бути негативним. + + + This value should be either negative or zero. + Значення має бути негативним або дорівнювати нулю. + + + This value is not a valid timezone. + Значення не є дійсним часовим поясом. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + Цей пароль був скомпрометований в результаті витоку даних та не повинен використовуватися. Будь ласка, використовуйте інший пароль. + + + This value should be between {{ min }} and {{ max }}. + Значення має бути між {{ min }} та {{ max }}. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php index 2f27974a801ab..3a84694cc7aca 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php @@ -148,20 +148,6 @@ public function testValidComparisonToPropertyPath($comparedValue) $this->assertNoViolation(); } - /** - * @dataProvider provideValidComparisonsToPropertyPath - */ - public function testValidComparisonToPropertyPathOnArray($comparedValue) - { - $constraint = $this->createConstraint(['propertyPath' => '[root][value]']); - - $this->setObject(['root' => ['value' => 5]]); - - $this->validator->validate($comparedValue, $constraint); - - $this->assertNoViolation(); - } - public function testNoViolationOnNullObjectWithPropertyPath() { $constraint = $this->createConstraint(['propertyPath' => 'propertyPath']); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php index 174bbb2863437..4d9d8a0ea46cb 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/BicValidatorTest.php @@ -50,17 +50,6 @@ public function testValidComparisonToPropertyPath() $this->assertNoViolation(); } - public function testValidComparisonToPropertyPathOnArray() - { - $constraint = new Bic(['ibanPropertyPath' => '[root][value]']); - - $this->setObject(['root' => ['value' => 'FR14 2004 1010 0505 0001 3M02 606']]); - - $this->validator->validate('SOGEFRPP', $constraint); - - $this->assertNoViolation(); - } - public function testInvalidComparisonToPropertyPath() { $constraint = new Bic(['ibanPropertyPath' => 'value']); diff --git a/src/Symfony/Component/Validator/Tests/Constraints/FileTest.php b/src/Symfony/Component/Validator/Tests/Constraints/FileTest.php index d3117ed44a245..e0b6ec8f41f5d 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/FileTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/FileTest.php @@ -97,6 +97,10 @@ public function provideValidSizes() ['1MI', 1048576, true], ['3m', 3000000, false], ['3M', 3000000, false], + ['1gi', 1073741824, true], + ['1GI', 1073741824, true], + ['4g', 4000000000, false], + ['4G', 4000000000, false], ]; } @@ -107,8 +111,6 @@ public function provideInvalidSizes() ['foo'], ['1Ko'], ['1kio'], - ['1G'], - ['1Gi'], ]; } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintAValidator.php b/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintAValidator.php deleted file mode 100644 index 8b0d30f571421..0000000000000 --- a/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintAValidator.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Tests\Fixtures; - -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\ConstraintValidator; -use Symfony\Component\Validator\Context\ExecutionContextInterface; - -class ConstraintAValidator extends ConstraintValidator -{ - public static $passedContext; - - public function initialize(ExecutionContextInterface $context) - { - parent::initialize($context); - - self::$passedContext = $context; - } - - public function validate($value, Constraint $constraint) - { - if ('VALID' != $value) { - $this->context->addViolation('message', ['param' => 'value']); - - return; - } - } -} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/FakeClassMetadata.php b/src/Symfony/Component/Validator/Tests/Fixtures/FakeClassMetadata.php deleted file mode 100644 index 39f54777795cd..0000000000000 --- a/src/Symfony/Component/Validator/Tests/Fixtures/FakeClassMetadata.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Tests\Fixtures; - -use Symfony\Component\Validator\Mapping\ClassMetadata; - -class FakeClassMetadata extends ClassMetadata -{ - public function addCustomPropertyMetadata($propertyName, $metadata) - { - if (!isset($this->members[$propertyName])) { - $this->members[$propertyName] = []; - } - - $this->members[$propertyName][] = $metadata; - } -} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/InvalidConstraintValidator.php b/src/Symfony/Component/Validator/Tests/Fixtures/InvalidConstraintValidator.php deleted file mode 100644 index bd9a5cf6c32dc..0000000000000 --- a/src/Symfony/Component/Validator/Tests/Fixtures/InvalidConstraintValidator.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Tests\Fixtures; - -class InvalidConstraintValidator -{ -} diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php index 2bc3d11ef6d49..c9c05a0edfa36 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php @@ -511,7 +511,7 @@ public function testAddCustomizedViolation() ->setParameter('%param%', 'value') ->setInvalidValue('Invalid value') ->setPlural(2) - ->setCode(42) + ->setCode('42') ->addViolation(); }; @@ -528,7 +528,7 @@ public function testAddCustomizedViolation() $this->assertSame($entity, $violations[0]->getRoot()); $this->assertSame('Invalid value', $violations[0]->getInvalidValue()); $this->assertSame(2, $violations[0]->getPlural()); - $this->assertSame(42, $violations[0]->getCode()); + $this->assertSame('42', $violations[0]->getCode()); } public function testNoDuplicateValidationIfClassConstraintInMultipleGroups() diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index bf9e3cf356759..884e84de9b89d 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -62,8 +62,8 @@ public static function castObject($obj, $class, $hasDebugInfo = false) foreach ($a as $k => $v) { if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) { if (!isset($publicProperties[$class])) { - foreach (get_class_vars($class) as $prop => $v) { - $publicProperties[$class][$prop] = true; + foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + $publicProperties[$class][$prop->name] = true; } } if (!isset($publicProperties[$class][$k])) { diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php index 58c2117737f99..f64033811f1ef 100644 --- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php @@ -40,7 +40,7 @@ protected function doClone($var) $currentDepth = 0; // Current tree depth $currentDepthFinalIndex = 0; // Final $queue index for current tree depth $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached - $cookie = (object) []; // Unique object used to detect hard references + $cookie = (object) []; // Unique object used to detect hard references $a = null; // Array cast for nested structures $stub = null; // Stub capturing the main properties of an original item value // or null if the original value is used directly @@ -80,8 +80,15 @@ protected function doClone($var) } foreach ($vals as $k => $v) { // $v is the original value or a stub object in case of hard references - $refs[$k] = $cookie; - if ($zvalIsRef = $vals[$k] === $cookie) { + + if (\PHP_VERSION_ID >= 70400) { + $zvalIsRef = null !== \ReflectionReference::fromArrayElement($vals, $k); + } else { + $refs[$k] = $cookie; + $zvalIsRef = $vals[$k] === $cookie; + } + + if ($zvalIsRef) { $vals[$k] = &$stub; // Break hard references to make $queue completely unset($stub); // independent from the original structure if ($v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index b113fbb239244..e3845dff44acc 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -473,7 +473,7 @@ function xpathHasClass(className) { return this.current(); } this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0; - + return this.current(); }, previous: function () { @@ -481,7 +481,7 @@ function xpathHasClass(className) { return this.current(); } this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1); - + return this.current(); }, isEmpty: function () { @@ -565,11 +565,11 @@ function showCurrent(state) "sf-dump-protected", "sf-dump-private", ].map(xpathHasClass).join(' or '); - + var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); while (node = xpathResult.iterateNext()) state.nodes.push(node); - + showCurrent(state); }, 400); }); diff --git a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php index d3141c6eaf285..9c84f898bbe0e 100644 --- a/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Tests\Fixtures\Php74; /** * @author Nicolas Grekas @@ -412,7 +413,7 @@ public function testCaster() [attr] => Array ( [file] => %a%eVarClonerTest.php - [line] => 20 + [line] => 21 ) ) @@ -433,6 +434,73 @@ public function testCaster() [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 ) +EOTXT; + $this->assertStringMatchesFormat($expected, print_r($clone, true)); + } + + /** + * @requires PHP 7.4 + */ + public function testPhp74() + { + $data = new Php74(); + + $cloner = new VarCloner(); + $clone = $cloner->cloneVar($data); + + $expected = <<<'EOTXT' +Symfony\Component\VarDumper\Cloner\Data Object +( + [data:Symfony\Component\VarDumper\Cloner\Data:private] => Array + ( + [0] => Array + ( + [0] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => Symfony\Component\VarDumper\Tests\Fixtures\Php74 + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 1 + [attr] => Array + ( + ) + + ) + + ) + + [1] => Array + ( + [p1] => 123 + [p2] => Symfony\Component\VarDumper\Cloner\Stub Object + ( + [type] => 4 + [class] => stdClass + [value] => + [cut] => 0 + [handle] => %i + [refCount] => 0 + [position] => 0 + [attr] => Array + ( + ) + + ) + + ) + + ) + + [position:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [key:Symfony\Component\VarDumper\Cloner\Data:private] => 0 + [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 + [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 + [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 +) + EOTXT; $this->assertStringMatchesFormat($expected, print_r($clone, true)); } diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php new file mode 100644 index 0000000000000..724fbeb7bdb6e --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php @@ -0,0 +1,14 @@ +p2 = new \stdClass(); + } +} diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index 5a32b915e50e7..2d0870d34b7c5 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -28,6 +28,7 @@ CHANGELOG * Dispatch `CompletedEvent` on `workflow.completed` * Dispatch `AnnounceEvent` on `workflow.announce` * Added support for many `initialPlaces` + * Deprecated `DefinitionBuilder::setInitialPlace()` method, use `DefinitionBuilder::setInitialPlaces()` instead. * Deprecated the `MultipleStateMarkingStore` class, use the `MethodMarkingStore` instead. * Deprecated the `SingleStateMarkingStore` class, use the `MethodMarkingStore` instead. diff --git a/src/Symfony/Component/Workflow/Definition.php b/src/Symfony/Component/Workflow/Definition.php index 1ecbc0dfb56e0..8a6e94206d49c 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -48,13 +48,13 @@ public function __construct(array $places, array $transitions, $initialPlaces = } /** - * @deprecated since Symfony 4.3. Use the getInitialPlaces() instead. + * @deprecated since Symfony 4.3. Use getInitialPlaces() instead. * * @return string|null */ public function getInitialPlace() { - @trigger_error(sprintf('Calling %s::getInitialPlace() is deprecated. Call %s::getInitialPlaces() instead.', __CLASS__, __CLASS__)); + @trigger_error(sprintf('Calling %s::getInitialPlace() is deprecated since Symfony 4.3. Call getInitialPlaces() instead.', __CLASS__), E_USER_DEPRECATED); if (!$this->initialPlaces) { return null; diff --git a/src/Symfony/Component/Workflow/DefinitionBuilder.php b/src/Symfony/Component/Workflow/DefinitionBuilder.php index 8fa90471ddec1..4bb6df4b0dc5e 100644 --- a/src/Symfony/Component/Workflow/DefinitionBuilder.php +++ b/src/Symfony/Component/Workflow/DefinitionBuilder.php @@ -24,7 +24,7 @@ class DefinitionBuilder { private $places = []; private $transitions = []; - private $initialPlace; + private $initialPlaces; private $metadataStore; /** @@ -42,7 +42,7 @@ public function __construct(array $places = [], array $transitions = []) */ public function build() { - return new Definition($this->places, $this->transitions, $this->initialPlace, $this->metadataStore); + return new Definition($this->places, $this->transitions, $this->initialPlaces, $this->metadataStore); } /** @@ -54,20 +54,36 @@ public function clear() { $this->places = []; $this->transitions = []; - $this->initialPlace = null; + $this->initialPlaces = null; $this->metadataStore = null; return $this; } /** + * @deprecated since Symfony 4.3. Use setInitialPlaces() instead. + * * @param string $place * * @return $this */ public function setInitialPlace($place) { - $this->initialPlace = $place; + @trigger_error(sprintf('Calling %s::setInitialPlace() is deprecated since Symfony 4.3. Call setInitialPlaces() instead.', __CLASS__), E_USER_DEPRECATED); + + $this->initialPlaces = $place; + + return $this; + } + + /** + * @param string|string[]|null $initialPlaces + * + * @return $this + */ + public function setInitialPlaces($initialPlaces) + { + $this->initialPlaces = $initialPlaces; return $this; } @@ -80,7 +96,7 @@ public function setInitialPlace($place) public function addPlace($place) { if (!$this->places) { - $this->initialPlace = $place; + $this->initialPlaces = $place; } $this->places[$place] = $place; diff --git a/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php b/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php index 62351d7d9218a..df8d9bb8b36bd 100644 --- a/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php +++ b/src/Symfony/Component/Workflow/Tests/DefinitionBuilderTest.php @@ -9,6 +9,7 @@ class DefinitionBuilderTest extends TestCase { + /** @group legacy */ public function testSetInitialPlace() { $builder = new DefinitionBuilder(['a', 'b']); @@ -18,6 +19,15 @@ public function testSetInitialPlace() $this->assertEquals(['b'], $definition->getInitialPlaces()); } + public function testSetInitialPlaces() + { + $builder = new DefinitionBuilder(['a', 'b']); + $builder->setInitialPlaces('b'); + $definition = $builder->build(); + + $this->assertEquals(['b'], $definition->getInitialPlaces()); + } + public function testAddTransition() { $places = range('a', 'b'); diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php b/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php index 12c8f745e07f9..ae48d52d07ee5 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php @@ -56,6 +56,7 @@ private function createSimpleWorkflowDefinition() $placesMetadata = []; $placesMetadata['c'] = [ 'bg_color' => 'DeepSkyBlue', + 'description' => 'My custom place description', ]; $transitionsMetadata = new \SplObjectStorage(); diff --git a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml index a316b64253809..0ea138f83f725 100644 --- a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml +++ b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-marking.puml @@ -17,7 +17,8 @@ skinparam agent { } state "a" <> state "b" <> -state "c" <> +state "c" <> as c +c : My custom place description agent "t1" agent "t2" "a" -[#Purple]-> "t1": "My custom transition label 2" diff --git a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml index 348ff6476b751..02e7f396eacb3 100644 --- a/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml +++ b/src/Symfony/Component/Workflow/Tests/fixtures/puml/square/simple-workflow-nomarking.puml @@ -17,7 +17,8 @@ skinparam agent { } state "a" <> state "b" -state "c" <> +state "c" <> as c +c : My custom place description agent "t1" agent "t2" "a" -[#Purple]-> "t1": "My custom transition label 2" diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index e3df8cb0d072d..76e53eda5120e 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -43,7 +43,7 @@ public function __construct(Definition $definition, MarkingStoreInterface $marki { $this->definition = $definition; $this->markingStore = $markingStore ?: new MultipleStateMarkingStore(); - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + $this->dispatcher = null !== $dispatcher ? LegacyEventDispatcherProxy::decorate($dispatcher) : null; $this->name = $name; } diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index fdd34b618dc7b..d8645aa726084 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Yaml; +use Symfony\Component\Yaml\Tag\TaggedValue; + /** * Dumper dumps PHP variables to YAML strings. * @@ -56,7 +58,7 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): $dumpObjectAsInlineMap = empty((array) $input); } - if ($inline <= 0 || (!\is_array($input) && $dumpObjectAsInlineMap) || empty($input)) { + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { $output .= $prefix.Inline::dump($input, $flags); } else { $dumpAsMap = Inline::isHash($input); @@ -75,6 +77,19 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): continue; } + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if ($inline - 1 <= 0) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + $dumpObjectAsInlineMap = true; if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index d8d544aa28129..456d7e9b04583 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Yaml\Dumper; use Symfony\Component\Yaml\Parser; +use Symfony\Component\Yaml\Tag\TaggedValue; use Symfony\Component\Yaml\Yaml; class DumperTest extends TestCase @@ -368,6 +369,94 @@ public function testDumpingStdClassInstancesRespectsInlineLevel() inner2: c inner3: { deep1: d, deep2: e } +YAML; + $this->assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueSequenceRespectsInlineLevel() + { + $data = [ + new TaggedValue('user', [ + 'username' => 'jane', + ]), + new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 2); + + $expected = <<assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueSequenceWithInlinedTagValues() + { + $data = [ + new TaggedValue('user', [ + 'username' => 'jane', + ]), + new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 1); + + $expected = <<assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueMapRespectsInlineLevel() + { + $data = [ + 'user1' => new TaggedValue('user', [ + 'username' => 'jane', + ]), + 'user2' => new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 2); + + $expected = <<assertSame($expected, $yaml); + } + + public function testDumpingTaggedValueMapWithInlinedTagValues() + { + $data = [ + 'user1' => new TaggedValue('user', [ + 'username' => 'jane', + ]), + 'user2' => new TaggedValue('user', [ + 'username' => 'john', + ]), + ]; + + $yaml = $this->dumper->dump($data, 1); + + $expected = <<assertSame($expected, $yaml); } diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index c0acd55ceaf49..075f73a2c1a4b 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -505,6 +505,21 @@ public function testCancel() $response->getHeaders(); } + public function testCancelInStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/404'); + + foreach ($client->stream($response) as $chunk) { + $response->cancel(); + } + + $this->expectException(TransportExceptionInterface::class); + + foreach ($client->stream($response) as $chunk) { + } + } + public function testOnProgressCancel() { $client = $this->getHttpClient(__FUNCTION__);