diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e51cf88761451..2d64894f413fe 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,16 +3,18 @@ | Branch? | master for features / 2.7 up to 4.0 for bug fixes | Bug fix? | yes/no | New feature? | yes/no -| BC breaks? | yes/no +| BC breaks? | no | Deprecations? | yes/no -| Tests pass? | yes/no -| Fixed tickets | #... +| Tests pass? | yes +| Fixed tickets | #... | License | MIT -| Doc PR | symfony/symfony-docs#... +| Doc PR | symfony/symfony-docs#... diff --git a/.travis.yml b/.travis.yml index e89cf2782007c..1718bd601bfc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ cache: directories: - .phpunit - php-$MIN_PHP + - ~/php-ext services: - memcached @@ -96,7 +97,24 @@ before_install: echo apc.enable_cli = 1 >> $INI echo extension = redis.so >> $INI echo extension = memcached.so >> $INI - echo extension = mongodb.so >> $INI + + # tpecl is a helper to compile and cache php extensions + tpecl () { + local ext_name=$1 + local ext_so=$2 + local INI=$3 + local ext_dir=$(php -r "echo ini_get('extension_dir');") + local ext_cache=~/php-ext/$(basename $ext_dir)/$ext_name + + if [[ -e $ext_cache/$ext_so ]]; then + echo extension = $ext_cache/$ext_so >> $INI + else + mkdir -p $ext_cache + echo yes | pecl install -f $ext_name && + cp $ext_dir/$ext_so $ext_cache + fi + } + export -f tpecl # Matrix lines for intermediate PHP versions are skipped for pull requests if [[ ! $deps && ! $PHP = $MIN_PHP && $TRAVIS_PULL_REQUEST != false ]]; then @@ -115,8 +133,15 @@ before_install: - | # Install extra PHP extensions - if [[ ! $skip && $PHP = 7.* ]]; then - tfold ext.apcu5 'echo yes | pecl install -f apcu-5.1.6' + if [[ ! $skip ]]; then + # install libsodium + sudo add-apt-repository ppa:ondrej/php -y + sudo apt-get update -q + sudo apt-get install libsodium-dev -y + + tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI + tfold ext.libsodium tpecl libsodium sodium.so $INI + tfold ext.mongodb tpecl mongodb-1.4.0RC1 mongodb.so $INI fi - | diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index b411c42ae9c81..9deacb4981f4f 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,81 @@ in 4.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.0.0...v4.0.1 +* 4.0.5 (2018-03-01) + + * bug #26327 [Form][WCAG] Errors sign for people that do not see colors (Nyholm) + * bug #26326 [Form][WCAG] Added role="presentation" on tables & removed bootstrap4 table (Nyholm) + * bug #26325 [Form][WCAG] Add hidden labels on date and time fields (Nyholm) + * bug #26338 [Debug] Keep previous errors of Error instances (Philipp91) + * bug #26328 [Form][WCAG] Fixed HTML errors (Nyholm) + * bug #26290 [FrameworkBundle] [Console][DX] add a warning when command is not found (Simperfit) + * bug #26318 [Routing] Fix GC control of PHP-DSL (nicolas-grekas) + * bug #26312 [Routing] Don't throw 405 when scheme requirement doesn't match (nicolas-grekas) + * bug #26275 Set controller without __invoke method from invokable class (Tobion) + * bug #26298 Fix ArrayInput::toString() for InputArgument::IS_ARRAY args (maximium) + * bug #26177 Update excluded_ajax_paths for sf4 (jenaye) + * bug #26289 [Security] Add missing use of Role (tony-tran) + * bug #26286 [Security] Add missing use for RoleInterface (tony-tran) + * bug #26265 [PropertyInfo] throw exception if docblock factory does not exist (xabbuh) + * bug #26247 [Translation] Process multiple segments within a single unit. (timewasted) + * bug #26254 fix custom radios/inputs for checkbox/radio type (mssimi) + * bug #26234 [FrameworkBundle] Add missing XML config for circular_reference_handler (dunglas) + * bug #26236 [PropertyInfo] ReflectionExtractor: give a chance to other extractors if no properties (dunglas) + * bug #26227 Add support for URL-like DSNs for the PdoSessionHandler (stof) + * bug #25557 [WebProfilerBundle] add a way to limit ajax request (Simperfit) + * bug #26088 [FrameworkBundle] Fix using annotation_reader in compiler pass to inject configured cache provider (Laizerox) + * bug #26157 [HttpKernel] Send new session cookie from AbstractTestSessionListener after session invalidation (rpkamp) + * bug #26230 [WebProfilerBundle] Fix anchor CSS (ro0NL) + * bug #26228 [HttpFoundation] Fix missing "throw" in JsonResponse (nicolas-grekas) + * bug #26211 [Console] Suppress warning from sapi_windows_vt100_support (adawolfa) + * bug #26176 Retro-fit proxy code to make it deterministic for older proxy manager implementations (lstrojny) + * bug #25787 Yaml parser regression with comments and non-strings (alexpott) + * bug #26156 Fixes #26136: Avoid emitting warning in hasParameterOption() (greg-1-anderson) + * bug #26183 [DI] Add null check for removeChild (changmin.keum) + * bug #26167 [TwigBridge] Apply some changes to support Bootstrap4-stable (mpiot, Nyholm) + * bug #26173 [Security] fix accessing request values (xabbuh) + * bug #26089 [PhpUnitBridge] Added support for PHPUnit 7 in Coverage Listener (lyrixx) + * bug #26170 [PHPUnit bridge] Avoid running the remove command without any packages (stof) + * bug #26159 created validator.tl.xlf for Form/Translations (ergiegonzaga) + * bug #26100 [Routing] Throw 405 instead of 404 when redirect is not possible (nicolas-grekas) + * bug #26119 [TwigBundle][WebProfilerBundle] Fix JS collision (ro0NL) + * bug #26040 [Process] Check PHP_BINDIR before $PATH in PhpExecutableFinder (nicolas-grekas) + * bug #26067 [YAML] Issue #26065: leading spaces in YAML multi-line string literals (tamc) + * bug #26012 Exit as late as possible (greg0ire) + * bug #26082 [Cache][WebProfiler] fix collecting cache stats with sub-requests + allow clearing calls (dmaicher) + * bug #26024 [PhpBridge] add PHPUnit 7 support to SymfonyTestsListener (shieldo) + * bug #26020 [Lock] Log already-locked errors as "notice" instead of "warning" (Simperfit) + * bug #26043 [Serialized] add context to serialize and deserialize (andrey1s) + * bug #26127 Deterministic time in cache items for reproducible builds (lstrojny) + * bug #26128 Make kernel build time optionally deterministic (lstrojny) + * bug #26117 isCsrfTokenValid() replace string by ?string (GaylordP) + * bug #26112 Env var maps to undefined constant. (dsmink) + * bug #26111 [Security] fix merge of 2.7 into 2.8 + add test case (dmaicher) + * bug #25893 [Console] Fix hasParameterOption / getParameterOption when used with multiple flags (greg-1-anderson) + * bug #25756 [TwigBundle] Register TwigBridge extensions first (fancyweb) + * bug #26051 [WebProfilerBundle] Fix sub request link (ro0NL) + * bug #25947 PhpDocExtractor::getTypes() throws fatal error when type omitted (Jared Farrish) + * bug #25940 [Form] keep the context when validating forms (xabbuh) + * bug #26057 [SecurityBundle] use libsodium to run Argon2i related tests (xabbuh) + * bug #25373 Use the PCRE_DOLLAR_ENDONLY modifier in route regexes (mpdude) + * bug #24435 [Form] Make sure errors are a part of the label on bootstrap 4 - this is a requirement for WCAG2 (Nyholm) + * bug #25762 [DependencyInjection] always call the parent class' constructor (xabbuh) + * bug #25976 [Config] Handle Service/EventSubscriberInterface in ReflectionClassResource (nicolas-grekas) + * bug #25989 [DI][Routing] Fix tracking of globbed resources (nicolas-grekas, sroze) + * bug #26009 [SecurityBundle] Allow remember-me factory creation when multiple user providers are configured. (iisisrael) + * bug #26010 [CssSelector] For AND operator, the left operand should have parentheses, not only right operand (Arnaud CHASSEUX) + * bug #26000 Fixed issue #25985 (KevinFrantz) + * bug #25996 Don't show wanna-be-private services as public in debug:container (chalasr) + * bug #25914 [HttpKernel] collect extension information as late as possible (xabbuh) + * bug #25981 [DI] Fix tracking of source class changes for lazy-proxies (nicolas-grekas) + * bug #25971 [Debug] Fix bad registration of exception handler, leading to mem leak (nicolas-grekas) + * bug #25962 [Routing] Fix trailing slash redirection for non-safe verbs (nicolas-grekas) + * bug #25948 [Form] Fixed empty data on expanded ChoiceType and FileType (HeahDude) + * bug #25978 Deterministic proxy names (lstrojny) + * bug #25972 support sapi_windows_vt100_support for php 7.2+ (jhdxr) + * bug #25744 [TwigBridge] Allow label translation to be safe (MatTheCat) + * bug #25932 Don't stop PSR-4 service discovery if a parent class is missing (derrabus) + * 4.0.4 (2018-01-29) * bug #25922 [HttpFoundation] Use the correct syntax for session gc based on Pdo driver (tanasecosminromeo) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 85b3003b2abc6..96a8a8f23cc19 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,8 +16,8 @@ Symfony is the result of the work of many people who made the code better - Kévin Dunglas (dunglas) - Jakub Zalas (jakubzalas) - Kris Wallsmith (kriswallsmith) - - Ryan Weaver (weaverryan) - Robin Chalas (chalas_r) + - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) - Maxime Steinhausser (ogizanagi) - Hugo Hamon (hhamon) @@ -58,21 +58,21 @@ Symfony is the result of the work of many people who made the code better - Diego Saint Esteben (dii3g0) - Dany Maillard (maidmaid) - Konstantin Kudryashov (everzet) + - Amrouche Hamza (simperfit) - Kevin Bond (kbond) - - Bilal Amarni (bamarni) - Pierre du Plessis (pierredup) + - Bilal Amarni (bamarni) - Florin Patan (florinpatan) - Jérémy DERUSSÉ (jderusse) - - Amrouche Hamza (simperfit) + - Samuel ROZE (sroze) - Gábor Egyed (1ed) + - Alexander M. Turek (derrabus) - Michel Weimerskirch (mweimerskirch) - Andrej Hudec (pulzarraider) - - Alexander M. Turek (derrabus) - Eric Clemmons (ericclemmons) - Jáchym Toušek (enumag) - Charles Sarrazin (csarrazi) - Titouan Galopin (tgalopin) - - Samuel ROZE (sroze) - Konstantin Myakshin (koc) - Christian Raue - Arnout Boks (aboks) @@ -100,9 +100,9 @@ Symfony is the result of the work of many people who made the code better - Maxime STEINHAUSSER - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) + - David Maicher (dmaicher) - Vladimir Reznichenko (kalessil) - Brice BERNARD (brikou) - - David Maicher (dmaicher) - Baptiste Clavié (talus) - marc.weistroff - lenar @@ -112,17 +112,17 @@ Symfony is the result of the work of many people who made the code better - Florian Voutzinos (florianv) - Colin Frei - Adrien Brault (adrienbrault) + - Tomáš Votruba (tomas_votruba) - Joshua Thijssen - Peter Kokot (maastermedia) - David Buchmann (dbu) - excelwebzone - - Tomáš Votruba (tomas_votruba) + - Grégoire Paris (greg0ire) - Fabien Pennequin (fabienpennequin) - Gordon Franke (gimler) - Eric GELOEN (gelo) - Daniel Wehner (dawehner) - Tugdual Saunier (tucksaun) - - Grégoire Paris (greg0ire) - Théo FIDRY (theofidry) - Robert Schönthal (digitalkaoz) - Florian Lonqueu-Brochard (florianlb) @@ -135,11 +135,11 @@ Symfony is the result of the work of many people who made the code better - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) + - Alex Pott - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) - Andréia Bohner (andreia) - - Alex Pott - Julien Falque (julienfalque) - Rafael Dohms (rdohms) - Arnaud Kleinpeter (nanocom) @@ -147,6 +147,7 @@ Symfony is the result of the work of many people who made the code better - Mikael Pajunen - Joel Wurtz (brouznouf) - Jérôme Vasseur (jvasseur) + - Chris Wilkinson (thewilkybarkid) - Oleg Voronkovich - Philipp Wahala (hifi) - Vyacheslav Pavlov @@ -169,7 +170,6 @@ Symfony is the result of the work of many people who made the code better - GDIBass - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - James Halsall (jaitsu) - - Chris Wilkinson (thewilkybarkid) - Warnar Boekkooi (boekkooi) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) @@ -213,8 +213,10 @@ Symfony is the result of the work of many people who made the code better - Tom Van Looy (tvlooy) - Sven Paulus (subsven) - Rui Marinho (ruimarinho) + - Niels Keurentjes (curry684) - Eugene Wissner - Julien Brochet (mewt) + - Gabriel Ostrolucký - Tristan Darricau (nicofuma) - Michaël Perrin (michael.perrin) - Marcel Beerta (mazen) @@ -247,13 +249,11 @@ Symfony is the result of the work of many people who made the code better - Alessandro Chitolina - Kristen Gilden (kgilden) - Pierre-Yves LEBECQ (pylebecq) - - Niels Keurentjes (curry684) - Jordan Samouh (jordansamouh) - Jakub Kucharovic (jkucharovic) - Uwe Jäger (uwej711) - Eugene Leonovich (rybakit) - Filippo Tessarotto - - Gabriel Ostrolucký - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) - GordonsLondon @@ -285,6 +285,7 @@ Symfony is the result of the work of many people who made the code better - Robert Kiss (kepten) - Roumen Damianoff (roumen) - Antonio J. García Lagar (ajgarlag) + - Benoît Burnichon (bburnichon) - Kim Hemsø Rasmussen (kimhemsoe) - Wouter Van Hecke - Jérôme Parmentier (lctrs) @@ -293,6 +294,9 @@ Symfony is the result of the work of many people who made the code better - Michael Holm (hollo) - Marc Weistroff (futurecat) - Christian Schmidt + - Yanick Witschi (toflar) + - Edi Modrić (emodric) + - Thomas Calvet (fancyweb) - Chad Sikorra (chadsikorra) - Chris Smith (cs278) - Florian Klein (docteurklein) @@ -309,6 +313,7 @@ Symfony is the result of the work of many people who made the code better - Wouter J - Ismael Ambrosi (iambrosi) - Baptiste Lafontaine + - François Pluchino (francoispluchino) - Aurelijus Valeiša (aurelijus) - Victor Bocharsky (bocharsky_bw) - Jan Decavele (jandc) @@ -347,11 +352,9 @@ Symfony is the result of the work of many people who made the code better - Damien Alexandre (damienalexandre) - Felix Labrecque - Yaroslav Kiliba + - Maxime Veber (nek-) - Terje Bråten - - Yanick Witschi (toflar) - Robbert Klarenbeek (robbertkl) - - Edi Modrić (emodric) - - Thomas Calvet (fancyweb) - JhonnyL - David Badura (davidbadura) - hossein zolfi (ocean) @@ -364,7 +367,6 @@ Symfony is the result of the work of many people who made the code better - Philipp Kräutli (pkraeutli) - Kirill chEbba Chebunin (chebba) - Greg Thornton (xdissent) - - Benoît Burnichon (bburnichon) - Costin Bereveanu (schniper) - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) @@ -372,7 +374,6 @@ Symfony is the result of the work of many people who made the code better - Hassan Amouhzi - Tamas Szijarto - Pavel Volokitin (pvolok) - - François Pluchino (francoispluchino) - Arthur de Moulins (4rthem) - Nicolas Dewez (nicolas_dewez) - Endre Fejes @@ -452,7 +453,6 @@ Symfony is the result of the work of many people who made the code better - Zander Baldwin - Adam Harvey - Anton Bakai - - Maxime Veber (nek-) - Alex Bakhturin - Alexander Obuhovich (aik099) - boombatower @@ -575,6 +575,7 @@ Symfony is the result of the work of many people who made the code better - Hany el-Kerdany - Wang Jingyu - Åsmund Garfors + - Gunnstein Lye (glye) - Maxime Douailin - Jean Pasdeloup (pasdeloup) - Benjamin Cremer (bcremer) @@ -587,6 +588,7 @@ Symfony is the result of the work of many people who made the code better - Sebastian Marek (proofek) - Erkhembayar Gantulga (erheme318) - Michal Trojanowski + - Mateusz Sip - David Fuhr - Kamil Kokot (pamil) - Aurimas Niekis (gcds) @@ -648,6 +650,8 @@ Symfony is the result of the work of many people who made the code better - Richard van den Brand (ricbra) - develop - VJ + - Delf Tonder (leberknecht) + - Sullivan SENECHAL (soullivaneuh) - Mark Sonnabaum - Richard Quadling - jochenvdv @@ -801,6 +805,7 @@ Symfony is the result of the work of many people who made the code better - Jacques Moati - Balazs Csaba (balazscsaba2006) - Douglas Reith (douglas_reith) + - Forfarle (forfarle) - Harry Walter (haswalt) - Johnson Page (jwpage) - Ruben Gonzalez (rubenruateltek) @@ -930,6 +935,7 @@ Symfony is the result of the work of many people who made the code better - benatespina (benatespina) - Denis Kop - jfcixmedia + - Nikita Konstantinov - Martijn Evers - Benjamin Paap (benjaminpaap) - Christian @@ -975,6 +981,7 @@ Symfony is the result of the work of many people who made the code better - Jeremy Bush - wizhippo - Viacheslav Sychov + - Matt Brunt - Carlos Ortega Huetos - rpg600 - Péter Buri (burci) @@ -1026,6 +1033,7 @@ Symfony is the result of the work of many people who made the code better - Felicitus - Krzysztof Przybyszewski - Paul Matthews + - Vacheslav Silyutin - Juan Traverso - Alain Flaus (halundra) - Tarjei Huse (tarjei) @@ -1070,7 +1078,6 @@ Symfony is the result of the work of many people who made the code better - Sebastian Göttschkes (sgoettschkes) - Tatsuya Tsuruoka - Ross Tuck - - Gunnstein Lye (glye) - Kévin Gomez (kevin) - azine - Dawid Sajdak @@ -1126,6 +1133,7 @@ Symfony is the result of the work of many people who made the code better - Kim Laï Trinh - Jason Desrosiers - m.chwedziak + - Andreas Frömer - Philip Frank - Lance McNearney - Gonzalo Vilaseca (gonzalovilaseca) @@ -1144,7 +1152,6 @@ Symfony is the result of the work of many people who made the code better - WedgeSama - Felds Liscia - Chihiro Adachi (chihiro-adachi) - - Sullivan SENECHAL - Tadcka - Beth Binkovitz - Gonzalo Míguez @@ -1202,6 +1209,7 @@ Symfony is the result of the work of many people who made the code better - Simon Neidhold - Xavier HAUSHERR - Valentin VALCIU + - Jeremiah VALERIE - Kevin Dew - James Cowgill - 1ma (jautenim) @@ -1251,6 +1259,7 @@ Symfony is the result of the work of many people who made the code better - natechicago - Jonathan Poston - Adrian Olek (adrianolek) + - Jody Mickey (jwmickey) - Przemysław Piechota (kibao) - Leonid Terentyev (li0n) - ryunosuke @@ -1260,6 +1269,7 @@ Symfony is the result of the work of many people who made the code better - Iwan van Staveren (istaveren) - Povilas S. (povilas) - pborreli + - Boris Betzholz - Eric Caron - 2manypeople - Wing @@ -1409,6 +1419,7 @@ Symfony is the result of the work of many people who made the code better - vlakoff - bertillon - Bertalan Attila + - AmsTaFF (amstaff) - Yannick Bensacq (cibou) - Frédéric G. Marand (fgm) - Freek Van der Herten (freekmurze) @@ -1514,6 +1525,7 @@ Symfony is the result of the work of many people who made the code better - Javier Núñez Berrocoso (javiernuber) - Jelle Bekker (jbekker) - Ian Jenkins (jenkoian) + - Giovanni Albero (johntree) - Jorge Martin (jorgemartind) - Joeri Verdeyen (jverdeyen) - Dmitrii Poddubnyi (karser) @@ -1560,6 +1572,7 @@ Symfony is the result of the work of many people who made the code better - Ismail Asci (ismailasci) - Simon CONSTANS (kosssi) - Kristof Van Cauwenbergh (kristofvc) + - Dennis Langen (nijusan) - Paulius Jarmalavičius (pjarmalavicius) - Ramon Henrique Ornelas (ramonornela) - Ricardo de Vries (ricknox) @@ -1598,6 +1611,7 @@ Symfony is the result of the work of many people who made the code better - lol768 - jamogon - Vyacheslav Slinko + - Jakub Chábek - Johannes - Jörg Rühl - wesleyh @@ -1606,6 +1620,7 @@ Symfony is the result of the work of many people who made the code better - Michael Genereux - patrick-mcdougle - Dariusz Czech + - Jack Wright - Anonymous User - Paweł Tomulik - Eric J. Duran @@ -1728,7 +1743,6 @@ Symfony is the result of the work of many people who made the code better - Fabien D. (fabd) - Carsten Eilers (fnc) - Sorin Gitlan (forapathy) - - Forfarle (forfarle) - Yohan Giarelli (frequence-web) - Gerry Vandermaesen (gerryvdm) - Ghazy Ben Ahmed (ghazy) diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index ee9d187638fab..4eb1a0ed2a923 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -163,7 +163,18 @@ DependencyInjection autowire: true ``` - * Autowiring services based on the types they implement is not supported anymore. Rename (or alias) your services to their FQCN id to make them autowirable. + * Autowiring services based on the types they implement is not supported anymore. + It will only look for an alias or a service id that matches a given FQCN. + Rename (or alias) your services to their FQCN id to make them autowirable. + In 3.4, you can activate this behavior instead of having deprecation messages + by setting the following parameter: + + ```yml + parameters: + container.autowiring.strict_mode: true + ``` + + From 4.0, you can remove it as it's the default behavior and the parameter is not handled anymore. * `_defaults` and `_instanceof` are now reserved service names in Yaml configurations. Please rename any services with that names. @@ -701,7 +712,7 @@ Process * Extending `Process::run()`, `Process::mustRun()` and `Process::restart()` is not supported anymore. - + * The `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class have been removed. Profiler diff --git a/link b/link index a5030d0683379..6a2ec15e22102 100755 --- a/link +++ b/link @@ -37,7 +37,12 @@ if (!is_dir("$argv[1]/vendor/symfony")) { $sfPackages = array('symfony/symfony' => __DIR__); $filesystem = new Filesystem(); -foreach (glob(__DIR__.'/src/Symfony/{Bundle,Bridge,Component,Component/Security}/*', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { +$braces = array('Bundle', 'Bridge', 'Component', 'Component/Security'); +$directories = call_user_func_array('array_merge', array_values(array_map(function ($part) { + return glob(__DIR__.'/src/Symfony/'.$part.'/*', GLOB_ONLYDIR | GLOB_NOSORT); +}, $braces))); + +foreach ($directories as $dir) { if ($filesystem->exists($composer = "$dir/composer.json")) { $sfPackages[json_decode(file_get_contents($composer))->name] = $dir; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php index d1596a1968b76..490a48cfe6c6e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php @@ -40,7 +40,7 @@ public function testResetService() $registry->resetManager(); $this->assertSame($foo, $container->get('foo')); - $this->assertFalse(isset($foo->bar)); + $this->assertObjectNotHasAttribute('bar', $foo); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php index d94b1d66fecf4..aeb3b55fdc49f 100644 --- a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -43,7 +43,7 @@ public function testGetLogsWithDebugProcessor() $logger = new Logger(__METHOD__, array($handler), array($processor)); $this->assertTrue($logger->error('error message')); - $this->assertSame(1, count($logger->getLogs())); + $this->assertCount(1, $logger->getLogs()); } public function testCountErrorsWithDebugProcessor() diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 3d6b68a7875fb..3c21d4cc7c8c7 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -16,6 +16,8 @@ CHANGELOG ----- * added a `CoverageListener` to enhance the code coverage report + * all deprecations but those from tests marked with `@group legacy` are always + displayed when not in `weak` mode 3.3.0 ----- diff --git a/src/Symfony/Bridge/PhpUnit/CoverageListener.php b/src/Symfony/Bridge/PhpUnit/CoverageListener.php index e6b4e7ec98b8b..79333fbd00221 100644 --- a/src/Symfony/Bridge/PhpUnit/CoverageListener.php +++ b/src/Symfony/Bridge/PhpUnit/CoverageListener.php @@ -11,34 +11,10 @@ namespace Symfony\Bridge\PhpUnit; -use PHPUnit\Framework\BaseTestListener; -use PHPUnit\Framework\Test; -use Symfony\Bridge\PhpUnit\Legacy\CoverageListenerTrait; - if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListener', 'Symfony\Bridge\PhpUnit\CoverageListener'); -// Using an early return instead of a else does not work when using the PHPUnit -// phar due to some weird PHP behavior (the class gets defined without executing -// the code before it and so the definition is not properly conditional) + class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV5', 'Symfony\Bridge\PhpUnit\CoverageListener'); +} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV6', 'Symfony\Bridge\PhpUnit\CoverageListener'); } else { - /** - * CoverageListener adds `@covers ` on each test suite when possible - * to make the code coverage more accurate. - * - * @author Grégoire Pineau - */ - class CoverageListener extends BaseTestListener - { - private $trait; - - public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) - { - $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); - } - - public function startTest(Test $test) - { - $this->trait->startTest($test); - } - } + class_alias('Symfony\Bridge\PhpUnit\Legacy\CoverageListenerForV7', 'Symfony\Bridge\PhpUnit\CoverageListener'); } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index dcdb0df8efddf..e14d218708c77 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -110,8 +110,7 @@ public static function register($mode = 0) $trace = debug_backtrace(true); $group = 'other'; - $isVendor = false; - $isWeak = DeprecationErrorHandler::MODE_WEAK === $mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = $inVendors($file)); + $isVendor = DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $inVendors($file); $i = count($trace); while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_') || 0 === strpos($trace[$i]['class'], 'PHPUnit\\')))) { @@ -128,7 +127,7 @@ public static function register($mode = 0) // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() // then we need to use the serialized information to determine // if the error has been triggered from vendor code. - $isWeak = DeprecationErrorHandler::MODE_WEAK === $mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor = isset($parsedMsg['triggering_file']) && $inVendors($parsedMsg['triggering_file'])); + $isVendor = DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && isset($parsedMsg['triggering_file']) && $inVendors($parsedMsg['triggering_file']); } else { $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class']; $method = $trace[$i]['function']; @@ -145,7 +144,7 @@ public static function register($mode = 0) || in_array('legacy', $Test::getGroups($class, $method), true) ) { $group = 'legacy'; - } elseif (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode && $isVendor) { + } elseif ($isVendor) { $group = 'remaining vendor'; } else { $group = 'remaining'; @@ -165,13 +164,13 @@ public static function register($mode = 0) exit(1); } - if ('legacy' !== $group && !$isWeak) { + if ('legacy' !== $group && DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; $ref = &$deprecations[$group][$msg][$class.'::'.$method]; ++$ref; } - } elseif (!$isWeak) { + } elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; } @@ -217,39 +216,60 @@ public static function register($mode = 0) $groups = array('unsilenced', 'remaining'); if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) { - $groups[] = 'remaining vendor'; + $groups[] = 'remaining vendor'; } array_push($groups, 'legacy', 'other'); - foreach ($groups as $group) { - if ($deprecations[$group.'Count']) { - echo "\n", $colorize( - sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), - 'legacy' !== $group && 'remaining vendor' !== $group - ), "\n"; + $displayDeprecations = function ($deprecations) use ($colorize, $cmp, $groups) { + foreach ($groups as $group) { + if ($deprecations[$group.'Count']) { + echo "\n", $colorize( + sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), + 'legacy' !== $group && 'remaining vendor' !== $group + ), "\n"; - uasort($deprecations[$group], $cmp); + uasort($deprecations[$group], $cmp); - foreach ($deprecations[$group] as $msg => $notices) { - echo "\n ", $notices['count'], 'x: ', $msg, "\n"; + foreach ($deprecations[$group] as $msg => $notices) { + echo "\n ", $notices['count'], 'x: ', $msg, "\n"; - arsort($notices); + arsort($notices); - foreach ($notices as $method => $count) { - if ('count' !== $method) { - echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + foreach ($notices as $method => $count) { + if ('count' !== $method) { + echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + } } } } } - } - if (!empty($notices)) { - echo "\n"; - } + if (!empty($notices)) { + echo "\n"; + } + }; - if (DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { - exit(1); + $displayDeprecations($deprecations); + + // store failing status + $isFailing = DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']; + + // reset deprecations array + foreach ($deprecations as $group => $arrayOrInt) { + $deprecations[$group] = is_int($arrayOrInt) ? 0 : array(); } + + register_shutdown_function(function () use (&$deprecations, $isFailing, $displayDeprecations, $mode) { + foreach ($deprecations as $group => $arrayOrInt) { + if (0 < (is_int($arrayOrInt) ? $arrayOrInt : count($arrayOrInt))) { + echo "Shutdown-time deprecations:\n"; + break; + } + } + $displayDeprecations($deprecations); + if ($isFailing || DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { + exit(1); + } + }); }); } } @@ -281,7 +301,8 @@ private static function hasColorSupport() { if ('\\' === DIRECTORY_SEPARATOR) { return - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD + defined('STDOUT') && function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT) + || '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListener.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php similarity index 91% rename from src/Symfony/Bridge/PhpUnit/Legacy/CoverageListener.php rename to src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php index 0227828515760..4bf19223f3c16 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListener.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV5.php @@ -19,7 +19,7 @@ * * @internal */ -class CoverageListener extends \PHPUnit_Framework_BaseTestListener +class CoverageListenerForV5 extends \PHPUnit_Framework_BaseTestListener { private $trait; diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php new file mode 100644 index 0000000000000..f0dfc7577cd03 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV6.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\Framework\BaseTestListener; +use PHPUnit\Framework\Test; + +/** + * CoverageListener adds `@covers ` on each test suite when possible + * to make the code coverage more accurate. + * + * @author Grégoire Pineau + * + * @internal + */ +class CoverageListenerForV6 extends BaseTestListener +{ + private $trait; + + public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) + { + $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); + } + + public function startTest(Test $test) + { + $this->trait->startTest($test); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php new file mode 100644 index 0000000000000..699d4bde35cc4 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/CoverageListenerForV7.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestListenerDefaultImplementation; +use PHPUnit\Framework\TestSuite; + +/** + * CoverageListener adds `@covers ` on each test suite when possible + * to make the code coverage more accurate. + * + * @author Grégoire Pineau + * + * @internal + */ +class CoverageListenerForV7 implements TestListener +{ + use TestListenerDefaultImplementation; + + private $trait; + + public function __construct(callable $sutFqcnResolver = null, $warningOnSutNotFound = false) + { + $this->trait = new CoverageListenerTrait($sutFqcnResolver, $warningOnSutNotFound); + } + + public function startTestSuite(TestSuite $suite): void + { + $this->trait->startTest($test); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php similarity index 76% rename from src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php rename to src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php index 5ed545a5127cc..2da40f2c204f1 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php @@ -18,7 +18,7 @@ * * @internal */ -class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener +class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener { private $trait; @@ -34,26 +34,26 @@ public function globalListenerDisabled() public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { - return $this->trait->startTestSuite($suite); + $this->trait->startTestSuite($suite); } public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) { - return $this->trait->addSkippedTest($test, $e, $time); + $this->trait->addSkippedTest($test, $e, $time); } public function startTest(\PHPUnit_Framework_Test $test) { - return $this->trait->startTest($test); + $this->trait->startTest($test); } public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) { - return $this->trait->addWarning($test, $e, $time); + $this->trait->addWarning($test, $e, $time); } public function endTest(\PHPUnit_Framework_Test $test, $time) { - return $this->trait->endTest($test, $time); + $this->trait->endTest($test, $time); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php new file mode 100644 index 0000000000000..5b864bfe58a32 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.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\Bridge\PhpUnit\Legacy; + +use PHPUnit\Framework\BaseTestListener; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + * + * @internal + */ +class SymfonyTestsListenerForV6 extends BaseTestListener +{ + private $trait; + + public function __construct(array $mockedNamespaces = array()) + { + $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); + } + + public function globalListenerDisabled() + { + $this->trait->globalListenerDisabled(); + } + + public function startTestSuite(TestSuite $suite) + { + $this->trait->startTestSuite($suite); + } + + public function addSkippedTest(Test $test, \Exception $e, $time) + { + $this->trait->addSkippedTest($test, $e, $time); + } + + public function startTest(Test $test) + { + $this->trait->startTest($test); + } + + public function addWarning(Test $test, Warning $e, $time) + { + $this->trait->addWarning($test, $e, $time); + } + + public function endTest(Test $test, $time) + { + $this->trait->endTest($test, $time); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php new file mode 100644 index 0000000000000..18bbdbeba0f03 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestListenerDefaultImplementation; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + * + * @internal + */ +class SymfonyTestsListenerForV7 implements TestListener +{ + use TestListenerDefaultImplementation; + + private $trait; + + public function __construct(array $mockedNamespaces = array()) + { + $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); + } + + public function globalListenerDisabled() + { + $this->trait->globalListenerDisabled(); + } + + public function startTestSuite(TestSuite $suite): void + { + $this->trait->startTestSuite($suite); + } + + public function addSkippedTest(Test $test, \Throwable $t, float $time): void + { + $this->trait->addSkippedTest($test, $t, $time); + } + + public function startTest(Test $test): void + { + $this->trait->startTest($test); + } + + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->trait->addWarning($test, $e, $time); + } + + public function endTest(Test $test, float $time): void + { + $this->trait->endTest($test, $time); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php index 4bbf2f1cb9724..8a57416bd241f 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/TestRunner.php @@ -23,7 +23,7 @@ class TestRunner extends \PHPUnit_TextUI_TestRunner */ protected function handleConfiguration(array &$arguments) { - $listener = new SymfonyTestsListener(); + $listener = new SymfonyTestsListenerForV5(); $result = parent::handleConfiguration($arguments); @@ -32,7 +32,7 @@ protected function handleConfiguration(array &$arguments) $registeredLocally = false; foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListener) { + if ($registeredListener instanceof SymfonyTestsListenerForV5) { $registeredListener->globalListenerDisabled(); $registeredLocally = true; break; diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php index c11fde9526eab..d46a79e00eadc 100644 --- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php @@ -11,60 +11,10 @@ namespace Symfony\Bridge\PhpUnit; -use PHPUnit\Framework\BaseTestListener; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; - if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); -// Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class -// gets defined without executing the code before it and so the definition is not properly conditional) + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); +} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); } else { - /** - * Collects and replays skipped tests. - * - * @author Nicolas Grekas - * - * @final - */ - class SymfonyTestsListener extends BaseTestListener - { - private $trait; - - public function __construct(array $mockedNamespaces = array()) - { - $this->trait = new Legacy\SymfonyTestsListenerTrait($mockedNamespaces); - } - - public function globalListenerDisabled() - { - $this->trait->globalListenerDisabled(); - } - - public function startTestSuite(TestSuite $suite) - { - return $this->trait->startTestSuite($suite); - } - - public function addSkippedTest(Test $test, \Exception $e, $time) - { - return $this->trait->addSkippedTest($test, $e, $time); - } - - public function startTest(Test $test) - { - return $this->trait->startTest($test); - } - - public function addWarning(Test $test, Warning $e, $time) - { - return $this->trait->addWarning($test, $e, $time); - } - - public function endTest(Test $test, $time) - { - return $this->trait->endTest($test, $time); - } - } + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php index b408b74010bd9..008ba437d83a9 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/CoverageListenerTest.php @@ -12,7 +12,9 @@ public function test() $this->markTestSkipped('This test cannot be run on Windows.'); } - if (\PHP_VERSION_ID >= 70000) { + exec('type phpdbg', $output, $returnCode); + + if (\PHP_VERSION_ID >= 70000 && 0 === $returnCode) { $php = 'phpdbg -qrr'; } else { exec('php --ri xdebug -d zend_extension=xdebug.so 2> /dev/null', $output, $returnCode); diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt index 39a3e985865fd..7a0595a7ddebc 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -59,6 +59,10 @@ $foo = new FooTestCase(); $foo->testLegacyFoo(); $foo->testNonLegacyBar(); +register_shutdown_function(function () { + exit('I get precedence over any exit statements inside the deprecation error handler.'); +}); + ?> --EXPECTF-- Unsilenced deprecation notices (3) @@ -80,3 +84,4 @@ Other deprecation notices (1) 1x: root deprecation +I get precedence over any exit statements inside the deprecation error handler. diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/deprecation_riddled.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/deprecation_riddled.php index 6f5123d4feb0c..16a58246d2115 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/deprecation_riddled.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/deprecation_riddled.php @@ -21,6 +21,8 @@ public function testLegacyFoo() { @trigger_error('silenced foo deprecation', E_USER_DEPRECATED); trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED); + @trigger_error('silenced foo deprecation', E_USER_DEPRECATED); + trigger_error('unsilenced foo deprecation', E_USER_DEPRECATED); } public function testNonLegacyBar() diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt new file mode 100644 index 0000000000000..fddeed6085dea --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt @@ -0,0 +1,91 @@ +--TEST-- +Test DeprecationErrorHandler in default mode +--FILE-- +testLegacyFoo(); +$foo->testNonLegacyBar(); + +register_shutdown_function(function () { + @trigger_error('root deprecation during shutdown', E_USER_DEPRECATED); +}); + +?> +--EXPECTF-- +Unsilenced deprecation notices (3) + + 2x: unsilenced foo deprecation + 2x in FooTestCase::testLegacyFoo + + 1x: unsilenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Remaining deprecation notices (1) + + 1x: silenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Legacy deprecation notices (1) + +Other deprecation notices (1) + + 1x: root deprecation + +Shutdown-time deprecations: + +Other deprecation notices (1) + + 1x: root deprecation during shutdown diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt index 9e78d96e70efb..8137a2ff44876 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt @@ -37,4 +37,3 @@ Unsilenced deprecation notices (1) Legacy deprecation notices (1) Other deprecation notices (1) - diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt index 6bba1c86be5ac..8fa436e20178b 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_eval_d_deprecation.phpt @@ -21,3 +21,5 @@ eval("@trigger_error('who knows where I come from?', E_USER_DEPRECATED);") --EXPECTF-- Other deprecation notices (1) + + 1x: who knows where I come from? diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_phar_deprecation.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_phar_deprecation.phpt index 4c4879e61156d..7a583f95337d9 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_phar_deprecation.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_phar_deprecation.phpt @@ -23,3 +23,5 @@ include 'phar://deprecation.phar/deprecation.php'; --EXPECTF-- Other deprecation notices (1) + + 1x: I come from… afar! :D diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_vendor.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_vendor.phpt index 7e9c6f8ed7682..68e233df7d0d9 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_vendor.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak_vendors_on_vendor.phpt @@ -20,10 +20,21 @@ require __DIR__.'/fake_vendor/acme/lib/deprecation_riddled.php'; ?> --EXPECTF-- -Unsilenced deprecation notices (2) +Unsilenced deprecation notices (3) + + 2x: unsilenced foo deprecation + 2x in FooTestCase::testLegacyFoo + + 1x: unsilenced bar deprecation + 1x in FooTestCase::testNonLegacyBar Remaining vendor deprecation notices (1) -Legacy deprecation notices (1) + 1x: silenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Legacy deprecation notices (2) Other deprecation notices (1) + + 1x: root deprecation diff --git a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php index 925f4831ac89d..241006431acea 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/Fixtures/coverage/tests/bootstrap.php @@ -13,7 +13,13 @@ require __DIR__.'/../src/FooCov.php'; require __DIR__.'/../../../../Legacy/CoverageListenerTrait.php'; + if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - require __DIR__.'/../../../../Legacy/CoverageListener.php'; + require_once __DIR__.'/../../../../Legacy/CoverageListenerForV5.php'; +} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { + require_once __DIR__.'/../../../../Legacy/CoverageListenerForV6.php'; +} else { + require_once __DIR__.'/../../../../Legacy/CoverageListenerForV7.php'; } + require __DIR__.'/../../../../CoverageListener.php'; diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index 408533292a656..869a8a8dbe85d 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -15,22 +15,20 @@ if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { class_alias('Symfony\Bridge\PhpUnit\Legacy\Command', 'Symfony\Bridge\PhpUnit\TextUI\Command'); - - return; -} - -/** - * {@inheritdoc} - * - * @internal - */ -class Command extends BaseCommand -{ +} else { /** * {@inheritdoc} + * + * @internal */ - protected function createRunner() + class Command extends BaseCommand { - return new TestRunner($this->arguments['loader']); + /** + * {@inheritdoc} + */ + protected function createRunner() + { + return new TestRunner($this->arguments['loader']); + } } } diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php index 07fe2d165b627..4e1fdca4d54be 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php @@ -16,42 +16,40 @@ if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunner', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner'); - - return; -} - -/** - * {@inheritdoc} - * - * @internal - */ -class TestRunner extends BaseRunner -{ +} else { /** * {@inheritdoc} + * + * @internal */ - protected function handleConfiguration(array &$arguments) + class TestRunner extends BaseRunner { - $listener = new SymfonyTestsListener(); + /** + * {@inheritdoc} + */ + protected function handleConfiguration(array &$arguments) + { + $listener = new SymfonyTestsListener(); - $result = parent::handleConfiguration($arguments); + $result = parent::handleConfiguration($arguments); - $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); + $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - $registeredLocally = false; + $registeredLocally = false; - foreach ($arguments['listeners'] as $registeredListener) { - if ($registeredListener instanceof SymfonyTestsListener) { - $registeredListener->globalListenerDisabled(); - $registeredLocally = true; - break; + foreach ($arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } } - } - if (!$registeredLocally) { - $arguments['listeners'][] = $listener; - } + if (!$registeredLocally) { + $arguments['listeners'][] = $listener; + } - return $result; + return $result; + } } } diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index fbcedeca8c6ab..2f97e32e11819 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -17,7 +17,7 @@ error_reporting(-1); if (PHP_VERSION_ID >= 70200) { // PHPUnit 6 is required for PHP 7.2+ - $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '6.3'; + $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '6.5'; } elseif (PHP_VERSION_ID >= 50600) { // PHPUnit 4 does not support PHP 7 $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '5.7'; @@ -77,7 +77,9 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ $zip->extractTo(getcwd()); $zip->close(); chdir("phpunit-$PHPUNIT_VERSION"); - passthru("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE); + if ($SYMFONY_PHPUNIT_REMOVE) { + passthru("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE); + } if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) { passthru("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\""); } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 0eaa6e844d4a1..ceaa1febc6202 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -13,6 +13,7 @@ use ProxyManager\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; +use ProxyManager\Version; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; @@ -22,7 +23,7 @@ * * @author Marco Pivetta * - * @final since version 3.3 + * @final */ class ProxyDumper implements DumperInterface { @@ -88,27 +89,43 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = */ public function getProxyCode(Definition $definition) { - return preg_replace( + $code = $this->classGenerator->generate($this->generateProxyClass($definition)); + + $code = preg_replace( '/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/', '$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;', - $this->classGenerator->generate($this->generateProxyClass($definition)) + $code ); + + if (version_compare(self::getProxyManagerVersion(), '2.2', '<')) { + $code = preg_replace( + '/((?:\$(?:this|initializer|instance)->)?(?:publicProperties|initializer|valueHolder))[0-9a-f]++/', + '${1}'.$this->getIdentifierSuffix($definition), + $code + ); + } + + return $code; + } + + private static function getProxyManagerVersion(): string + { + if (!\class_exists(Version::class)) { + return '0.0.1'; + } + + return defined(Version::class.'::VERSION') ? Version::VERSION : Version::getVersion(); } /** * Produces the proxy class name for the given definition. - * - * @return string */ - private function getProxyClassName(Definition $definition) + private function getProxyClassName(Definition $definition): string { - return preg_replace('/^.*\\\\/', '', $definition->getClass()).'_'.substr(hash('sha256', spl_object_hash($definition).$this->salt), -7); + return preg_replace('/^.*\\\\/', '', $definition->getClass()).'_'.$this->getIdentifierSuffix($definition); } - /** - * @return ClassGenerator - */ - private function generateProxyClass(Definition $definition) + private function generateProxyClass(Definition $definition): ClassGenerator { $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); @@ -116,4 +133,9 @@ private function generateProxyClass(Definition $definition) return $generatedClass; } + + private function getIdentifierSuffix(Definition $definition): string + { + return substr(hash('sha256', $definition->getClass().$this->salt), -7); + } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index 6dee01fccec85..725d4246bac0b 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -58,6 +58,14 @@ public function testGetProxyCode() ); } + public function testDeterministicProxyCode() + { + $definition = new Definition(__CLASS__); + $definition->setLazy(true); + + $this->assertSame($this->dumper->getProxyCode($definition), $this->dumper->getProxyCode($definition)); + } + public function testGetProxyFactoryCode() { $definition = new Definition(__CLASS__); diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index cc9dfe36de64b..bbc5e3a32299b 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -259,6 +259,6 @@ protected static function fixCodeMarkup($line) $line .= ''; } - return $line; + return trim($line); } } diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 4875b1fab8452..57e8902dce98a 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -91,7 +91,7 @@ public function getUrl($name, $parameters = array(), $schemeRelative = false) * * @return array An array with the contexts the URL is safe * - * @final since version 3.4 + * @final */ public function isUrlGenerationSafe(Node $argsNode) { diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig index e236d12cb709a..e23e6f8a29d09 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig @@ -28,7 +28,6 @@ col-sm-2 {{- form_label(form) -}}
{{- form_widget(form) -}} - {{- form_errors(form) -}}
{##} {%- endif -%} @@ -40,7 +39,6 @@ col-sm-2 {{- form_label(form) -}}
{{- form_widget(form) -}} - {{- form_errors(form) -}}
{##} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig index 15413f1c9a0fe..a3484d9036bec 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig @@ -3,11 +3,25 @@ {# Widgets #} {% block money_widget -%} - {% if not valid %} - {% set group_class = ' form-control is-invalid' %} - {% set valid = true %} - {% endif %} - {{- parent() -}} + {%- set prepend = not (money_pattern starts with '{{') -%} + {%- set append = not (money_pattern ends with '}}') -%} + {%- if prepend or append -%} +
+ {%- if prepend -%} +
+ {{ money_pattern|replace({ '{{ widget }}':''}) }} +
+ {%- endif -%} + {{- block('form_widget_simple') -}} + {%- if append -%} +
+ {{ money_pattern|replace({ '{{ widget }}':''}) }} +
+ {%- endif -%} +
+ {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} {%- endblock money_widget %} {% block datetime_widget -%} @@ -39,14 +53,65 @@ {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%} {% set valid = true %} {%- endif -%} - {{- parent() -}} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
+ {%- if with_years -%} +
+ {{ form_label(form.years) }} + {{ form_widget(form.years) }} +
+ {%- endif -%} + {%- if with_months -%} +
+ {{ form_label(form.months) }} + {{ form_widget(form.months) }} +
+ {%- endif -%} + {%- if with_weeks -%} +
+ {{ form_label(form.weeks) }} + {{ form_widget(form.weeks) }} +
+ {%- endif -%} + {%- if with_days -%} +
+ {{ form_label(form.days) }} + {{ form_widget(form.days) }} +
+ {%- endif -%} + {%- if with_hours -%} +
+ {{ form_label(form.hours) }} + {{ form_widget(form.hours) }} +
+ {%- endif -%} + {%- if with_minutes -%} +
+ {{ form_label(form.minutes) }} + {{ form_widget(form.minutes) }} +
+ {%- endif -%} + {%- if with_seconds -%} +
+ {{ form_label(form.seconds) }} + {{ form_widget(form.seconds) }} +
+ {%- endif -%} + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
+ {%- endif -%} {%- endblock dateinterval_widget %} {% block percent_widget -%}
{% set valid = true %} {{- block('form_widget_simple') -}} - % +
+ % +
{%- endblock percent_widget %} @@ -54,12 +119,16 @@ {% if type is not defined or type != 'hidden' %} {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control' ~ (type|default('') == 'file' ? '-file' : ''))|trim}) -%} {% endif %} + {%- if type is defined and (type == 'range' or type == 'color') %} + {# Attribute "required" is not supported #} + {%- set required = false -%} + {% endif %} {{- parent() -}} {%- endblock form_widget_simple %} {%- block widget_attributes -%} {%- if not valid %} - {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} {% endif -%} {{ parent() }} {%- endblock widget_attributes -%} @@ -71,15 +140,14 @@ {% block checkbox_widget -%} {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} - {% if 'checkbox-inline' in parent_label_class %} - {{- form_label(form, null, { widget: parent() }) -}} - {% elseif 'form-check-inline' in parent_label_class %} -
+ {%- if 'checkbox-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
{{- form_label(form, null, { widget: parent() }) -}}
- {% else -%} -
+ {%- else -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} +
{{- form_label(form, null, { widget: parent() }) -}}
{%- endif -%} @@ -87,18 +155,21 @@ {% block radio_widget -%} {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} - {%- if 'radio-inline' in parent_label_class -%} - {{- form_label(form, null, { widget: parent() }) -}} + {%- if 'radio-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
+ {{- form_label(form, null, { widget: parent() }) -}} +
{%- else -%} -
+ {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} +
{{- form_label(form, null, { widget: parent() }) -}}
{%- endif -%} {%- endblock radio_widget %} {% block choice_widget_expanded -%} - {% if '-inline' in label_attr.class|default('') -%} +
{%- for child in form %} {{- form_widget(child, { parent_label_class: label_attr.class|default(''), @@ -106,43 +177,54 @@ valid: valid, }) -}} {% endfor -%} - {%- else -%} - {%- if not valid -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) %} - {%- endif -%} -
- {%- for child in form %} - {{- form_widget(child, { - parent_label_class: label_attr.class|default(''), - translation_domain: choice_translation_domain, - valid: true, - }) -}} - {% endfor -%} -
- {%- endif %} +
{%- endblock choice_widget_expanded %} {# Labels #} {% block form_label -%} - {%- if compound is defined and compound -%} - {%- set element = 'legend' -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-legend')|trim}) -%} - {%- else -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-control-label')|trim}) -%} + {% if label is not same as(false) -%} + {%- if compound is defined and compound -%} + {%- set element = 'legend' -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- else -%} + {%- set label_attr = label_attr|merge({for: id, class: (label_attr.class|default('') ~ ' form-control-label')|trim}) -%} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + {% if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}{{- form_errors(form) -}} {%- endif -%} - {{- parent() -}} {%- endblock form_label %} {% block checkbox_radio_label -%} {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} {%- if widget is defined -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class) %} + {% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class) %} + {%- if is_parent_custom or is_custom -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%} + {%- else %} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {%- endif %} + {%- if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} {%- if required -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} {%- endif -%} {%- if parent_label_class is defined -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': ''})|trim}) -%} {%- endif -%} {%- if label is not same as(false) and label is empty -%} {%- if label_format is not empty -%} @@ -154,8 +236,11 @@ {%- set label = name|humanize -%} {%- endif -%} {%- endif -%} + + {{ widget|raw }} - {{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}} + {{- label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}} + {{- form_errors(form) -}} {%- endif -%} {%- endblock checkbox_radio_label %} @@ -169,7 +254,6 @@ <{{ element|default('div') }} class="form-group"> {{- form_label(form) -}} {{- form_widget(form) -}} - {{- form_errors(form) -}} {%- endblock form_row %} @@ -177,12 +261,12 @@ {% block form_errors -%} {%- if errors|length > 0 -%} -
-
    - {%- for error in errors -%} -
  • {{ error.message }}
  • - {%- endfor -%} -
-
+
+
    + {%- for error in errors -%} +
  • {{ 'Error'|trans({}, 'validators') }} {{ error.message }}
  • + {%- endfor -%} +
+
{%- endif %} {%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig index d57978220f330..801415872a079 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_base_layout.html.twig @@ -40,6 +40,16 @@
{{- form_errors(form.date) -}} {{- form_errors(form.time) -}} + +
+ {%- if form.date.year is defined %}{{ form_label(form.date.year) }}{% endif -%} + {%- if form.date.month is defined %}{{ form_label(form.date.month) }}{% endif -%} + {%- if form.date.day is defined %}{{ form_label(form.date.day) }}{% endif -%} + {%- if form.time.hour is defined %}{{ form_label(form.time.hour) }}{% endif -%} + {%- if form.time.minute is defined %}{{ form_label(form.time.minute) }}{% endif -%} + {%- if form.time.second is defined %}{{ form_label(form.time.second) }}{% endif -%} +
+ {{- form_widget(form.date, { datetime: true } ) -}} {{- form_widget(form.time, { datetime: true } ) -}}
@@ -54,6 +64,12 @@ {%- if datetime is not defined or not datetime -%}
{%- endif %} +
+ {{ form_label(form.year) }} + {{ form_label(form.month) }} + {{ form_label(form.day) }} +
+ {{- date_pattern|replace({ '{{ year }}': form_widget(form.year), '{{ month }}': form_widget(form.month), @@ -73,7 +89,10 @@ {%- if datetime is not defined or false == datetime -%}
{%- endif -%} - {{- form_widget(form.hour) }}{% if with_minutes %}:{{ form_widget(form.minute) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second) }}{% endif %} +
{{ form_label(form.hour) }}
+ {{- form_widget(form.hour) -}} + {%- if with_minutes -%}:
{{ form_label(form.minute) }}
{{ form_widget(form.minute) }}{%- endif -%} + {%- if with_seconds -%}:
{{ form_label(form.second) }}
{{ form_widget(form.second) }}{%- endif -%} {%- if datetime is not defined or false == datetime -%}
{%- endif -%} @@ -88,7 +107,7 @@
{{- form_errors(form) -}}
- +
{%- if with_years %}{% endif -%} @@ -147,7 +166,7 @@ {% block choice_label -%} {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} - {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': ''})|trim}) -%} {{- block('form_label') -}} {% endblock choice_label %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index f85aad1c68b9a..21ebd44a26e83 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -136,7 +136,7 @@ {%- else -%}
{{- form_errors(form) -}} -
+
{%- if with_years %}{% endif -%} @@ -271,7 +271,13 @@ {% set label = name|humanize %} {%- endif -%} {%- endif -%} - <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {%- if translation_domain is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|trans({}, translation_domain) -}} + {%- endif -%} + {%- endif -%} {%- endblock form_label -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php index d3822ee77796a..d8cbde1017345 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap4LayoutTest.php @@ -29,7 +29,7 @@ class FormExtensionBootstrap4LayoutTest extends AbstractBootstrap4LayoutTest { use RuntimeLoaderProvider; /** - * @var FormRenderer; + * @var FormRenderer */ private $renderer; diff --git a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php index 2074eeb93c013..d45f60c09d1c6 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php @@ -49,6 +49,10 @@ protected function getVariableGetterWithoutStrictCheck($name) protected function getVariableGetterWithStrictCheck($name) { + if (Environment::VERSION_ID > 20404) { + return sprintf('(isset($context["%s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new Twig_Error_Runtime(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); + } + if (Environment::MAJOR_VERSION >= 2) { return sprintf('(isset($context["%s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new Twig_Error_Runtime(\'Variable "%1$s" does not exist.\', 0, $this->getSourceContext()); })())', $name); } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 5c7d9bfd53c2e..1d08eb246ed81 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -23,7 +23,7 @@ "symfony/asset": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", - "symfony/form": "~3.4|~4.0", + "symfony/form": "^3.4.5|^4.0.5", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-intl-icu": "~1.0", @@ -41,7 +41,7 @@ "symfony/workflow": "~3.4|~4.0" }, "conflict": { - "symfony/form": "<3.4", + "symfony/form": "<3.4.5|<4.0.5,>=4.0", "symfony/console": "<3.4" }, "suggest": { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php index 5c360bc334409..131d889cd46f4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/RouterCacheWarmer.php @@ -22,7 +22,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index 3c352b4c051a2..b6860a24ec03c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -25,7 +25,7 @@ * * @author Roland Franssen * - * @final since version 3.4 + * @final */ class AboutCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index ea122cf80305a..ac3262baac35d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -28,7 +28,7 @@ * @author Fabien Potencier * @author Gábor Egyed * - * @final since version 3.4 + * @final */ class AssetsInstallCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index a33b3f3d908dd..a0872338a51b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -29,7 +29,7 @@ * @author Francis Besset * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class CacheClearCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 5219e1dbbad39..ccd315cad502b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -23,7 +23,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class CacheWarmupCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index d2fb64e763d62..cfef898242961 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -25,7 +25,7 @@ * * @author Grégoire Pineau * - * @final since version 3.4 + * @final */ class ConfigDebugCommand extends AbstractConfigCommand { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 06900cc2c40e5..8850efa373da8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -26,7 +26,7 @@ * @author Wouter J * @author Grégoire Pineau * - * @final since version 3.4 + * @final */ class ConfigDumpReferenceCommand extends AbstractConfigCommand { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index cd3e2da829aae..9830609d969ad 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -29,7 +29,7 @@ * * @author Ryan Weaver * - * @internal since version 3.4 + * @internal */ class ContainerDebugCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index 75d98807984d0..bc525f2e73425 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -25,7 +25,7 @@ * * @author Matthieu Auger * - * @final since version 3.4 + * @final */ class EventDispatcherDebugCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 425f607f84a07..ce932575335b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -29,7 +29,7 @@ * @author Fabien Potencier * @author Tobias Schultze * - * @final since version 3.4 + * @final */ class RouterDebugCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index 74ef9fdc96a07..83be4ceab81ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -26,7 +26,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class RouterMatchCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index a444df3a5affb..50737aa75c645 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -33,7 +33,7 @@ * * @author Florian Voutzinos * - * @final since version 3.4 + * @final */ class TranslationDebugCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 2404dbdba7c5f..a29e3081f36e6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -31,7 +31,7 @@ * * @author Michel Salib * - * @final since version 3.4 + * @final */ class TranslationUpdateCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index 1c031f5999acf..e527da1d06e37 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -22,7 +22,7 @@ /** * @author Grégoire Pineau * - * @final since version 3.4 + * @final */ class WorkflowDumpCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php index 42ee30e145077..0b5bb061d66e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php @@ -20,7 +20,7 @@ * @author Robin Chalas * @author Javier Eguiluz * - * @final since version 3.4 + * @final */ class XliffLintCommand extends BaseLintCommand { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index 4edd92ff6974e..1163ff1c28fb1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -19,7 +19,7 @@ * @author Grégoire Pineau * @author Robin Chalas * - * @final since version 3.4 + * @final */ class YamlLintCommand extends BaseLintCommand { diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index bcdec7eee31b4..2be849db17b13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -65,6 +65,8 @@ public function doRun(InputInterface $input, OutputInterface $output) $this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher')); + $this->registerCommands(); + if ($this->registrationErrors) { $this->renderRegistrationErrors($input, $output); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 03fb20c441d3d..7b87fcf8965cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -113,11 +113,11 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $service = $this->resolveServiceDefinition($builder, $serviceId); if ($service instanceof Alias) { - if ($showPrivate || $service->isPublic()) { + if ($showPrivate || ($service->isPublic() && !$service->isPrivate())) { $data['aliases'][$serviceId] = $this->getContainerAliasData($service); } } elseif ($service instanceof Definition) { - if (($showPrivate || $service->isPublic())) { + if (($showPrivate || ($service->isPublic() && !$service->isPrivate()))) { $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments); } } else { @@ -211,7 +211,7 @@ private function getContainerDefinitionData(Definition $definition, bool $omitTa { $data = array( 'class' => (string) $definition->getClass(), - 'public' => $definition->isPublic(), + 'public' => $definition->isPublic() && !$definition->isPrivate(), 'synthetic' => $definition->isSynthetic(), 'lazy' => $definition->isLazy(), 'shared' => $definition->isShared(), @@ -265,7 +265,7 @@ private function getContainerAliasData(Alias $alias): array { return array( 'service' => (string) $alias, - 'public' => $alias->isPublic(), + 'public' => $alias->isPublic() && !$alias->isPrivate(), ); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 2efee2ed1d1b9..8836cc234bfb0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -139,11 +139,11 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $service = $this->resolveServiceDefinition($builder, $serviceId); if ($service instanceof Alias) { - if ($showPrivate || $service->isPublic()) { + if ($showPrivate || ($service->isPublic() && !$service->isPrivate())) { $services['aliases'][$serviceId] = $service; } } elseif ($service instanceof Definition) { - if (($showPrivate || $service->isPublic())) { + if (($showPrivate || ($service->isPublic() && !$service->isPrivate()))) { $services['definitions'][$serviceId] = $service; } } else { @@ -182,7 +182,7 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o protected function describeContainerDefinition(Definition $definition, array $options = array()) { $output = '- Class: `'.$definition->getClass().'`' - ."\n".'- Public: '.($definition->isPublic() ? 'yes' : 'no') + ."\n".'- Public: '.($definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no') ."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no') ."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no') ."\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no') @@ -239,7 +239,7 @@ protected function describeContainerDefinition(Definition $definition, array $op protected function describeContainerAlias(Alias $alias, array $options = array(), ContainerBuilder $builder = null) { $output = '- Service: `'.$alias.'`' - ."\n".'- Public: '.($alias->isPublic() ? 'yes' : 'no'); + ."\n".'- Public: '.($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no'); if (!isset($options['id'])) { return $this->write($output); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 63513bfa430b5..e4d4d1ea209a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -194,7 +194,7 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $definition = $this->resolveServiceDefinition($builder, $serviceId); if ($definition instanceof Definition) { // filter out private services unless shown explicitly - if (!$showPrivate && !$definition->isPublic()) { + if (!$showPrivate && (!$definition->isPublic() || $definition->isPrivate())) { unset($serviceIds[$key]); continue; } @@ -212,7 +212,7 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o } } } elseif ($definition instanceof Alias) { - if (!$showPrivate && !$definition->isPublic()) { + if (!$showPrivate && (!$definition->isPublic() || $definition->isPrivate())) { unset($serviceIds[$key]); continue; } @@ -302,7 +302,7 @@ protected function describeContainerDefinition(Definition $definition, array $op $tableRows[] = array('Calls', implode(', ', $callInformation)); } - $tableRows[] = array('Public', $definition->isPublic() ? 'yes' : 'no'); + $tableRows[] = array('Public', $definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no'); $tableRows[] = array('Synthetic', $definition->isSynthetic() ? 'yes' : 'no'); $tableRows[] = array('Lazy', $definition->isLazy() ? 'yes' : 'no'); $tableRows[] = array('Shared', $definition->isShared() ? 'yes' : 'no'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 37af3802bea67..1e3148db9a697 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -283,7 +283,7 @@ private function getContainerServicesDocument(ContainerBuilder $builder, string foreach ($this->sortServiceIds($serviceIds) as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); - if (($service instanceof Definition || $service instanceof Alias) && !($showPrivate || $service->isPublic())) { + if (($service instanceof Definition || $service instanceof Alias) && !($showPrivate || ($service->isPublic() && !$service->isPrivate()))) { continue; } @@ -322,7 +322,7 @@ private function getContainerDefinitionDocument(Definition $definition, string $ } } - $serviceXML->setAttribute('public', $definition->isPublic() ? 'true' : 'false'); + $serviceXML->setAttribute('public', $definition->isPublic() && !$definition->isPrivate() ? 'true' : 'false'); $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false'); $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false'); $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false'); @@ -421,7 +421,7 @@ private function getContainerAliasDocument(Alias $alias, string $id = null): \DO } $aliasXML->setAttribute('service', (string) $alias); - $aliasXML->setAttribute('public', $alias->isPublic() ? 'true' : 'false'); + $aliasXML->setAttribute('public', $alias->isPublic() && !$alias->isPrivate() ? 'true' : 'false'); return $dom; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index c104ba10c2465..51ac58b9fae83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -31,7 +31,7 @@ abstract class Controller implements ContainerAwareInterface * * @return mixed * - * @final since version 3.4 + * @final */ protected function getParameter(string $name) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php index 6fc94bc8a2a20..69639f65d65ab 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php @@ -42,7 +42,7 @@ trait ControllerTrait /** * Returns true if the service id is defined. * - * @final since version 3.4 + * @final */ protected function has(string $id): bool { @@ -54,7 +54,7 @@ protected function has(string $id): bool * * @return object The service * - * @final since version 3.4 + * @final */ protected function get(string $id) { @@ -66,7 +66,7 @@ protected function get(string $id) * * @see UrlGeneratorInterface * - * @final since version 3.4 + * @final */ protected function generateUrl(string $route, array $parameters = array(), int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string { @@ -78,7 +78,7 @@ protected function generateUrl(string $route, array $parameters = array(), int $ * * @param string $controller The controller name (a string like BlogBundle:Post:index) * - * @final since version 3.4 + * @final */ protected function forward(string $controller, array $path = array(), array $query = array()): Response { @@ -93,7 +93,7 @@ protected function forward(string $controller, array $path = array(), array $que /** * Returns a RedirectResponse to the given URL. * - * @final since version 3.4 + * @final */ protected function redirect(string $url, int $status = 302): RedirectResponse { @@ -103,7 +103,7 @@ protected function redirect(string $url, int $status = 302): RedirectResponse /** * Returns a RedirectResponse to the given route with the given parameters. * - * @final since version 3.4 + * @final */ protected function redirectToRoute(string $route, array $parameters = array(), int $status = 302): RedirectResponse { @@ -113,7 +113,7 @@ protected function redirectToRoute(string $route, array $parameters = array(), i /** * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. * - * @final since version 3.4 + * @final */ protected function json($data, int $status = 200, array $headers = array(), array $context = array()): JsonResponse { @@ -133,7 +133,7 @@ protected function json($data, int $status = 200, array $headers = array(), arra * * @param \SplFileInfo|string $file File object or path to file to be sent as response * - * @final since version 3.4 + * @final */ protected function file($file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse { @@ -148,7 +148,7 @@ protected function file($file, string $fileName = null, string $disposition = Re * * @throws \LogicException * - * @final since version 3.4 + * @final */ protected function addFlash(string $type, string $message) { @@ -164,7 +164,7 @@ protected function addFlash(string $type, string $message) * * @throws \LogicException * - * @final since version 3.4 + * @final */ protected function isGranted($attributes, $subject = null): bool { @@ -181,7 +181,7 @@ protected function isGranted($attributes, $subject = null): bool * * @throws AccessDeniedException * - * @final since version 3.4 + * @final */ protected function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.') { @@ -197,7 +197,7 @@ protected function denyAccessUnlessGranted($attributes, $subject = null, string /** * Returns a rendered view. * - * @final since version 3.4 + * @final */ protected function renderView(string $view, array $parameters = array()): string { @@ -215,7 +215,7 @@ protected function renderView(string $view, array $parameters = array()): string /** * Renders a view. * - * @final since version 3.4 + * @final */ protected function render(string $view, array $parameters = array(), Response $response = null): Response { @@ -239,7 +239,7 @@ protected function render(string $view, array $parameters = array(), Response $r /** * Streams a view. * - * @final since version 3.4 + * @final */ protected function stream(string $view, array $parameters = array(), StreamedResponse $response = null): StreamedResponse { @@ -275,7 +275,7 @@ protected function stream(string $view, array $parameters = array(), StreamedRes * * throw $this->createNotFoundException('Page not found!'); * - * @final since version 3.4 + * @final */ protected function createNotFoundException(string $message = 'Not Found', \Exception $previous = null): NotFoundHttpException { @@ -291,7 +291,7 @@ protected function createNotFoundException(string $message = 'Not Found', \Excep * * @throws \LogicException If the Security component is not available * - * @final since version 3.4 + * @final */ protected function createAccessDeniedException(string $message = 'Access Denied.', \Exception $previous = null): AccessDeniedException { @@ -305,7 +305,7 @@ protected function createAccessDeniedException(string $message = 'Access Denied. /** * Creates and returns a Form instance from the type of the form. * - * @final since version 3.4 + * @final */ protected function createForm(string $type, $data = null, array $options = array()): FormInterface { @@ -315,7 +315,7 @@ protected function createForm(string $type, $data = null, array $options = array /** * Creates and returns a form builder instance. * - * @final since version 3.4 + * @final */ protected function createFormBuilder($data = null, array $options = array()): FormBuilderInterface { @@ -327,7 +327,7 @@ protected function createFormBuilder($data = null, array $options = array()): Fo * * @throws \LogicException If DoctrineBundle is not available * - * @final since version 3.4 + * @final */ protected function getDoctrine(): ManagerRegistry { @@ -347,7 +347,7 @@ protected function getDoctrine(): ManagerRegistry * * @see TokenInterface::getUser() * - * @final since version 3.4 + * @final */ protected function getUser() { @@ -370,12 +370,12 @@ protected function getUser() /** * Checks the validity of a CSRF token. * - * @param string $id The id used when generating the token - * @param string $token The actual token sent with the request that should be validated + * @param string $id The id used when generating the token + * @param string|null $token The actual token sent with the request that should be validated * - * @final since version 3.4 + * @final */ - protected function isCsrfTokenValid(string $id, string $token): bool + protected function isCsrfTokenValid(string $id, ?string $token): bool { if (!$this->container->has('security.csrf.token_manager')) { throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index 8e6ce84408d09..37430d6ef7ce1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -22,7 +22,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class RedirectController { diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index c15cde111578b..2273075ed1969 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -20,7 +20,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class TemplateController { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php index 35ea20a89accd..4f09e52bdcbd1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php @@ -26,16 +26,16 @@ public function process(ContainerBuilder $container) { // "annotations.cached_reader" is wired late so that any passes using // "annotation_reader" at build time don't get any cache - if ($container->hasDefinition('annotations.cached_reader')) { - $reader = $container->getDefinition('annotations.cached_reader'); + foreach ($container->findTaggedServiceIds('annotations.cached_reader') as $id => $tags) { + $reader = $container->getDefinition($id); $properties = $reader->getProperties(); if (isset($properties['cacheProviderBackup'])) { $provider = $properties['cacheProviderBackup']->getValues()[0]; unset($properties['cacheProviderBackup']); $reader->setProperties($properties); - $container->set('annotations.cached_reader', null); - $container->setDefinition('annotations.cached_reader', $reader->replaceArgument(1, $provider)); + $container->set($id, null); + $container->setDefinition($id, $reader->replaceArgument(1, $provider)); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index ecc4d9e56356a..cb3366a499425 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -22,6 +22,7 @@ class UnusedTagsPass implements CompilerPassInterface { private $whitelist = array( + 'annotations.cached_reader', 'cache.pool.clearer', 'console.command', 'container.hot_path', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 95d02644501a6..f9f3ddf0406c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1116,7 +1116,9 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde ->replaceArgument(2, $config['debug']) // temporary property to lazy-reference the cache provider without using it until AddAnnotationsCachedReaderPass runs ->setProperty('cacheProviderBackup', new ServiceClosureArgument(new Reference($cacheService))) + ->addTag('annotations.cached_reader') ; + $container->setAlias('annotation_reader', 'annotations.cached_reader')->setPrivate(true); $container->setAlias(Reader::class, new Alias('annotations.cached_reader', false)); } else { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index f6bd479d8e194..7021bf1407cb0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -30,7 +30,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 cca86f8c61dde..f81b7d225cbca 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 @@ -230,6 +230,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php index f5777af95a7e5..474c725566d5c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/AnnotatedRouteControllerLoader.php @@ -29,7 +29,11 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader */ protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot) { - $route->setDefault('_controller', $class->getName().'::'.$method->getName()); + if ('__invoke' === $method->getName()) { + $route->setDefault('_controller', $class->getName()); + } else { + $route->setDefault('_controller', $class->getName().'::'.$method->getName()); + } } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php index d06e98be77706..c977bb13a1313 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php @@ -165,6 +165,33 @@ public function testRunOnlyWarnsOnUnregistrableCommand() $this->assertContains('fine', $output); } + public function testRegistrationErrorsAreDisplayedOnCommandNotFound() + { + $container = new ContainerBuilder(); + $container->register('event_dispatcher', EventDispatcher::class); + + $kernel = $this->getMockBuilder(KernelInterface::class)->getMock(); + $kernel + ->method('getBundles') + ->willReturn(array($this->createBundleMock( + array((new Command(null))->setCode(function (InputInterface $input, OutputInterface $output) { $output->write('fine'); })) + ))); + $kernel + ->method('getContainer') + ->willReturn($container); + + $application = new Application($kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'fine')); + $output = $tester->getDisplay(); + + $this->assertSame(1, $tester->getStatusCode()); + $this->assertContains('Some commands could not be registered:', $output); + $this->assertContains('Command "fine" is not defined.', $output); + } + private function getKernel(array $bundles, $useDispatcher = false) { $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index 9eb2e874d63d5..ce5cc7880b27c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\CompiledRoute; use Symfony\Component\Routing\RouteCollection; class ObjectsProvider @@ -36,7 +37,7 @@ public static function getRouteCollections() public static function getRoutes() { return array( - 'route_1' => new Route( + 'route_1' => new RouteStub( '/hello/{name}', array('name' => 'Joseph'), array('name' => '[a-z]+'), @@ -45,7 +46,7 @@ public static function getRoutes() array('http', 'https'), array('get', 'head') ), - 'route_2' => new Route( + 'route_2' => new RouteStub( '/name/add', array(), array(), @@ -187,3 +188,11 @@ public static function staticMethod() { } } + +class RouteStub extends Route +{ + public function compile() + { + return new CompiledRoute('', '#PATH_REGEX#', array(), array(), '#HOST_REGEX#'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php index 0006070f51ddb..105760641c34f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPrunerPassTest.php @@ -44,27 +44,17 @@ public function testCompilerPassReplacesCommandArgument() public function testCompilePassIsIgnoredIfCommandDoesNotExist() { - $container = $this - ->getMockBuilder(ContainerBuilder::class) - ->setMethods(array('hasDefinition', 'getDefinition', 'findTaggedServiceIds')) - ->getMock(); - - $container - ->expects($this->atLeastOnce()) - ->method('hasDefinition') - ->with(CachePoolPruneCommand::class) - ->will($this->returnValue(false)); - - $container - ->expects($this->never()) - ->method('getDefinition'); + $container = new ContainerBuilder(); - $container - ->expects($this->never()) - ->method('findTaggedServiceIds'); + $definitionsBefore = count($container->getDefinitions()); + $aliasesBefore = count($container->getAliases()); $pass = new CachePoolPrunerPass(); $pass->process($container); + + // the container is untouched (i.e. no new definitions or aliases) + $this->assertCount($definitionsBefore, $container->getDefinitions()); + $this->assertCount($aliasesBefore, $container->getAliases()); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php index a93d8ca5d0015..2a9b3329142cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/LoggingTranslatorPassTest.php @@ -13,96 +13,69 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; class LoggingTranslatorPassTest extends TestCase { public function testProcess() { - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->getMock(); - $parameterBag = $this->getMockBuilder('Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface')->getMock(); - - $container->expects($this->exactly(2)) - ->method('hasAlias') - ->will($this->returnValue(true)); - - $container->expects($this->once()) - ->method('getParameter') - ->will($this->returnValue(true)); - - $container->expects($this->once()) - ->method('getAlias') - ->will($this->returnValue('translation.default')); - - $container->expects($this->exactly(3)) - ->method('getDefinition') - ->will($this->returnValue($definition)); - - $container->expects($this->once()) - ->method('hasParameter') - ->with('translator.logging') - ->will($this->returnValue(true)); - - $definition->expects($this->once()) - ->method('getClass') - ->will($this->returnValue('Symfony\Bundle\FrameworkBundle\Translation\Translator')); - - $parameterBag->expects($this->once()) - ->method('resolveValue') - ->will($this->returnValue("Symfony\Bundle\FrameworkBundle\Translation\Translator")); - - $container->expects($this->once()) - ->method('getParameterBag') - ->will($this->returnValue($parameterBag)); - - $container->expects($this->once()) - ->method('getReflectionClass') - ->with('Symfony\Bundle\FrameworkBundle\Translation\Translator') - ->will($this->returnValue(new \ReflectionClass('Symfony\Bundle\FrameworkBundle\Translation\Translator'))); - - $definition->expects($this->once()) - ->method('getTag') - ->with('container.service_subscriber') - ->willReturn(array(array('id' => 'translator'), array('id' => 'foo'))); - - $definition->expects($this->once()) - ->method('clearTag') - ->with('container.service_subscriber'); - - $definition->expects($this->any()) - ->method('addTag') - ->withConsecutive( - array('container.service_subscriber', array('id' => 'foo')), - array('container.service_subscriber', array('key' => 'translator', 'id' => 'translator.logging.inner')) - ); + $container = new ContainerBuilder(); + $container->setParameter('translator.logging', true); + $container->setParameter('translator.class', 'Symfony\Component\Translation\Translator'); + $container->register('monolog.logger'); + $container->setAlias('logger', 'monolog.logger'); + $container->register('translator.default', '%translator.class%'); + $container->register('translator.logging', '%translator.class%'); + $container->setAlias('translator', 'translator.default'); + $translationWarmerDefinition = $container->register('translation.warmer') + ->addArgument(new Reference('translator')) + ->addTag('container.service_subscriber', array('id' => 'translator')) + ->addTag('container.service_subscriber', array('id' => 'foo')); $pass = new LoggingTranslatorPass(); $pass->process($container); + + $this->assertEquals( + array('container.service_subscriber' => array( + array('id' => 'foo'), + array('key' => 'translator', 'id' => 'translator.logging.inner'), + )), + $translationWarmerDefinition->getTags() + ); } public function testThatCompilerPassIsIgnoredIfThereIsNotLoggerDefinition() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->getMock(); - $container->expects($this->once()) - ->method('hasAlias') - ->will($this->returnValue(false)); + $container = new ContainerBuilder(); + $container->register('identity_translator'); + $container->setAlias('translator', 'identity_translator'); + + $definitionsBefore = count($container->getDefinitions()); + $aliasesBefore = count($container->getAliases()); $pass = new LoggingTranslatorPass(); $pass->process($container); + + // the container is untouched (i.e. no new definitions or aliases) + $this->assertCount($definitionsBefore, $container->getDefinitions()); + $this->assertCount($aliasesBefore, $container->getAliases()); } public function testThatCompilerPassIsIgnoredIfThereIsNotTranslatorDefinition() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->getMock(); - $container->expects($this->at(0)) - ->method('hasAlias') - ->will($this->returnValue(true)); + $container = new ContainerBuilder(); + $container->register('monolog.logger'); + $container->setAlias('logger', 'monolog.logger'); - $container->expects($this->at(0)) - ->method('hasAlias') - ->will($this->returnValue(false)); + $definitionsBefore = count($container->getDefinitions()); + $aliasesBefore = count($container->getAliases()); $pass = new LoggingTranslatorPass(); $pass->process($container); + + // the container is untouched (i.e. no new definitions or aliases) + $this->assertCount($definitionsBefore, $container->getDefinitions()); + $this->assertCount($aliasesBefore, $container->getAliases()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php index e064ce9f17f02..9fcae720b20b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php @@ -12,18 +12,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; class ProfilerPassTest extends TestCase { - private $profilerDefinition; - - protected function setUp() - { - $this->profilerDefinition = new Definition('ProfilerClass'); - } - /** * Tests that collectors that specify a template but no "id" will throw * an exception (both are needed if the template is specified). @@ -31,17 +24,15 @@ protected function setUp() * Thus, a fully-valid tag looks something like this: * * + * + * @expectedException \InvalidArgumentException */ public function testTemplateNoIdThrowsException() { - // one service, with a template key, but no id - $services = array( - 'my_collector_service' => array(0 => array('template' => 'foo')), - ); - - $builder = $this->createContainerMock($services); - - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $builder = new ContainerBuilder(); + $builder->register('profiler', 'ProfilerClass'); + $builder->register('my_collector_service') + ->addTag('data_collector', array('template' => 'foo')); $profilerPass = new ProfilerPass(); $profilerPass->process($builder); @@ -49,45 +40,19 @@ public function testTemplateNoIdThrowsException() public function testValidCollector() { - // one service, with a template key, but no id - $services = array( - 'my_collector_service' => array(0 => array('template' => 'foo', 'id' => 'my_collector')), - ); - - $container = $this->createContainerMock($services); - - // fake the getDefinition() to return a Profiler definition - $container->expects($this->atLeastOnce()) - ->method('getDefinition'); - - // assert that the data_collector.templates parameter should be set - $container->expects($this->once()) - ->method('setParameter') - ->with('data_collector.templates', array('my_collector_service' => array('my_collector', 'foo'))); + $container = new ContainerBuilder(); + $profilerDefinition = $container->register('profiler', 'ProfilerClass'); + $container->register('my_collector_service') + ->addTag('data_collector', array('template' => 'foo', 'id' => 'my_collector')); $profilerPass = new ProfilerPass(); $profilerPass->process($container); + $this->assertSame(array('my_collector_service' => array('my_collector', 'foo')), $container->getParameter('data_collector.templates')); + // grab the method calls off of the "profiler" definition - $methodCalls = $this->profilerDefinition->getMethodCalls(); + $methodCalls = $profilerDefinition->getMethodCalls(); $this->assertCount(1, $methodCalls); $this->assertEquals('add', $methodCalls[0][0]); // grab the method part of the first call } - - private function createContainerMock($services) - { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'getDefinition', 'findTaggedServiceIds', 'setParameter'))->getMock(); - $container->expects($this->any()) - ->method('hasDefinition') - ->with($this->equalTo('profiler')) - ->will($this->returnValue(true)); - $container->expects($this->any()) - ->method('getDefinition') - ->will($this->returnValue($this->profilerDefinition)); - $container->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->returnValue($services)); - - return $container; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php index 6c6f15899559c..d91c806bc8c10 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; class UnusedTagsPassTest extends TestCase { @@ -20,24 +21,14 @@ public function testProcess() { $pass = new UnusedTagsPass(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds', 'findUnusedTags', 'findTags', 'log'))->getMock(); - $container->expects($this->once()) - ->method('log') - ->with($pass, 'Tag "kenrel.event_subscriber" was defined on service(s) "foo", "bar", but was never used. Did you mean "kernel.event_subscriber"?'); - $container->expects($this->once()) - ->method('findTags') - ->will($this->returnValue(array('kenrel.event_subscriber'))); - $container->expects($this->once()) - ->method('findUnusedTags') - ->will($this->returnValue(array('kenrel.event_subscriber', 'form.type'))); - $container->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('kenrel.event_subscriber') - ->will($this->returnValue(array( - 'foo' => array(), - 'bar' => array(), - ))); + $container = new ContainerBuilder(); + $container->register('foo') + ->addTag('kenrel.event_subscriber'); + $container->register('bar') + ->addTag('kenrel.event_subscriber'); $pass->process($container); + + $this->assertSame(array(sprintf('%s: Tag "kenrel.event_subscriber" was defined on service(s) "foo", "bar", but was never used. Did you mean "kernel.event_subscriber"?', UnusedTagsPass::class)), $container->getCompiler()->getLog()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index a97daeef1d131..64de9309e80c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -68,6 +68,7 @@ 'enabled' => true, 'enable_annotations' => true, 'name_converter' => 'serializer.name_converter.camel_case_to_snake_case', + 'circular_reference_handler' => 'my.circular.reference.handler', ), 'property_info' => true, 'ide' => 'file%%link%%format', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 6920efebed320..f083b9cb1a221 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -41,7 +41,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 3194a0fab2e1a..f54c3e6eb18d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -51,9 +51,10 @@ framework: debug: true file_cache_dir: '%kernel.cache_dir%/annotations' serializer: - enabled: true - enable_annotations: true - name_converter: serializer.name_converter.camel_case_to_snake_case + enabled: true + enable_annotations: true + name_converter: serializer.name_converter.camel_case_to_snake_case + circular_reference_handler: my.circular.reference.handler property_info: ~ ide: file%%link%%format request: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 2f033cbd00492..f3b3e5817a3ed 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -25,6 +25,7 @@ use Symfony\Component\Cache\Adapter\ProxyAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; @@ -582,10 +583,12 @@ public function testValidationService() public function testAnnotations() { - $container = $this->createContainerFromFile('full'); + $container = $this->createContainerFromFile('full', array(), true, false); + $container->addCompilerPass(new TestAnnotationsPass()); + $container->compile(); $this->assertEquals($container->getParameter('kernel.cache_dir').'/annotations', $container->getDefinition('annotations.filesystem_cache')->getArgument(0)); - $this->assertSame('annotations.filesystem_cache', (string) $container->getDefinition('annotations.cached_reader')->getArgument(1)); + $this->assertSame('annotations.filesystem_cache', (string) $container->getDefinition('annotation_reader')->getArgument(1)); } public function testFileLinkFormat() @@ -771,6 +774,7 @@ public function testSerializerEnabled() $this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1)); $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1)); $this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3)); + $this->assertEquals(array('setCircularReferenceHandler', array(new Reference('my.circular.reference.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[0]); } public function testRegisterSerializerExtractor() @@ -1029,10 +1033,10 @@ protected function createContainer(array $data = array()) ), $data))); } - protected function createContainerFromFile($file, $data = array(), $resetCompilerPasses = true) + protected function createContainerFromFile($file, $data = array(), $resetCompilerPasses = true, $compile = true) { $cacheKey = md5(get_class($this).$file.serialize($data)); - if (isset(self::$containerCache[$cacheKey])) { + if ($compile && isset(self::$containerCache[$cacheKey])) { return self::$containerCache[$cacheKey]; } $container = $this->createContainer($data); @@ -1043,7 +1047,12 @@ protected function createContainerFromFile($file, $data = array(), $resetCompile $container->getCompilerPassConfig()->setOptimizationPasses(array()); $container->getCompilerPassConfig()->setRemovingPasses(array()); } - $container->getCompilerPassConfig()->setBeforeRemovingPasses(array(new AddAnnotationsCachedReaderPass(), new AddConstraintValidatorsPass(), new TranslatorPass('translator.default', 'translation.reader'))); + $container->getCompilerPassConfig()->setBeforeRemovingPasses(array(new AddConstraintValidatorsPass(), new TranslatorPass('translator.default', 'translation.reader'))); + $container->getCompilerPassConfig()->setAfterRemovingPasses(array(new AddAnnotationsCachedReaderPass())); + + if (!$compile) { + return $container; + } $container->compile(); return self::$containerCache[$cacheKey] = $container; @@ -1101,7 +1110,7 @@ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $con $this->assertFalse($poolDefinition->isAbstract(), sprintf('Service definition "%s" is not abstract.', $id)); $tag = $poolDefinition->getTag('cache.pool'); - $this->assertTrue(isset($tag[0]['default_lifetime']), 'The default lifetime is stored as an attribute of the "cache.pool" tag.'); + $this->assertArrayHasKey('default_lifetime', $tag[0], 'The default lifetime is stored as an attribute of the "cache.pool" tag.'); $this->assertSame($defaultLifetime, $tag[0]['default_lifetime'], 'The default lifetime is stored as an attribute of the "cache.pool" tag.'); $parentDefinition = $poolDefinition; @@ -1136,3 +1145,15 @@ private function assertCachePoolServiceDefinitionIsCreated(ContainerBuilder $con } } } + +/** + * Simulates ReplaceAliasByActualDefinitionPass. + */ +class TestAnnotationsPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $container->setDefinition('annotation_reader', $container->getDefinition('annotations.cached_reader')); + $container->removeDefinition('annotations.cached_reader'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json index e2ab628937760..df76274db6417 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.json @@ -17,7 +17,7 @@ "%parameter%", { "class": "inline_service", - "public": true, + "public": false, "synthetic": false, "lazy": false, "shared": true, @@ -39,7 +39,7 @@ }, { "class": "inline_service", - "public": true, + "public": false, "synthetic": false, "lazy": false, "shared": true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml index b016ae382a908..59811b00d37b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_arguments.xml @@ -6,7 +6,7 @@ %parameter% - + arg1 arg2 @@ -15,7 +15,7 @@ foo - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json index 5074d4fba6170..2568b87dec458 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.json @@ -15,7 +15,7 @@ "%parameter%", { "class": "inline_service", - "public": true, + "public": false, "synthetic": false, "lazy": false, "shared": true, @@ -37,7 +37,7 @@ }, { "class": "inline_service", - "public": true, + "public": false, "synthetic": false, "lazy": false, "shared": true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml index 732f99f7839a1..e250060d75581 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_arguments_1.xml @@ -4,7 +4,7 @@ %parameter% - + arg1 arg2 @@ -13,7 +13,7 @@ foo - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json index beac79f1f8758..1108109fb0b48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.json @@ -1,11 +1,11 @@ { "path": "\/hello\/{name}", - "pathRegex": "#^\/hello(?:\/(?P[a-z]+))?$#s", + "pathRegex": "#PATH_REGEX#", "host": "localhost", - "hostRegex": "#^localhost$#si", + "hostRegex": "#HOST_REGEX#", "scheme": "http|https", "method": "GET|HEAD", - "class": "Symfony\\Component\\Routing\\Route", + "class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub", "defaults": { "name": "Joseph" }, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md index 5bfba18b2c814..c36d35c83e8ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.md @@ -1,10 +1,10 @@ - Path: /hello/{name} -- Path Regex: #^/hello(?:/(?P[a-z]+))?$#s +- Path Regex: #PATH_REGEX# - Host: localhost -- Host Regex: #^localhost$#si +- Host Regex: #HOST_REGEX# - Scheme: http|https - Method: GET|HEAD -- Class: Symfony\Component\Routing\Route +- Class: Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub - Defaults: - `name`: Joseph - Requirements: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt index ed0bcca6562eb..25074dfd18b2c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.txt @@ -1,17 +1,17 @@ -+--------------+---------------------------------------------------------+ -| Property | Value | -+--------------+---------------------------------------------------------+ -| Route Name | | -| Path | /hello/{name} | -| Path Regex | #^/hello(?:/(?P[a-z]+))?$#s | -| Host | localhost | -| Host Regex | #^localhost$#si | -| Scheme | http|https | -| Method | GET|HEAD | -| Requirements | name: [a-z]+ | -| Class | Symfony\Component\Routing\Route | -| Defaults | name: Joseph | -| Options | compiler_class: Symfony\Component\Routing\RouteCompiler | -| | opt1: val1 | -| | opt2: val2 | -+--------------+---------------------------------------------------------+ ++--------------+-------------------------------------------------------------------+ +| Property | Value | ++--------------+-------------------------------------------------------------------+ +| Route Name | | +| Path | /hello/{name} | +| Path Regex | #PATH_REGEX# | +| Host | localhost | +| Host Regex | #HOST_REGEX# | +| Scheme | http|https | +| Method | GET|HEAD | +| Requirements | name: [a-z]+ | +| Class | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub | +| Defaults | name: Joseph | +| Options | compiler_class: Symfony\Component\Routing\RouteCompiler | +| | opt1: val1 | +| | opt2: val2 | ++--------------+-------------------------------------------------------------------+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml index b6040bdad160c..9ff531c92821a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_1.xml @@ -1,7 +1,7 @@ - - /hello/{name} - localhost + + /hello/{name} + localhost http https GET diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json index 58caf26d537e7..e190ef0cbf89a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.json @@ -1,11 +1,11 @@ { "path": "\/name\/add", - "pathRegex": "#^\/name\/add$#s", + "pathRegex": "#PATH_REGEX#", "host": "localhost", - "hostRegex": "#^localhost$#si", + "hostRegex": "#HOST_REGEX#", "scheme": "http|https", "method": "PUT|POST", - "class": "Symfony\\Component\\Routing\\Route", + "class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub", "defaults": [], "requirements": "NO CUSTOM", "options": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md index 0a3f84be17c70..1d776c5ffe49e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.md @@ -1,10 +1,10 @@ - Path: /name/add -- Path Regex: #^/name/add$#s +- Path Regex: #PATH_REGEX# - Host: localhost -- Host Regex: #^localhost$#si +- Host Regex: #HOST_REGEX# - Scheme: http|https - Method: PUT|POST -- Class: Symfony\Component\Routing\Route +- Class: Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub - Defaults: NONE - Requirements: NO CUSTOM - Options: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt index 828f6316bedb9..5593cc0d81ab0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.txt @@ -1,17 +1,17 @@ -+--------------+---------------------------------------------------------+ -| Property | Value | -+--------------+---------------------------------------------------------+ -| Route Name | | -| Path | /name/add | -| Path Regex | #^/name/add$#s | -| Host | localhost | -| Host Regex | #^localhost$#si | -| Scheme | http|https | -| Method | PUT|POST | -| Requirements | NO CUSTOM | -| Class | Symfony\Component\Routing\Route | -| Defaults | NONE | -| Options | compiler_class: Symfony\Component\Routing\RouteCompiler | -| | opt1: val1 | -| | opt2: val2 | -+--------------+---------------------------------------------------------+ ++--------------+-------------------------------------------------------------------+ +| Property | Value | ++--------------+-------------------------------------------------------------------+ +| Route Name | | +| Path | /name/add | +| Path Regex | #PATH_REGEX# | +| Host | localhost | +| Host Regex | #HOST_REGEX# | +| Scheme | http|https | +| Method | PUT|POST | +| Requirements | NO CUSTOM | +| Class | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub | +| Defaults | NONE | +| Options | compiler_class: Symfony\Component\Routing\RouteCompiler | +| | opt1: val1 | +| | opt2: val2 | ++--------------+-------------------------------------------------------------------+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml index 0f94cf7c41764..584ab1b12de59 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_2.xml @@ -1,7 +1,7 @@ - - /name/add - localhost + + /name/add + localhost http https PUT diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json index 350bffdb3a9c7..bd60070ed5cf4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.json @@ -1,12 +1,12 @@ { "route_1": { "path": "\/hello\/{name}", - "pathRegex": "#^\/hello(?:\/(?P[a-z]+))?$#s", + "pathRegex": "#PATH_REGEX#", "host": "localhost", - "hostRegex": "#^localhost$#si", + "hostRegex": "#HOST_REGEX#", "scheme": "http|https", "method": "GET|HEAD", - "class": "Symfony\\Component\\Routing\\Route", + "class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub", "defaults": { "name": "Joseph" }, @@ -21,12 +21,12 @@ }, "route_2": { "path": "\/name\/add", - "pathRegex": "#^\/name\/add$#s", + "pathRegex": "#PATH_REGEX#", "host": "localhost", - "hostRegex": "#^localhost$#si", + "hostRegex": "#HOST_REGEX#", "scheme": "http|https", "method": "PUT|POST", - "class": "Symfony\\Component\\Routing\\Route", + "class": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\RouteStub", "defaults": [], "requirements": "NO CUSTOM", "options": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md index 815f36e2cffa4..cbb70b4d31736 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.md @@ -2,12 +2,12 @@ route_1 ------- - Path: /hello/{name} -- Path Regex: #^/hello(?:/(?P[a-z]+))?$#s +- Path Regex: #PATH_REGEX# - Host: localhost -- Host Regex: #^localhost$#si +- Host Regex: #HOST_REGEX# - Scheme: http|https - Method: GET|HEAD -- Class: Symfony\Component\Routing\Route +- Class: Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub - Defaults: - `name`: Joseph - Requirements: @@ -22,12 +22,12 @@ route_2 ------- - Path: /name/add -- Path Regex: #^/name/add$#s +- Path Regex: #PATH_REGEX# - Host: localhost -- Host Regex: #^localhost$#si +- Host Regex: #HOST_REGEX# - Scheme: http|https - Method: PUT|POST -- Class: Symfony\Component\Routing\Route +- Class: Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\RouteStub - Defaults: NONE - Requirements: NO CUSTOM - Options: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml index 6d17820c3143d..666a53730dee0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/route_collection_1.xml @@ -1,8 +1,8 @@ - - /hello/{name} - localhost + + /hello/{name} + localhost http https GET @@ -19,9 +19,9 @@ - - /name/add - localhost + + /name/add + localhost http https PUT diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php index e0548078fca2c..ba5db79405cc5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -220,16 +220,17 @@ public function testDefaultValuesAsNonStrings($value) public function testGetRouteCollectionAddsContainerParametersResource() { - $routeCollection = $this->getMockBuilder(RouteCollection::class)->getMock(); - $routeCollection->method('getIterator')->willReturn(new \ArrayIterator(array(new Route('/%locale%')))); - $routeCollection->expects($this->once())->method('addResource')->with(new ContainerParametersResource(array('locale' => 'en'))); + $routeCollection = new RouteCollection(); + $routeCollection->add('foo', new Route('/%locale%')); $sc = $this->getServiceContainer($routeCollection); $sc->setParameter('locale', 'en'); $router = new Router($sc, 'foo'); - $router->getRouteCollection(); + $routeCollection = $router->getRouteCollection(); + + $this->assertEquals(array(new ContainerParametersResource(array('locale' => 'en'))), $routeCollection->getResources()); } public function getNonStringValues() diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index b074883d56fa3..034b1bbc3f9f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -27,7 +27,7 @@ "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", - "symfony/routing": "~3.4|~4.0" + "symfony/routing": "^3.4.5|^4.0.5" }, "require-dev": { "doctrine/cache": "~1.0", diff --git a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php index a09d5c3651f96..0ddbdb429d9e0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php @@ -27,7 +27,7 @@ * * @author Sarah Khalil * - * @final since version 3.4 + * @final */ class UserPasswordEncoderCommand extends Command { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index a144a9c39ef5f..b5d08d596e7ec 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -409,7 +409,10 @@ private function createAuthenticationListeners($container, $id, $firewall, &$aut throw new InvalidConfigurationException(sprintf('Invalid firewall "%s": user provider "%s" not found.', $id, $firewall[$key]['provider'])); } $userProvider = $providerIds[$normalizedName]; - } elseif($defaultProvider) { + } elseif ('remember_me' === $key) { + // RememberMeFactory will use the firewall secret when created + $userProvider = null; + } elseif ($defaultProvider) { $userProvider = $defaultProvider; } else { throw new InvalidConfigurationException(sprintf('Not configuring explicitly the provider for the "%s" listener on "%s" firewall is ambiguous as there is more than one registered provider.', $key, $id)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml index a51e766005456..6a1f925160fbf 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/argon2i_encoder.yml @@ -1,6 +1,6 @@ security: encoders: - JMS\FooBundle\Entity\User6: + JMS\FooBundle\Entity\User7: algorithm: argon2i providers: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index d60da7386cac8..22d108c59ac56 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -193,6 +193,28 @@ public function testMissingProviderForListener() $container->compile(); } + public function testPerListenerProviderWithRememberMe() + { + $container = $this->getRawContainer(); + $container->loadFromExtension('security', array( + 'providers' => array( + 'first' => array('id' => 'foo'), + 'second' => array('id' => 'bar'), + ), + + 'firewalls' => array( + 'default' => array( + 'form_login' => array('provider' => 'second'), + 'logout_on_user_change' => true, + 'remember_me' => array('secret' => 'baz'), + ), + ), + )); + + $container->compile(); + $this->addToAssertionCount(1); + } + protected function getRawContainer() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php index b38b665798f1f..629ff18a0cb6f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php @@ -86,7 +86,7 @@ public function testEncodePasswordArgon2i() $this->assertContains('Password encoding succeeded', $output); $encoder = new Argon2iPasswordEncoder(); - preg_match('# Encoded password\s+(\$argon2i\$[\w\d,=\$+\/]+={0,2})\s+#', $output, $matches); + preg_match('# Encoded password\s+(\$argon2id\$[\w\d,=\$+\/]+={0,2})\s+#', $output, $matches); $hash = $matches[1]; $this->assertTrue($encoder->isPasswordValid($hash, 'password', null)); } @@ -250,7 +250,7 @@ protected function tearDown() private function setupArgon2i() { putenv('COLUMNS='.(119 + strlen(PHP_EOL))); - $kernel = $this->createKernel(array('test_case' => 'PasswordEncode', 'root_config' => 'argon2i')); + $kernel = $this->createKernel(array('test_case' => 'PasswordEncode', 'root_config' => 'argon2i.yml')); $kernel->boot(); $application = new Application($kernel); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php index f520ab11f0096..d5e6cb6c4acb2 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php @@ -34,11 +34,22 @@ public function process(ContainerBuilder $container) // For instance, global variable definitions must be registered // afterward. If not, the globals from the extensions will never // be registered. - $calls = $definition->getMethodCalls(); - $definition->setMethodCalls(array()); + $currentMethodCalls = $definition->getMethodCalls(); + $twigBridgeExtensionsMethodCalls = array(); + $othersExtensionsMethodCalls = array(); foreach ($container->findTaggedServiceIds('twig.extension', true) as $id => $attributes) { - $definition->addMethodCall('addExtension', array(new Reference($id))); + $methodCall = array('addExtension', array(new Reference($id))); + $extensionClass = $container->getDefinition($id)->getClass(); + + if (is_string($extensionClass) && 0 === strpos($extensionClass, 'Symfony\Bridge\Twig\Extension')) { + $twigBridgeExtensionsMethodCalls[] = $methodCall; + } else { + $othersExtensionsMethodCalls[] = $methodCall; + } + } + + if (!empty($twigBridgeExtensionsMethodCalls) || !empty($othersExtensionsMethodCalls)) { + $definition->setMethodCalls(array_merge($twigBridgeExtensionsMethodCalls, $othersExtensionsMethodCalls, $currentMethodCalls)); } - $definition->setMethodCalls(array_merge($definition->getMethodCalls(), $calls)); } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig index 0ac832ee0610d..1bc9ccf14c06d 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig @@ -37,7 +37,7 @@ addEventListener: addEventListener, createTabs: function() { - var tabGroups = document.querySelectorAll('.sf-tabs'); + var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { @@ -99,11 +99,13 @@ document.getElementById(activeTabId).className = 'block'; }); } + + tabGroups[i].setAttribute('data-processed', 'true'); } }, createToggles: function() { - var toggles = document.querySelectorAll('.sf-toggle'); + var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); for (var i = 0; i < toggles.length; i++) { var elementSelector = toggles[i].getAttribute('data-toggle-selector'); @@ -156,14 +158,16 @@ var altContent = toggle.getAttribute('data-toggle-alt-content'); toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; }); - } - /* Prevents from disallowing clicks on links inside toggles */ - var toggleLinks = document.querySelectorAll('.sf-toggle a'); - for (var i = 0; i < toggleLinks.length; i++) { - addEventListener(toggleLinks[i], 'click', function(e) { - e.stopPropagation(); - }); + /* Prevents from disallowing clicks on links inside toggles */ + var toggleLinks = toggles[i].querySelectorAll('a'); + for (var j = 0; j < toggleLinks.length; j++) { + addEventListener(toggleLinks[j], 'click', function(e) { + e.stopPropagation(); + }); + } + + toggles[i].setAttribute('data-processed', 'true'); } } }; diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigEnvironmentPassTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigEnvironmentPassTest.php new file mode 100644 index 0000000000000..0af3fe4b32532 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigEnvironmentPassTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class TwigEnvironmentPassTest extends TestCase +{ + public function testTwigBridgeExtensionsAreRegisteredFirst() + { + $container = new ContainerBuilder(); + $twigDefinition = $container->register('twig'); + $container->register('other_extension', 'Foo\Bar') + ->addTag('twig.extension'); + $container->register('twig_bridge_extension', FormExtension::class) + ->addTag('twig.extension'); + + $twigEnvironmentPass = new TwigEnvironmentPass(); + $twigEnvironmentPass->process($container); + + $methodCalls = $twigDefinition->getMethodCalls(); + $this->assertCount(2, $methodCalls); + + $twigBridgeExtensionReference = $methodCalls[0][1][0]; + $this->assertInstanceOf(Reference::class, $twigBridgeExtensionReference); + $this->assertSame('twig_bridge_extension', (string) $twigBridgeExtensionReference); + + $otherExtensionReference = $methodCalls[1][1][0]; + $this->assertInstanceOf(Reference::class, $otherExtensionReference); + $this->assertSame('other_extension', (string) $otherExtensionReference); + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php index 6575f28c9736d..b7870ac56c06e 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php @@ -12,14 +12,14 @@ namespace Symfony\Bundle\TwigBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass; class TwigLoaderPassTest extends TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ContainerBuilder */ private $builder; /** @@ -33,64 +33,33 @@ class TwigLoaderPassTest extends TestCase protected function setUp() { - $this->builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'setAlias', 'getDefinition'))->getMock(); + $this->builder = new ContainerBuilder(); + $this->builder->register('twig'); $this->chainLoader = new Definition('loader'); $this->pass = new TwigLoaderPass(); } - public function testMapperPassWithOneTaggedLoaders() + public function testMapperPassWithOneTaggedLoader() { - $serviceIds = array( - 'test_loader_1' => array( - array(), - ), - ); - - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue($serviceIds)); - $this->builder->expects($this->once()) - ->method('setAlias') - ->with('twig.loader', 'test_loader_1') - ->will($this->returnValue(new Alias('test_loader_1'))); + $this->builder->register('test_loader_1') + ->addTag('twig.loader'); $this->pass->process($this->builder); + + $this->assertSame('test_loader_1', (string) $this->builder->getAlias('twig.loader')); } public function testMapperPassWithTwoTaggedLoaders() { - $serviceIds = array( - 'test_loader_1' => array( - array(), - ), - 'test_loader_2' => array( - array(), - ), - ); - - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue($serviceIds)); - $this->builder->expects($this->once()) - ->method('getDefinition') - ->with('twig.loader.chain') - ->will($this->returnValue($this->chainLoader)); - $this->builder->expects($this->once()) - ->method('setAlias') - ->with('twig.loader', 'twig.loader.chain') - ->will($this->returnValue(new Alias('twig.loader.chain'))); + $this->builder->setDefinition('twig.loader.chain', $this->chainLoader); + $this->builder->register('test_loader_1') + ->addTag('twig.loader'); + $this->builder->register('test_loader_2') + ->addTag('twig.loader'); $this->pass->process($this->builder); + + $this->assertSame('twig.loader.chain', (string) $this->builder->getAlias('twig.loader')); $calls = $this->chainLoader->getMethodCalls(); $this->assertCount(2, $calls); $this->assertEquals('addLoader', $calls[0][0]); @@ -101,33 +70,15 @@ public function testMapperPassWithTwoTaggedLoaders() public function testMapperPassWithTwoTaggedLoadersWithPriority() { - $serviceIds = array( - 'test_loader_1' => array( - array('priority' => 100), - ), - 'test_loader_2' => array( - array('priority' => 200), - ), - ); - - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue($serviceIds)); - $this->builder->expects($this->once()) - ->method('getDefinition') - ->with('twig.loader.chain') - ->will($this->returnValue($this->chainLoader)); - $this->builder->expects($this->once()) - ->method('setAlias') - ->with('twig.loader', 'twig.loader.chain') - ->will($this->returnValue(new Alias('twig.loader.chain'))); + $this->builder->setDefinition('twig.loader.chain', $this->chainLoader); + $this->builder->register('test_loader_1') + ->addTag('twig.loader', array('priority' => 100)); + $this->builder->register('test_loader_2') + ->addTag('twig.loader', array('priority' => 200)); $this->pass->process($this->builder); + + $this->assertSame('twig.loader.chain', (string) $this->builder->getAlias('twig.loader')); $calls = $this->chainLoader->getMethodCalls(); $this->assertCount(2, $calls); $this->assertEquals('addLoader', $calls[0][0]); @@ -141,15 +92,6 @@ public function testMapperPassWithTwoTaggedLoadersWithPriority() */ public function testMapperPassWithZeroTaggedLoaders() { - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue(array())); - $this->pass->process($this->builder); } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php index 812e311195993..bab9fe383dc5d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/Configuration.php @@ -38,7 +38,7 @@ public function getConfigTreeBuilder() ->children() ->booleanNode('toolbar')->defaultFalse()->end() ->booleanNode('intercept_redirects')->defaultFalse()->end() - ->scalarNode('excluded_ajax_paths')->defaultValue('^/(app(_[\\w]+)?\\.php/)?_wdt')->end() + ->scalarNode('excluded_ajax_paths')->defaultValue('^/((index|app(_[\w]+)?)\.php/)?_wdt')->end() ->end() ; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 8f94f6f5f0bd3..a1ff4afd01f63 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -58,7 +58,7 @@
Route name - {{ collector.route|default('NONE') }} + {{ collector.route|default('n/a') }}
@@ -271,9 +271,7 @@
{% for child in profile.children %}

- - {{ helper.set_handler(child.getcollector('request').controller) }} - + {{ helper.set_handler(child.getcollector('request').controller) }} (token = {{ child.token }})

diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index aac9e3a0d4725..a59e7811d6c87 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -39,7 +39,7 @@ setTimeout(function(){ options.maxTries--; request(url, onSuccess, onError, payload, options); - }, 500); + }, 1000); return null; } @@ -122,6 +122,11 @@ return; } + var nbOfAjaxRequest = tbody.rows.count(); + if (nbOfAjaxRequest >= 100) { + tbody.deleteRow(nbOfAjaxRequest - 1); + } + var request = requestStack[index]; pendingRequests++; var row = document.createElement('tr'); @@ -393,7 +398,7 @@ }, createTabs: function() { - var tabGroups = document.querySelectorAll('.sf-tabs'); + var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])'); /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { @@ -455,11 +460,13 @@ document.getElementById(activeTabId).className = 'block'; }); } + + tabGroups[i].setAttribute('data-processed', 'true'); } }, createToggles: function() { - var toggles = document.querySelectorAll('.sf-toggle'); + var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); for (var i = 0; i < toggles.length; i++) { var elementSelector = toggles[i].getAttribute('data-toggle-selector'); @@ -512,14 +519,16 @@ var altContent = toggle.getAttribute('data-toggle-alt-content'); toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; }); - } - /* Prevents from disallowing clicks on links inside toggles */ - var toggleLinks = document.querySelectorAll('.sf-toggle a'); - for (var i = 0; i < toggleLinks.length; i++) { - addEventListener(toggleLinks[i], 'click', function(e) { - e.stopPropagation(); - }); + /* Prevents from disallowing clicks on links inside toggles */ + var toggleLinks = toggles[i].querySelectorAll('a'); + for (var j = 0; j < toggleLinks.length; j++) { + addEventListener(toggleLinks[j], 'click', function(e) { + e.stopPropagation(); + }); + } + + toggles[i].setAttribute('data-processed', 'true'); } } }; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig index d0f5cda02dccc..f69406475a2af 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/open.css.twig @@ -68,7 +68,7 @@ a.doc:hover { .anchor { position: relative; - display: block; + display: inline-block; top: -7em; visibility: hidden; } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/ConfigurationTest.php index abd9c033d1bf7..12672d7e02566 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -32,10 +32,10 @@ public function testConfigTree($options, $results) public function getDebugModes() { return array( - array(array(), array('intercept_redirects' => false, 'toolbar' => false, 'excluded_ajax_paths' => '^/(app(_[\\w]+)?\\.php/)?_wdt')), - array(array('intercept_redirects' => true), array('intercept_redirects' => true, 'toolbar' => false, 'excluded_ajax_paths' => '^/(app(_[\\w]+)?\\.php/)?_wdt')), - array(array('intercept_redirects' => false), array('intercept_redirects' => false, 'toolbar' => false, 'excluded_ajax_paths' => '^/(app(_[\\w]+)?\\.php/)?_wdt')), - array(array('toolbar' => true), array('intercept_redirects' => false, 'toolbar' => true, 'excluded_ajax_paths' => '^/(app(_[\\w]+)?\\.php/)?_wdt')), + array(array(), array('intercept_redirects' => false, 'toolbar' => false, 'excluded_ajax_paths' => '^/((index|app(_[\w]+)?)\.php/)?_wdt')), + array(array('intercept_redirects' => true), array('intercept_redirects' => true, 'toolbar' => false, 'excluded_ajax_paths' => '^/((index|app(_[\w]+)?)\.php/)?_wdt')), + array(array('intercept_redirects' => false), array('intercept_redirects' => false, 'toolbar' => false, 'excluded_ajax_paths' => '^/((index|app(_[\w]+)?)\.php/)?_wdt')), + array(array('toolbar' => true), array('intercept_redirects' => false, 'toolbar' => true, 'excluded_ajax_paths' => '^/((index|app(_[\w]+)?)\.php/)?_wdt')), array(array('excluded_ajax_paths' => 'test'), array('intercept_redirects' => false, 'toolbar' => false, 'excluded_ajax_paths' => 'test')), ); } diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index e8563521baef8..98d0e526933b9 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -204,11 +204,12 @@ public function reset() public function getCalls() { - try { - return $this->calls; - } finally { - $this->calls = array(); - } + return $this->calls; + } + + public function clearCalls() + { + $this->calls = array(); } protected function start($name) diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index 33e08f167fbdc..e4b08790ade4c 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -57,8 +57,7 @@ public function reset() { $this->data = array(); foreach ($this->instances as $instance) { - // Calling getCalls() will clear the calls. - $instance->getCalls(); + $instance->clearCalls(); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/NullAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/NullAdapterTest.php index c28a4550263df..73e5cad5529a9 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/NullAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/NullAdapterTest.php @@ -53,7 +53,7 @@ public function testGetItems() $itemKey = $item->getKey(); $this->assertEquals($itemKey, $key, 'Keys must be preserved when fetching multiple items'); - $this->assertTrue(in_array($key, $keys), 'Cache key can not change.'); + $this->assertContains($key, $keys, 'Cache key can not change.'); $this->assertFalse($item->isHit()); // Remove $key for $keys diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php index 5e6abd16c18c4..ff4b9d34bcbaf 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php @@ -43,7 +43,7 @@ public function testProxyfiedItem() $proxyItem = $pool->getItem('foo'); - $this->assertFalse($proxyItem === $item); + $this->assertNotSame($item, $proxyItem); $pool->save($proxyItem->set('bar')); } } diff --git a/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php index 16dd7764d2ca1..7b760fd3bddaa 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php @@ -47,7 +47,7 @@ public function testGetMultiple() $count = 0; foreach ($items as $key => $item) { - $this->assertTrue(in_array($key, $keys), 'Cache key can not change.'); + $this->assertContains($key, $keys, 'Cache key can not change.'); $this->assertSame($default, $item); // Remove $key for $keys diff --git a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php index bcb940cb0f754..23974b3bc5487 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php @@ -36,9 +36,9 @@ public function prune() continue; } - if ($time >= (int) $expiresAt = fgets($h)) { + if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { fclose($h); - $pruned = isset($expiresAt[0]) && @unlink($file) && !file_exists($file) && $pruned; + $pruned = @unlink($file) && !file_exists($file) && $pruned; } else { fclose($h); } @@ -60,11 +60,9 @@ protected function doFetch(array $ids) if (!file_exists($file) || !$h = @fopen($file, 'rb')) { continue; } - if ($now >= (int) $expiresAt = fgets($h)) { + if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) { fclose($h); - if (isset($expiresAt[0])) { - @unlink($file); - } + @unlink($file); } else { $i = rawurldecode(rtrim(fgets($h))); $value = stream_get_contents($h); @@ -94,7 +92,7 @@ protected function doHave($id) protected function doSave(array $values, $lifetime) { $ok = true; - $expiresAt = time() + ($lifetime ?: 31557600); // 31557600s = 1 year + $expiresAt = $lifetime ? (time() + $lifetime) : 0; foreach ($values as $id => $value) { $ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok; diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php index 136d2d42dc396..c06171992aad6 100644 --- a/src/Symfony/Component/Config/Loader/FileLoader.php +++ b/src/Symfony/Component/Config/Loader/FileLoader.php @@ -70,7 +70,7 @@ public function getLocator() * @throws FileLoaderImportCircularReferenceException * @throws FileLocatorFileNotFoundException */ - public function import($resource, $type = null, bool $ignoreErrors = false, $sourceResource = null) + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) { if (is_string($resource) && strlen($resource) !== $i = strcspn($resource, '*?{[')) { $ret = array(); diff --git a/src/Symfony/Component/Config/Resource/FileResource.php b/src/Symfony/Component/Config/Resource/FileResource.php index 8fa97162d4de7..78d552a5ab63b 100644 --- a/src/Symfony/Component/Config/Resource/FileResource.php +++ b/src/Symfony/Component/Config/Resource/FileResource.php @@ -60,7 +60,7 @@ public function getResource() */ public function isFresh($timestamp) { - return file_exists($this->resource) && @filemtime($this->resource) <= $timestamp; + return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp; } public function serialize() diff --git a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php index 35cdcc4c1c2c7..c5b7abf8960e7 100644 --- a/src/Symfony/Component/Config/Resource/ReflectionClassResource.php +++ b/src/Symfony/Component/Config/Resource/ReflectionClassResource.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Config\Resource; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + /** * @author Nicolas Grekas */ @@ -37,11 +40,11 @@ public function isFresh($timestamp) } foreach ($this->files as $file => $v) { - if (!file_exists($file)) { + if (false === $filemtime = @filemtime($file)) { return false; } - if (@filemtime($file) > $timestamp) { + if ($filemtime > $timestamp) { return $this->hash === $this->computeHash(); } } @@ -114,7 +117,9 @@ private function computeHash() private function generateSignature(\ReflectionClass $class) { - yield $class->getDocComment().$class->getModifiers(); + yield $class->getDocComment(); + yield (int) $class->isFinal(); + yield (int) $class->isAbstract(); if ($class->isTrait()) { yield print_r(class_uses($class->name), true); @@ -142,5 +147,15 @@ private function generateSignature(\ReflectionClass $class) } yield print_r($defaults, true); } + + if ($class->isSubclassOf(EventSubscriberInterface::class)) { + yield EventSubscriberInterface::class; + yield print_r(\call_user_func(array($class->name, 'getSubscribedEvents')), true); + } + + if ($class->isSubclassOf(ServiceSubscriberInterface::class)) { + yield ServiceSubscriberInterface::class; + yield print_r(\call_user_func(array($class->name, 'getSubscribedServices')), true); + } } } diff --git a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php index 299b593d71dff..229e83e89bbcb 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/ReflectionClassResourceTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Resource\ReflectionClassResource; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; class ReflectionClassResourceTest extends TestCase { @@ -100,7 +102,7 @@ public function testHashedSignature($changeExpected, $changedLine, $changedCode) $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); if ($changeExpected) { - $this->assertTrue($expectedSignature !== $signature); + $this->assertNotSame($expectedSignature, $signature); } else { $this->assertSame($expectedSignature, $signature); } @@ -132,8 +134,52 @@ public function provideHashedSignature() yield array(0, 14, '/** priv docblock */'); yield array(0, 15, ''); } + + public function testEventSubscriber() + { + $res = new ReflectionClassResource(new \ReflectionClass(TestEventSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + + TestEventSubscriber::$subscribedEvents = array(123); + $this->assertFalse($res->isFresh(0)); + + $res = new ReflectionClassResource(new \ReflectionClass(TestEventSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + } + + public function testServiceSubscriber() + { + $res = new ReflectionClassResource(new \ReflectionClass(TestServiceSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + + TestServiceSubscriber::$subscribedServices = array(123); + $this->assertFalse($res->isFresh(0)); + + $res = new ReflectionClassResource(new \ReflectionClass(TestServiceSubscriber::class)); + $this->assertTrue($res->isFresh(0)); + } } interface DummyInterface { } + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static $subscribedEvents = array(); + + public static function getSubscribedEvents() + { + return self::$subscribedEvents; + } +} + +class TestServiceSubscriber implements ServiceSubscriberInterface +{ + public static $subscribedServices = array(); + + public static function getSubscribedServices() + { + return self::$subscribedServices; + } +} diff --git a/src/Symfony/Component/Config/composer.json b/src/Symfony/Component/Config/composer.json index 93b1ba6f1e024..ea00fa5e2f8cc 100644 --- a/src/Symfony/Component/Config/composer.json +++ b/src/Symfony/Component/Config/composer.json @@ -20,6 +20,8 @@ "symfony/filesystem": "~3.4|~4.0" }, "require-dev": { + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 4441296b7f1a7..edeabdbf257bc 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -277,7 +277,11 @@ public function hasParameterOption($values, $onlyParams = false) return false; } foreach ($values as $value) { - if ($token === $value || 0 === strpos($token, $value.'=')) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = 0 === strpos($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) { return true; } } @@ -301,13 +305,16 @@ public function getParameterOption($values, $default = false, $onlyParams = fals } foreach ($values as $value) { - if ($token === $value || 0 === strpos($token, $value.'=')) { - if (false !== $pos = strpos($token, '=')) { - return substr($token, $pos + 1); - } - + if ($token === $value) { return array_shift($tokens); } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = 0 === strpos($value, '--') ? $value.'=' : $value; + if ('' !== $leading && 0 === strpos($token, $leading)) { + return substr($token, strlen($leading)); + } } } diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index e6c28de978cb1..4d9797ba1af0c 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -114,7 +114,7 @@ public function __toString() $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); } } else { - $params[] = is_array($val) ? array_map(array($this, 'escapeToken'), $val) : $this->escapeToken($val); + $params[] = is_array($val) ? implode(' ', array_map(array($this, 'escapeToken'), $val)) : $this->escapeToken($val); } } diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index b9e0651faf5db..43810f7ac21bd 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php @@ -33,6 +33,8 @@ public function getFirstArgument(); * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. * * @param string|array $values The values to look for in the raw parameters (can be an array) * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal @@ -46,6 +48,8 @@ public function hasParameterOption($values, $onlyParams = false); * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found diff --git a/src/Symfony/Component/Console/Output/StreamOutput.php b/src/Symfony/Component/Console/Output/StreamOutput.php index e0ade2482db5c..617beec986e26 100644 --- a/src/Symfony/Component/Console/Output/StreamOutput.php +++ b/src/Symfony/Component/Console/Output/StreamOutput.php @@ -92,7 +92,8 @@ protected function hasColorSupport() { if (DIRECTORY_SEPARATOR === '\\') { return - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD + function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support($this->stream) + || '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); diff --git a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php index 8287bce521d37..61d1723e0842e 100644 --- a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php +++ b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php @@ -314,6 +314,10 @@ public function testHasParameterOption() $input = new ArgvInput(array('cli.php', '-f', 'foo')); $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '-etest')); + $this->assertTrue($input->hasParameterOption('-e'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $this->assertFalse($input->hasParameterOption('-s'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '--foo', 'foo')); $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given short option is in the raw input'); @@ -339,6 +343,46 @@ public function testHasParameterOptionOnlyOptions() $this->assertFalse($input->hasParameterOption('--foo', true), '->hasParameterOption() returns false if the given option is in the raw input but after an end of options signal'); } + public function testHasParameterOptionEdgeCasesAndLimitations() + { + $input = new ArgvInput(array('cli.php', '-fh')); + // hasParameterOption does not know if the previous short option, -f, + // takes a value or not. If -f takes a value, then -fh does NOT include + // -h; Otherwise it does. Since we do not know which short options take + // values, hasParameterOption does not support this use-case. + $this->assertFalse($input->hasParameterOption('-h'), '->hasParameterOption() returns true if the given short option is in the raw input'); + // hasParameterOption does detect that `-fh` contains `-f`, since + // `-f` is the first short option in the set. + $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + // The test below happens to pass, although it might make more sense + // to disallow it, and require the use of + // $input->hasParameterOption('-f') && $input->hasParameterOption('-h') + // instead. + $this->assertTrue($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + // In theory, if -fh is supported, then -hf should also work. + // However, this is not supported. + $this->assertFalse($input->hasParameterOption('-hf'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', '-f', '-h')); + // If hasParameterOption('-fh') is supported for 'cli.php -fh', then + // one might also expect that it should also be supported for + // 'cli.php -f -h'. However, this is not supported. + $this->assertFalse($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + } + + public function testNoWarningOnInvalidParameterOption() + { + $input = new ArgvInput(array('cli.php', '-edev')); + + $this->assertTrue($input->hasParameterOption(array('-e', ''))); + // No warning thrown + $this->assertFalse($input->hasParameterOption(array('-m', ''))); + + $this->assertEquals('dev', $input->getParameterOption(array('-e', ''))); + // No warning thrown + $this->assertFalse($input->getParameterOption(array('-m', ''))); + } + public function testToString() { $input = new ArgvInput(array('cli.php', '-f', 'foo')); diff --git a/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php b/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php index 3e8584352a4ae..6b443e0b2abae 100644 --- a/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php +++ b/src/Symfony/Component/Console/Tests/Input/ArrayInputTest.php @@ -170,5 +170,8 @@ public function testToString() $input = new ArrayInput(array('-b' => array('bval_1', 'bval_2'), '--f' => array('fval_1', 'fval_2'))); $this->assertSame('-b=bval_1 -b=bval_2 --f=fval_1 --f=fval_2', (string) $input); + + $input = new ArrayInput(array('array_arg' => array('val_1', 'val_2'))); + $this->assertSame('val_1 val_2', (string) $input); } } diff --git a/src/Symfony/Component/CssSelector/Tests/CssSelectorConverterTest.php b/src/Symfony/Component/CssSelector/Tests/CssSelectorConverterTest.php index a27fadfef1e70..a3eea7ad21869 100644 --- a/src/Symfony/Component/CssSelector/Tests/CssSelectorConverterTest.php +++ b/src/Symfony/Component/CssSelector/Tests/CssSelectorConverterTest.php @@ -59,7 +59,7 @@ public function getCssToXPathWithoutPrefixTestData() array('h1', 'h1'), array('foo|h1', 'foo:h1'), array('h1, h2, h3', 'h1 | h2 | h3'), - array('h1:nth-child(3n+1)', "*/*[name() = 'h1' and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"), + array('h1:nth-child(3n+1)', "*/*[(name() = 'h1') and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"), array('h1 > p', 'h1/p'), array('h1#foo', "h1[@id = 'foo']"), array('h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), diff --git a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php index 218a3b43c1a78..519417835cefe 100644 --- a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php +++ b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php @@ -102,18 +102,20 @@ public function getCssToXPathTestData() array('e[foo^="bar"]', "e[@foo and starts-with(@foo, 'bar')]"), array('e[foo$="bar"]', "e[@foo and substring(@foo, string-length(@foo)-2) = 'bar']"), array('e[foo*="bar"]', "e[@foo and contains(@foo, 'bar')]"), + array('e[foo!="bar"]', "e[not(@foo) or @foo != 'bar']"), + array('e[foo!="bar"][foo!="baz"]', "e[(not(@foo) or @foo != 'bar') and (not(@foo) or @foo != 'baz')]"), array('e[hreflang|="en"]', "e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]"), - array('e:nth-child(1)', "*/*[name() = 'e' and (position() = 1)]"), - array('e:nth-last-child(1)', "*/*[name() = 'e' and (position() = last() - 0)]"), - array('e:nth-last-child(2n+2)', "*/*[name() = 'e' and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"), + array('e:nth-child(1)', "*/*[(name() = 'e') and (position() = 1)]"), + array('e:nth-last-child(1)', "*/*[(name() = 'e') and (position() = last() - 0)]"), + array('e:nth-last-child(2n+2)', "*/*[(name() = 'e') and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"), array('e:nth-of-type(1)', '*/e[position() = 1]'), array('e:nth-last-of-type(1)', '*/e[position() = last() - 0]'), array('div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"), - array('e:first-child', "*/*[name() = 'e' and (position() = 1)]"), - array('e:last-child', "*/*[name() = 'e' and (position() = last())]"), + array('e:first-child', "*/*[(name() = 'e') and (position() = 1)]"), + array('e:last-child', "*/*[(name() = 'e') and (position() = last())]"), array('e:first-of-type', '*/e[position() = 1]'), array('e:last-of-type', '*/e[position() = last()]'), - array('e:only-child', "*/*[name() = 'e' and (last() = 1)]"), + array('e:only-child', "*/*[(name() = 'e') and (last() = 1)]"), array('e:only-of-type', 'e[last() = 1]'), array('e:empty', 'e[not(*) and not(string-length())]'), array('e:EmPTY', 'e[not(*) and not(string-length())]'), @@ -127,7 +129,7 @@ public function getCssToXPathTestData() array('e:nOT(*)', 'e[0]'), array('e f', 'e/descendant-or-self::*/f'), array('e > f', 'e/f'), - array('e + f', "e/following-sibling::*[name() = 'f' and (position() = 1)]"), + array('e + f', "e/following-sibling::*[(name() = 'f') and (position() = 1)]"), array('e ~ f', 'e/following-sibling::f'), array('div#container p', "div[@id = 'container']/descendant-or-self::*/p"), ); diff --git a/src/Symfony/Component/CssSelector/XPath/XPathExpr.php b/src/Symfony/Component/CssSelector/XPath/XPathExpr.php index 0b958fa1ad98e..8090df99075fa 100644 --- a/src/Symfony/Component/CssSelector/XPath/XPathExpr.php +++ b/src/Symfony/Component/CssSelector/XPath/XPathExpr.php @@ -45,7 +45,7 @@ public function getElement(): string public function addCondition(string $condition): XPathExpr { - $this->condition = $this->condition ? sprintf('%s and (%s)', $this->condition, $condition) : $condition; + $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; return $this; } @@ -77,7 +77,7 @@ public function addStarPrefix(): XPathExpr * * @return $this */ - public function join(string $combiner, XPathExpr $expr): XPathExpr + public function join(string $combiner, self $expr): self { $path = $this->__toString().$combiner; diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 0a13ac1e66b9e..f9c80d041bcfb 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -140,7 +140,7 @@ public function loadClass($class) if ($this->isFinder && !isset($this->loaded[$class])) { $this->loaded[$class] = true; if ($file = $this->classLoader[0]->findFile($class) ?: false) { - $wasCached = \function_exists('opcache_is_script_cached') && opcache_is_script_cached($file); + $wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file); require $file; diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index 047883a70c264..00149b38e53d4 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -133,9 +133,20 @@ public static function register(self $handler = null, $replace = true) } if (!$replace && $prev) { restore_error_handler(); + $handlerIsRegistered = is_array($prev) && $handler === $prev[0]; + } else { + $handlerIsRegistered = true; } - if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] === $handler) { + if (is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) { restore_exception_handler(); + if (!$handlerIsRegistered) { + $handler = $prev[0]; + } elseif ($handler !== $prev[0] && $replace) { + set_exception_handler(array($handler, 'handleException')); + $p = $prev[0]->setExceptionHandler(null); + $handler->setExceptionHandler($p); + $prev[0]->setExceptionHandler($p); + } } else { $handler->setExceptionHandler($prev); } diff --git a/src/Symfony/Component/Debug/Exception/FatalThrowableError.php b/src/Symfony/Component/Debug/Exception/FatalThrowableError.php index 34f43b17b13b4..fafc92263e704 100644 --- a/src/Symfony/Component/Debug/Exception/FatalThrowableError.php +++ b/src/Symfony/Component/Debug/Exception/FatalThrowableError.php @@ -36,7 +36,8 @@ public function __construct(\Throwable $e) $e->getCode(), $severity, $e->getFile(), - $e->getLine() + $e->getLine(), + $e->getPrevious() ); $this->setTrace($e->getTrace()); diff --git a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php index 25a7d3b19673c..1580ca993ba58 100644 --- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php @@ -212,7 +212,7 @@ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true); $xError = array( 'type' => E_USER_DEPRECATED, - 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".', + 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".', ); $this->assertSame($xError, $lastError); @@ -234,7 +234,7 @@ class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true); $xError = array( 'type' => E_USER_DEPRECATED, - 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".', + 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".', ); $this->assertSame($xError, $lastError); @@ -269,10 +269,10 @@ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true); restore_error_handler(); $this->assertSame($deprecations, array( - 'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal since version 3.4. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".', + 'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".', 'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternalsParent".', 'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', - 'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait2::internalMethod()" method is considered internal since version 3.4. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', + 'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait2::internalMethod()" method is considered internal. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', )); } } diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index 4b7bcc3cddb2f..0d57204481e13 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -35,7 +35,7 @@ public function testRegister() $newHandler = new ErrorHandler(); - $this->assertSame($newHandler, ErrorHandler::register($newHandler, false)); + $this->assertSame($handler, ErrorHandler::register($newHandler, false)); $h = set_error_handler('var_dump'); restore_error_handler(); $this->assertSame(array($handler, 'handleError'), $h); diff --git a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php index c4cf58315a4e3..5c781dbf42da9 100644 --- a/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php @@ -111,7 +111,7 @@ public function testHeadersForHttpException() /** * @dataProvider flattenDataProvider */ - public function testFlattenHttpException(\Exception $exception, $statusCode) + public function testFlattenHttpException(\Exception $exception) { $flattened = FlattenException::create($exception); $flattened2 = FlattenException::create($exception); @@ -126,7 +126,7 @@ public function testFlattenHttpException(\Exception $exception, $statusCode) /** * @dataProvider flattenDataProvider */ - public function testPrevious(\Exception $exception, $statusCode) + public function testPrevious(\Exception $exception) { $flattened = FlattenException::create($exception); $flattened2 = FlattenException::create($exception); @@ -170,7 +170,7 @@ public function testFile(\Exception $exception) /** * @dataProvider flattenDataProvider */ - public function testToArray(\Exception $exception, $statusCode) + public function testToArray(\Exception $exception) { $flattened = FlattenException::create($exception); $flattened->setTrace(array(), 'foo.php', 123); @@ -190,7 +190,7 @@ public function testToArray(\Exception $exception, $statusCode) public function flattenDataProvider() { return array( - array(new \Exception('test', 123), 500), + array(new \Exception('test', 123)), ); } diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/AnnotatedClass.php b/src/Symfony/Component/Debug/Tests/Fixtures/AnnotatedClass.php index dff9517d0a046..4eecb6d3f1668 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/AnnotatedClass.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/AnnotatedClass.php @@ -5,7 +5,7 @@ class AnnotatedClass { /** - * @deprecated since version 3.4. + * @deprecated */ public function deprecatedMethod() { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/FinalClass.php b/src/Symfony/Component/Debug/Tests/Fixtures/FinalClass.php index 2cf26b19e4fc8..f4c69b85322b7 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/FinalClass.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/FinalClass.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures; /** - * @final since version 3.3. + * @final */ class FinalClass { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/FinalMethod.php b/src/Symfony/Component/Debug/Tests/Fixtures/FinalMethod.php index 92ec421863f3f..d8c673a5c51b0 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/FinalMethod.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/FinalMethod.php @@ -5,7 +5,7 @@ class FinalMethod { /** - * @final since version 3.3. + * @final */ public function finalMethod() { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php index 119842c260027..30efe79b333af 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures; /** - * @internal since version 3.4. + * @internal */ class InternalClass { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php index 05f18e83e4a9e..e4cbe12aec298 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php @@ -8,7 +8,7 @@ trait InternalTrait2 { /** - * @internal since version 3.4 + * @internal */ public function internalMethod() { diff --git a/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt b/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt index b9d3d7288714e..685280bc8a18f 100644 --- a/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt +++ b/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt @@ -23,4 +23,4 @@ class_exists(ExtendedFinalMethod::class); ?> --EXPECTF-- -The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod". +The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod". diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index 4ed990ac08d2b..572eabb7d99e7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -56,14 +56,14 @@ protected function processValue($value, $isRoot = false) } $class = $value->getClass(); - if (!is_subclass_of($class, ServiceSubscriberInterface::class)) { - if (!class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId)); - } - + if (!$r = $this->container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId)); + } + if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) { throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class)); } - $this->container->addObjectResource($class); + $class = $r->name; + $subscriberMap = array(); $declaringClass = (new \ReflectionMethod($class, 'getSubscribedServices'))->class; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php index 5a370398408dc..721e87568326e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php @@ -21,7 +21,7 @@ * * @author Johannes M. Schmitt * - * @final since version 3.4 + * @final */ class ServiceReferenceGraph { diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 6bd7e1974466c..8d7011c1b015c 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1397,7 +1397,7 @@ public function log(CompilerPassInterface $pass, string $message) * * @return array An array of Service conditionals * - * @internal since version 3.4 + * @internal */ public static function getServiceConditionals($value) { diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 989725d53db5b..9865adbd7a94f 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -128,7 +128,7 @@ public function getFactory() */ public function setDecoratedService($id, $renamedId = null, $priority = 0) { - if ($renamedId && $id == $renamedId) { + if ($renamedId && $id === $renamedId) { throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index b7f1efdacaa05..4e475fe0b81b9 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -120,6 +120,7 @@ public function dump(array $options = array()) 'debug' => true, 'hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', + 'build_time' => time(), ), $options); $this->namespace = $options['namespace']; @@ -129,6 +130,11 @@ public function dump(array $options = array()) if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); + $baseClassWithNamespace = $baseClass; + } elseif ('Container' === $baseClass) { + $baseClassWithNamespace = Container::class; + } else { + $baseClassWithNamespace = $baseClass; } $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); @@ -170,7 +176,7 @@ public function dump(array $options = array()) } $code = - $this->startClass($options['class'], $baseClass). + $this->startClass($options['class'], $baseClass, $baseClassWithNamespace). $this->addServices(). $this->addDefaultParametersMethod(). $this->endClass() @@ -218,7 +224,7 @@ public function dump(array $options = array()) array_pop($code); $code["Container{$hash}/{$options['class']}.php"] = substr_replace($files[$options['class'].'.php'], "namespace ? "\nnamespace {$this->namespace};\n" : ''; - $time = time(); + $time = $options['build_time']; $id = hash('crc32', $hash.$time); $code[$options['class'].'.php'] = <<isProxyCandidate($definition)) { continue; } + // register class' reflector for resource tracking + $this->container->getReflectionClass($definition->getClass()); $proxyCode = "\n".$proxyDumper->getProxyCode($definition); if ($strip) { $proxyCode = "dumpLiteralClass($class), implode(', ', $arguments)); } - private function startClass(string $class, string $baseClass): string + private function startClass(string $class, string $baseClass, string $baseClassWithNamespace): string { $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; @@ -910,6 +918,14 @@ public function __construct() $code .= " \$this->buildParameters = \$buildParameters;\n"; } + if (Container::class !== $baseClassWithNamespace) { + $r = $this->container->getReflectionClass($baseClassWithNamespace, false); + + if (null !== $r && (null !== $constructor = $r->getConstructor()) && 0 === $constructor->getNumberOfRequiredParameters()) { + $code .= " parent::__construct();\n\n"; + } + } + if ($this->container->getParameterBag()->all()) { $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; } @@ -970,10 +986,10 @@ protected function load(\$file, \$lazyLoad = true) } $code .= << * - * @final since version 3.3 + * @final */ class NullDumper implements DumperInterface { diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index 42cccaa186659..83a3f4f87ca45 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -60,11 +60,16 @@ public function registerClasses(Definition $prototype, $namespace, $resource, $e $interfaces = array(); $singlyImplemented = array(); - foreach ($classes as $class) { + foreach ($classes as $class => $errorMessage) { if (interface_exists($class, false)) { $interfaces[] = $class; } else { - $this->setDefinition($class, unserialize($serializedPrototype)); + $this->setDefinition($class, $definition = unserialize($serializedPrototype)); + if (null !== $errorMessage) { + $definition->addError($errorMessage); + + continue; + } foreach (class_implements($class, false) as $interface) { $singlyImplemented[$interface] = isset($singlyImplemented[$interface]) ? false : $class; } @@ -139,13 +144,25 @@ private function findClasses($namespace, $pattern, $excludePattern) if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $class)) { continue; } + + try { + $r = $this->container->getReflectionClass($class); + } catch (\ReflectionException $e) { + $classes[$class] = sprintf( + 'While discovering services from namespace "%s", an error was thrown when processing the class "%s": "%s".', + $namespace, + $class, + $e->getMessage() + ); + continue; + } // check to make sure the expected class exists - if (!$r = $this->container->getReflectionClass($class)) { + if (!$r) { throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); } if ($r->isInstantiable() || $r->isInterface()) { - $classes[] = $class; + $classes[$class] = null; } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php new file mode 100644 index 0000000000000..4b25610efe48e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Loader/GlobFileLoader.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +/** + * GlobFileLoader loads files from a glob pattern. + * + * @author Nicolas Grekas + */ +class GlobFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + foreach ($this->glob($resource, false, $globResource) as $path => $info) { + $this->import($path); + } + + $this->container->addResource($globResource); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'glob' === $type; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ExtensionCompilerPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ExtensionCompilerPassTest.php index e083611458770..810fbe48a573f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ExtensionCompilerPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ExtensionCompilerPassTest.php @@ -12,7 +12,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ExtensionCompilerPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; /** * @author Wouter J @@ -24,33 +27,55 @@ class ExtensionCompilerPassTest extends TestCase protected function setUp() { - $this->container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->getMock(); + $this->container = new ContainerBuilder(); $this->pass = new ExtensionCompilerPass(); } public function testProcess() { - $extension1 = $this->createExtensionMock(true); - $extension1->expects($this->once())->method('process'); - $extension2 = $this->createExtensionMock(false); - $extension3 = $this->createExtensionMock(false); - $extension4 = $this->createExtensionMock(true); - $extension4->expects($this->once())->method('process'); - - $this->container->expects($this->any()) - ->method('getExtensions') - ->will($this->returnValue(array($extension1, $extension2, $extension3, $extension4))) - ; + $extension1 = new CompilerPassExtension('extension1'); + $extension2 = new DummyExtension('extension2'); + $extension3 = new DummyExtension('extension3'); + $extension4 = new CompilerPassExtension('extension4'); + + $this->container->registerExtension($extension1); + $this->container->registerExtension($extension2); + $this->container->registerExtension($extension3); + $this->container->registerExtension($extension4); $this->pass->process($this->container); + + $this->assertTrue($this->container->hasDefinition('extension1')); + $this->assertFalse($this->container->hasDefinition('extension2')); + $this->assertFalse($this->container->hasDefinition('extension3')); + $this->assertTrue($this->container->hasDefinition('extension4')); } +} + +class DummyExtension extends Extension +{ + private $alias; - private function createExtensionMock($hasInlineCompile) + public function __construct($alias) { - return $this->getMockBuilder('Symfony\Component\DependencyInjection\\'.( - $hasInlineCompile - ? 'Compiler\CompilerPassInterface' - : 'Extension\ExtensionInterface' - ))->getMock(); + $this->alias = $alias; } + + public function getAlias() + { + return $this->alias; + } + + public function load(array $configs, ContainerBuilder $container) + { + } + + public function process(ContainerBuilder $container) + { + $container->register($this->alias); + } +} + +class CompilerPassExtension extends DummyExtension implements CompilerPassInterface +{ } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php index fccda7e129847..77872720aa4fb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php @@ -110,7 +110,7 @@ public function testProcessedEnvsAreIncompatibleWithResolve() { $container = new ContainerBuilder(); $container->registerExtension(new BarExtension()); - $container->prependExtensionConfig('bar', []); + $container->prependExtensionConfig('bar', array()); (new MergeExtensionConfigurationPass())->process($container); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 2025cbe943764..a4b0277b637e5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -106,6 +106,46 @@ public function testDumpRelativeDir() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services12.php', $dumper->dump(array('file' => __FILE__)), '->dump() dumps __DIR__ relative strings'); } + public function testDumpCustomContainerClassWithoutConstructor() + { + $container = new ContainerBuilder(); + $container->compile(); + + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/custom_container_class_without_constructor.php', $dumper->dump(array('base_class' => 'NoConstructorContainer', 'namespace' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\Container'))); + } + + public function testDumpCustomContainerClassConstructorWithoutArguments() + { + $container = new ContainerBuilder(); + $container->compile(); + + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/custom_container_class_constructor_without_arguments.php', $dumper->dump(array('base_class' => 'ConstructorWithoutArgumentsContainer', 'namespace' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\Container'))); + } + + public function testDumpCustomContainerClassWithOptionalArgumentLessConstructor() + { + $container = new ContainerBuilder(); + $container->compile(); + + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/custom_container_class_with_optional_constructor_arguments.php', $dumper->dump(array('base_class' => 'ConstructorWithOptionalArgumentsContainer', 'namespace' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\Container'))); + } + + public function testDumpCustomContainerClassWithMandatoryArgumentLessConstructor() + { + $container = new ContainerBuilder(); + $container->compile(); + + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/custom_container_class_with_mandatory_constructor_arguments.php', $dumper->dump(array('base_class' => 'ConstructorWithMandatoryArgumentsContainer', 'namespace' => 'Symfony\Component\DependencyInjection\Tests\Fixtures\Container'))); + } + /** * @dataProvider provideInvalidParameters * @expectedException \InvalidArgumentException diff --git a/src/Symfony/Component/DependencyInjection/Tests/Extension/ExtensionTest.php b/src/Symfony/Component/DependencyInjection/Tests/Extension/ExtensionTest.php index 90852c359e514..9f66bfd7c6802 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Extension/ExtensionTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Extension/ExtensionTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\DependencyInjection\Tests\Extension; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; class ExtensionTest extends TestCase { @@ -20,36 +22,8 @@ class ExtensionTest extends TestCase */ public function testIsConfigEnabledReturnsTheResolvedValue($enabled) { - $pb = $this->getMockBuilder('Symfony\Component\DependencyInjection\ParameterBag\ParameterBag') - ->setMethods(array('resolveValue')) - ->getMock() - ; - - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder') - ->setMethods(array('getParameterBag')) - ->getMock() - ; - - $pb->expects($this->once()) - ->method('resolveValue') - ->with($this->equalTo($enabled)) - ->will($this->returnValue($enabled)) - ; - - $container->expects($this->once()) - ->method('getParameterBag') - ->will($this->returnValue($pb)) - ; - - $extension = $this->getMockBuilder('Symfony\Component\DependencyInjection\Extension\Extension') - ->setMethods(array()) - ->getMockForAbstractClass() - ; - - $r = new \ReflectionMethod('Symfony\Component\DependencyInjection\Extension\Extension', 'isConfigEnabled'); - $r->setAccessible(true); - - $r->invoke($extension, $container, array('enabled' => $enabled)); + $extension = new EnableableExtension(); + $this->assertSame($enabled, $extension->isConfigEnabled(new ContainerBuilder(), array('enabled' => $enabled))); } public function getResolvedEnabledFixtures() @@ -66,18 +40,20 @@ public function getResolvedEnabledFixtures() */ public function testIsConfigEnabledOnNonEnableableConfig() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder') - ->getMock() - ; + $extension = new EnableableExtension(); - $extension = $this->getMockBuilder('Symfony\Component\DependencyInjection\Extension\Extension') - ->setMethods(array()) - ->getMockForAbstractClass() - ; + $extension->isConfigEnabled(new ContainerBuilder(), array()); + } +} - $r = new \ReflectionMethod('Symfony\Component\DependencyInjection\Extension\Extension', 'isConfigEnabled'); - $r->setAccessible(true); +class EnableableExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } - $r->invoke($extension, $container, array()); + public function isConfigEnabled(ContainerBuilder $container, array $config) + { + return parent::isConfigEnabled($container, $config); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Container/ConstructorWithMandatoryArgumentsContainer.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Container/ConstructorWithMandatoryArgumentsContainer.php new file mode 100644 index 0000000000000..ba55fb75d8e20 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Container/ConstructorWithMandatoryArgumentsContainer.php @@ -0,0 +1,10 @@ +tag('baz'); $di->load(Prototype::class.'\\', '../Prototype') ->autoconfigure() - ->exclude('../Prototype/{OtherDir}') + ->exclude('../Prototype/{OtherDir,BadClasses}') ->factory('f') ->deprecate('%service_id%') ->args(array(0)) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php new file mode 100644 index 0000000000000..01caae6489535 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_constructor_without_arguments.php @@ -0,0 +1,61 @@ +services = $this->privates = array(); + + $this->aliases = array(); + } + + public function reset() + { + $this->privates = array(); + parent::reset(); + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return array( + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php new file mode 100644 index 0000000000000..e513fd7219147 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_mandatory_constructor_arguments.php @@ -0,0 +1,59 @@ +services = $this->privates = array(); + + $this->aliases = array(); + } + + public function reset() + { + $this->privates = array(); + parent::reset(); + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return array( + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php new file mode 100644 index 0000000000000..1c20741bf7dc4 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_with_optional_constructor_arguments.php @@ -0,0 +1,61 @@ +services = $this->privates = array(); + + $this->aliases = array(); + } + + public function reset() + { + $this->privates = array(); + parent::reset(); + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return array( + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php new file mode 100644 index 0000000000000..0a4975b7da395 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/custom_container_class_without_constructor.php @@ -0,0 +1,59 @@ +services = $this->privates = array(); + + $this->aliases = array(); + } + + public function reset() + { + $this->privates = array(); + parent::reset(); + } + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function getRemovedIds() + { + return array( + 'Psr\\Container\\ContainerInterface' => true, + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml index 333e71ce57d5a..381f95dd00fa5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_prototype.xml @@ -1,6 +1,6 @@ - + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml index fb47bcb7e7a52..8c0b202aab2b7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_prototype.yml @@ -1,4 +1,4 @@ services: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\: resource: ../Prototype - exclude: '../Prototype/{OtherDir}' + exclude: '../Prototype/{OtherDir,BadClasses}' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php index 4cba7443e8ce9..8a271a818a475 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php @@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\MissingParent; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\AnotherSub\DeeperBaz; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Baz; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo; @@ -163,6 +164,26 @@ public function testNestedRegisterClasses() $this->assertFalse($alias->isPrivate()); } + public function testMissingParentClass() + { + $container = new ContainerBuilder(); + $container->setParameter('bad_classes_dir', 'BadClasses'); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $loader->registerClasses( + (new Definition())->setPublic(false), + 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\\', + 'Prototype/%bad_classes_dir%/*' + ); + + $this->assertTrue($container->has(MissingParent::class)); + + $this->assertSame( + array('While discovering services from namespace "Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\", an error was thrown when processing the class "Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\MissingParent": "Class Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\MissingClass not found".'), + $container->getDefinition(MissingParent::class)->getErrors() + ); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException * @expectedExceptionMessageRegExp /Expected to find class "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\Prototype\\Bar" in file ".+" while importing services from resource "Prototype\/Sub\/\*", but it was not found\! Check the namespace prefix used with the resource/ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php new file mode 100644 index 0000000000000..a3be4dfd3b99a --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/GlobFileLoaderTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\GlobFileLoader; +use Symfony\Component\Config\FileLocator; + +class GlobFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new GlobFileLoader(new ContainerBuilder(), new FileLocator()); + + $this->assertTrue($loader->supports('any-path', 'glob'), '->supports() returns true if the resource has the glob type'); + $this->assertFalse($loader->supports('any-path'), '->supports() returns false if the resource is not of glob type'); + } + + public function testLoadAddsTheGlobResourceToTheContainer() + { + $loader = new GlobFileLoaderWithoutImport($container = new ContainerBuilder(), new FileLocator()); + $loader->load(__DIR__.'/../Fixtures/config/*'); + + $this->assertEquals(new GlobResource(__DIR__.'/../Fixtures/config', '/*', false), $container->getResources()[1]); + } +} + +class GlobFileLoaderWithoutImport extends GlobFileLoader +{ + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) + { + } +} diff --git a/src/Symfony/Component/DomCrawler/Form.php b/src/Symfony/Component/DomCrawler/Form.php index 473e3919d6a29..6988d5096745e 100644 --- a/src/Symfony/Component/DomCrawler/Form.php +++ b/src/Symfony/Component/DomCrawler/Form.php @@ -209,7 +209,7 @@ public function getUri() parse_str($query, $currentParameters); } - $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), null, '&'); + $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), '', '&'); $pos = strpos($uri, '?'); $base = false === $pos ? $uri : substr($uri, 0, $pos); diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php index 210ef972386a2..a546516a8cfc6 100644 --- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php +++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php @@ -84,17 +84,15 @@ public function process(ContainerBuilder $container) $def = $container->getDefinition($id); // We must assume that the class value has been correctly filled, even if the service is created by a factory - $class = $container->getParameterBag()->resolveValue($def->getClass()); - $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + $class = $def->getClass(); - if (!is_subclass_of($class, $interface)) { - if (!class_exists($class, false)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); - } - - throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(EventSubscriberInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class)); } - $container->addObjectResource($class); + $class = $r->name; ExtractingEventDispatcher::$subscriber = $class; $extractingDispatcher->addSubscriber($extractingDispatcher); diff --git a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php index dbb1aa5c57b57..c1b49f75b8a47 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -27,29 +27,10 @@ class RegisterListenersPassTest extends TestCase */ public function testEventSubscriberWithoutInterface() { - // one service, not implementing any interface - $services = array( - 'my_event_subscriber' => array(0 => array()), - ); - - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $definition->expects($this->atLeastOnce()) - ->method('getClass') - ->will($this->returnValue('stdClass')); - - $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); - $builder->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - - // We don't test kernel.event_listener here - $builder->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->onConsecutiveCalls(array(), $services)); - - $builder->expects($this->atLeastOnce()) - ->method('getDefinition') - ->will($this->returnValue($definition)); + $builder = new ContainerBuilder(); + $builder->register('event_dispatcher'); + $builder->register('my_event_subscriber', 'stdClass') + ->addTag('kernel.event_subscriber'); $registerListenersPass = new RegisterListenersPass(); $registerListenersPass->process($builder); @@ -61,31 +42,25 @@ public function testValidEventSubscriber() 'my_event_subscriber' => array(0 => array()), ); - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $definition->expects($this->atLeastOnce()) - ->method('getClass') - ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')); - - $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition'))->getMock(); - $builder->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - - // We don't test kernel.event_listener here - $builder->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->onConsecutiveCalls(array(), $services)); - - $builder->expects($this->atLeastOnce()) - ->method('getDefinition') - ->will($this->returnValue($definition)); - - $builder->expects($this->atLeastOnce()) - ->method('findDefinition') - ->will($this->returnValue($definition)); + $builder = new ContainerBuilder(); + $eventDispatcherDefinition = $builder->register('event_dispatcher'); + $builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService') + ->addTag('kernel.event_subscriber'); $registerListenersPass = new RegisterListenersPass(); $registerListenersPass->process($builder); + + $expectedCalls = array( + array( + 'addListener', + array( + 'event', + array(new ServiceClosureArgument(new Reference('my_event_subscriber')), 'onEvent'), + 0, + ), + ), + ); + $this->assertEquals($expectedCalls, $eventDispatcherDefinition->getMethodCalls()); } /** diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 50c8a2b30e575..db5df5cbeb472 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -524,13 +524,18 @@ public function makePathRelative($endPath, $startPath) /** * Mirrors a directory to another. * + * Copies files and directories from the origin directory into the target directory. By default: + * + * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option) + * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option) + * * @param string $originDir The origin directory * @param string $targetDir The target directory - * @param \Traversable $iterator A Traversable instance + * @param \Traversable $iterator Iterator that filters which files and directories to copy * @param array $options An array of boolean options * Valid options are: - * - $options['override'] Whether to override an existing file on copy or not (see copy()) - * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink()) + * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) * * @throws IOException When file type is unknown diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 1dab061f879eb..e7157b1b0a599 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -313,6 +313,8 @@ public function exclude($dirs) /** * Excludes "hidden" directories and files (starting with a dot). * + * This option is enabled by default. + * * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not * * @return $this @@ -333,6 +335,8 @@ public function ignoreDotFiles($ignoreDotFiles) /** * Forces the finder to ignore version control directories. * + * This option is enabled by default. + * * @param bool $ignoreVCS Whether to exclude VCS files or not * * @return $this diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index d9df942c6af30..e536efac37ce3 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -31,7 +31,6 @@ use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; -use Symfony\Component\Form\Util\FormUtil; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -87,12 +86,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) $form = $event->getForm(); $data = $event->getData(); + // Since the type always use mapper an empty array will not be + // considered as empty in Form::submit(), we need to evaluate + // empty data here so its value is submitted to sub forms if (null === $data) { $emptyData = $form->getConfig()->getEmptyData(); - - if (false === FormUtil::isEmpty($emptyData) && array() !== $emptyData) { - $data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData; - } + $data = $emptyData instanceof \Closure ? $emptyData($form, $data) : $emptyData; } // Convert the submitted data to a string, if scalar, before diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 387325782933c..36836378ae5bc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -27,10 +27,10 @@ class FileType extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { + // Ensure that submitted data is always an uploaded file or an array of some $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) { $form = $event->getForm(); $requestHandler = $form->getConfig()->getRequestHandler(); - $data = null; if ($options['multiple']) { $data = array(); @@ -46,19 +46,16 @@ public function buildForm(FormBuilderInterface $builder, array $options) } } - // submitted data for an input file (not required) without choosing any file - if (array(null) === $data || array() === $data) { + // Since the array is never considered empty in the view data format + // on submission, we need to evaluate the configured empty data here + if (array() === $data) { $emptyData = $form->getConfig()->getEmptyData(); - - $data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData; + $data = $emptyData instanceof \Closure ? $emptyData($form, $data) : $emptyData; } $event->setData($data); } elseif (!$requestHandler->isFileUpload($event->getData())) { - $emptyData = $form->getConfig()->getEmptyData(); - - $data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData; - $event->setData($data); + $event->setData(null); } }); } diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index ae6afadaab94a..273db0dbd2877 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -113,6 +113,7 @@ public function validate($form, Constraint $constraint) ? (string) $form->getViewData() : gettype($form->getViewData()); + $this->context->setConstraint($constraint); $this->context->buildViolation($config->getOption('invalid_message')) ->setParameters(array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters'))) ->setInvalidValue($form->getViewData()) @@ -124,6 +125,7 @@ public function validate($form, Constraint $constraint) // Mark the form with an error if it contains extra fields if (!$config->getOption('allow_extra_fields') && count($form->getExtraData()) > 0) { + $this->context->setConstraint($constraint); $this->context->buildViolation($config->getOption('extra_fields_message')) ->setParameter('{{ extra_fields }}', implode('", "', array_keys($form->getExtraData()))) ->setInvalidValue($form->getExtraData()) diff --git a/src/Symfony/Component/Form/Resources/translations/validators.tl.xlf b/src/Symfony/Component/Form/Resources/translations/validators.tl.xlf new file mode 100644 index 0000000000000..02def0bf31f6f --- /dev/null +++ b/src/Symfony/Component/Form/Resources/translations/validators.tl.xlf @@ -0,0 +1,19 @@ + + + + + + This form should not contain extra fields. + Ang pormang itong ay hindi dapat magkarron ng dagdag na mga patlang. + + + The uploaded file was too large. Please try to upload a smaller file. + Ang ini-upload na file ay masyadong malaki. Pakiulit muling mag-upload ng mas maliit na file. + + + The CSRF token is invalid. Please try to resubmit the form. + Hindi balido ang CSRF token. Maagpasa muli ng isang pang porma. + + + + diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php index 2833de7d91de0..e8ede167b3802 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap4HorizontalLayoutTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Form\Tests; +use Symfony\Component\Form\FormError; + /** * Abstract class providing test cases for the Bootstrap 4 horizontal Twig form theme. * @@ -18,6 +20,33 @@ */ abstract class AbstractBootstrap4HorizontalLayoutTest extends AbstractBootstrap4LayoutTest { + public function testRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name"] + [ + ./div[ + ./ul + [./li + [./span[.="[trans]Error[/trans]"]] + [./span[.="[trans]Error![/trans]"]] + ] + [count(./li)=1] + ] + ] + /following-sibling::div[./input[@id="name"]] + ] +' + ); + } + public function testLabelOnForm() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); @@ -27,7 +56,7 @@ public function testLabelOnForm() $this->assertMatchesXpath($html, '/legend - [@class="col-form-label col-sm-2 col-form-legend required"] + [@class="col-form-label col-sm-2 col-form-label required"] [.="[trans]Name[/trans]"] ' ); @@ -118,7 +147,7 @@ public function testLegendOnExpandedType() $this->assertMatchesXpath($html, '/legend - [@class="col-sm-2 col-form-legend required"] + [@class="col-sm-2 col-form-label required"] [.="[trans]Custom label[/trans]"] ' ); diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php index 88ebd261b2a44..ec97577818d4d 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap4LayoutTest.php @@ -20,6 +20,33 @@ */ abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest { + public function testRow() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $form->addError(new FormError('[trans]Error![/trans]')); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, + '/div + [ + ./label[@for="name"] + [ + ./div[ + ./ul + [./li + [./span[.="[trans]Error[/trans]"]] + [./span[.="[trans]Error![/trans]"]] + ] + [count(./li)=1] + ] + ] + /following-sibling::input[@id="name"] + ] +' + ); + } + public function testLabelOnForm() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); @@ -29,7 +56,7 @@ public function testLabelOnForm() $this->assertMatchesXpath($html, '/legend - [@class="col-form-legend required"] + [@class="col-form-label required"] [.="[trans]Name[/trans]"] ' ); @@ -120,7 +147,7 @@ public function testLegendOnExpandedType() $this->assertMatchesXpath($html, '/legend - [@class="col-form-legend required"] + [@class="col-form-label required"] [.="[trans]Custom label[/trans]"] ' ); @@ -142,9 +169,12 @@ public function testErrors() [@class="list-unstyled mb-0"] [ ./li - [.="[trans]Error 1[/trans]"] + [./span[.="[trans]Error[/trans]"]] + [./span[.="[trans]Error 1[/trans]"]] + /following-sibling::li - [.="[trans]Error 2[/trans]"] + [./span[.="[trans]Error[/trans]"]] + [./span[.="[trans]Error 2[/trans]"]] ] [count(./li)=2] ] @@ -160,12 +190,10 @@ public function testCheckedCheckbox() '/div [@class="form-check"] [ - ./label - [.=" [trans]Name[/trans]"] + ./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class form-check-input"][@checked="checked"][@value="1"] + /following-sibling::label + [.="[trans]Name[/trans]"] [@class="form-check-label required"] - [ - ./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class form-check-input"][@checked="checked"][@value="1"] - ] ] ' ); @@ -210,20 +238,16 @@ public function testSingleExpandedChoiceAttributesWithMainAttributes() ./div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -239,11 +263,9 @@ public function testUncheckedCheckbox() '/div [@class="form-check"] [ - ./label - [.=" [trans]Name[/trans]"] - [ - ./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class form-check-input"][not(@checked)] - ] + ./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class form-check-input"][not(@checked)] + /following-sibling::label + [.="[trans]Name[/trans]"] ] ' ); @@ -259,11 +281,9 @@ public function testCheckboxWithValue() '/div [@class="form-check"] [ - ./label - [.=" [trans]Name[/trans]"] - [ - ./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class form-check-input"][@value="foo&bar"] - ] + ./input[@type="checkbox"][@name="name"][@id="my&id"][@class="my&class form-check-input"][@value="foo&bar"] + /following-sibling::label + [.="[trans]Name[/trans]"] ] ' ); @@ -283,20 +303,16 @@ public function testSingleChoiceExpanded() ./div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -319,18 +335,14 @@ public function testSingleChoiceExpandedWithLabelsAsFalse() ./div [@class="form-check"] [ - ./label - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label ] /following-sibling::div [@class="form-check"] [ - ./label - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -359,28 +371,22 @@ public function testSingleChoiceExpandedWithLabelsSetByCallable() ./div [@class="form-check"] [ - ./label - [.=" [trans]label.&a[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label + [.="[trans]label.&a[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]label.&c[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_2"][@value="&c"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_2"][@value="&c"][not(@checked)] + /following-sibling::label + [.="[trans]label.&c[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -405,18 +411,14 @@ public function testSingleChoiceExpandedWithLabelsSetFalseByCallable() ./div [@class="form-check"] [ - ./label - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label ] /following-sibling::div [@class="form-check"] [ - ./label - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -439,20 +441,16 @@ public function testSingleChoiceExpandedWithoutTranslation() ./div [@class="form-check"] [ - ./label - [.=" Choice&A"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label + [.="Choice&A"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" Choice&B"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label + [.="Choice&B"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -475,20 +473,16 @@ public function testSingleChoiceExpandedAttributes() ./div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)][@class="foo&bar form-check-input"] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)][@class="foo&bar form-check-input"] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -512,29 +506,23 @@ public function testSingleChoiceExpandedWithPlaceholder() ./div [@class="form-check"] [ - ./label - [.=" [trans]Test&Me[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] + /following-sibling::label + [.="[trans]Test&Me[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -559,29 +547,23 @@ public function testSingleChoiceExpandedWithPlaceholderWithoutTranslation() ./div [@class="form-check"] [ - ./label - [.=" Placeholder&Not&Translated"] - [ - ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_placeholder"][not(@checked)] + /following-sibling::label + [.="Placeholder&Not&Translated"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" Choice&A"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label + [.="Choice&A"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" Choice&B"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label + [.="Choice&B"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -603,20 +585,16 @@ public function testSingleChoiceExpandedWithBooleanValue() ./div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_0"][@checked] - ] + ./input[@type="radio"][@name="name"][@id="name_0"][@checked] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] - ] + ./input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -639,29 +617,23 @@ public function testMultipleChoiceExpanded() ./div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&C[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] + /following-sibling::label + [.="[trans]Choice&C[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -684,18 +656,14 @@ public function testMultipleChoiceExpandedWithLabelsAsFalse() ./div [@class="form-check"] [ - ./label - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label ] /following-sibling::div [@class="form-check"] [ - ./label - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -724,28 +692,22 @@ public function testMultipleChoiceExpandedWithLabelsSetByCallable() ./div [@class="form-check"] [ - ./label - [.=" [trans]label.&a[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label + [.="[trans]label.&a[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]label.&c[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@value="&c"][not(@checked)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@value="&c"][not(@checked)] + /following-sibling::label + [.="[trans]label.&c[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -770,18 +732,14 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() ./div [@class="form-check"] [ - ./label - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label ] /following-sibling::div [@class="form-check"] [ - ./label - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -805,29 +763,23 @@ public function testMultipleChoiceExpandedWithoutTranslation() ./div [@class="form-check"] [ - ./label - [.=" Choice&A"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + /following-sibling::label + [.="Choice&A"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" Choice&B"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] + /following-sibling::label + [.="Choice&B"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" Choice&C"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] + /following-sibling::label + [.="Choice&C"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -851,29 +803,23 @@ public function testMultipleChoiceExpandedAttributes() ./div [@class="form-check"] [ - ./label - [.=" [trans]Choice&A[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] + /following-sibling::label + [.="[trans]Choice&A[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&B[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)][@class="foo&bar form-check-input"] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)][@class="foo&bar form-check-input"] + /following-sibling::label + [.="[trans]Choice&B[/trans]"] ] /following-sibling::div [@class="form-check"] [ - ./label - [.=" [trans]Choice&C[/trans]"] - [ - ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] - ] + ./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] + /following-sibling::label + [.="[trans]Choice&C[/trans]"] ] /following-sibling::input[@type="hidden"][@id="name__token"] ] @@ -889,17 +835,15 @@ public function testCheckedRadio() '/div [@class="form-check"] [ - ./label + ./input + [@id="my&id"] + [@type="radio"] + [@name="name"] + [@class="my&class form-check-input"] + [@checked="checked"] + [@value="1"] + /following-sibling::label [@class="form-check-label required"] - [ - ./input - [@id="my&id"] - [@type="radio"] - [@name="name"] - [@class="my&class form-check-input"] - [@checked="checked"] - [@value="1"] - ] ] ' ); @@ -913,16 +857,14 @@ public function testUncheckedRadio() '/div [@class="form-check"] [ - ./label + ./input + [@id="my&id"] + [@type="radio"] + [@name="name"] + [@class="my&class form-check-input"] + [not(@checked)] + /following-sibling::label [@class="form-check-label required"] - [ - ./input - [@id="my&id"] - [@type="radio"] - [@name="name"] - [@class="my&class form-check-input"] - [not(@checked)] - ] ] ' ); @@ -938,16 +880,15 @@ public function testRadioWithValue() '/div [@class="form-check"] [ - ./label + ./input + [@id="my&id"] + [@type="radio"] + [@name="name"] + [@class="my&class form-check-input"] + [@value="foo&bar"] + /following-sibling::label [@class="form-check-label required"] - [ - ./input - [@id="my&id"] - [@type="radio"] - [@name="name"] - [@class="my&class form-check-input"] - [@value="foo&bar"] - ] + [@for="my&id"] ] ' ); @@ -972,6 +913,60 @@ public function testFile() $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class form-control-file')), '/input [@type="file"] +' + ); + } + + public function testMoney() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType', 1234.56, array( + 'currency' => 'EUR', + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), + '/div + [@class="input-group"] + [ + ./div + [@class="input-group-prepend"] + [ + ./span + [@class="input-group-text"] + [contains(.., "€")] + ] + /following-sibling::input + [@id="my&id"] + [@type="text"] + [@name="name"] + [@class="my&class form-control"] + [@value="1234.56"] + ] +' + ); + } + + public function testPercent() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\PercentType', 0.1); + + $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), + '/div + [@class="input-group"] + [ + ./input + [@id="my&id"] + [@type="text"] + [@name="name"] + [@class="my&class form-control"] + [@value="10"] + /following-sibling::div + [@class="input-group-append"] + [ + ./span + [@class="input-group-text"] + [contains(.., "%")] + ] + ] ' ); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 192d38f1a9225..5ae3c53689ea6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -598,6 +598,20 @@ public function testSubmitSingleChoiceWithEmptyData() $this->assertSame('test', $form->getData()); } + public function testSubmitSingleChoiceWithEmptyDataAndInitialData() + { + $form = $this->factory->create(static::TESTED_TYPE, 'initial', array( + 'multiple' => false, + 'expanded' => false, + 'choices' => array('initial', 'test'), + 'empty_data' => 'test', + )); + + $form->submit(null); + + $this->assertSame('test', $form->getData()); + } + public function testSubmitMultipleChoiceWithEmptyData() { $form = $this->factory->create(static::TESTED_TYPE, null, array( @@ -612,6 +626,34 @@ public function testSubmitMultipleChoiceWithEmptyData() $this->assertSame(array('test'), $form->getData()); } + public function testSubmitMultipleChoiceWithEmptyDataAndInitialEmptyArray() + { + $form = $this->factory->create(static::TESTED_TYPE, array(), array( + 'multiple' => true, + 'expanded' => false, + 'choices' => array('test'), + 'empty_data' => array('test'), + )); + + $form->submit(null); + + $this->assertSame(array('test'), $form->getData()); + } + + public function testSubmitMultipleChoiceWithEmptyDataAndInitialData() + { + $form = $this->factory->create(static::TESTED_TYPE, array('initial'), array( + 'multiple' => true, + 'expanded' => false, + 'choices' => array('initial', 'test'), + 'empty_data' => array('test'), + )); + + $form->submit(null); + + $this->assertSame(array('test'), $form->getData()); + } + public function testSubmitSingleChoiceExpandedWithEmptyData() { $form = $this->factory->create(static::TESTED_TYPE, null, array( @@ -626,6 +668,20 @@ public function testSubmitSingleChoiceExpandedWithEmptyData() $this->assertSame('test', $form->getData()); } + public function testSubmitSingleChoiceExpandedWithEmptyDataAndInitialData() + { + $form = $this->factory->create(static::TESTED_TYPE, 'initial', array( + 'multiple' => false, + 'expanded' => true, + 'choices' => array('initial', 'test'), + 'empty_data' => 'test', + )); + + $form->submit(null); + + $this->assertSame('test', $form->getData()); + } + public function testSubmitMultipleChoiceExpandedWithEmptyData() { $form = $this->factory->create(static::TESTED_TYPE, null, array( @@ -640,6 +696,49 @@ public function testSubmitMultipleChoiceExpandedWithEmptyData() $this->assertSame(array('test'), $form->getData()); } + public function testSubmitMultipleChoiceExpandedWithEmptyDataAndInitialEmptyArray() + { + $form = $this->factory->create(static::TESTED_TYPE, array(), array( + 'multiple' => true, + 'expanded' => true, + 'choices' => array('test'), + 'empty_data' => array('test'), + )); + + $form->submit(null); + + $this->assertSame(array('test'), $form->getData()); + } + + public function testSubmitMultipleChoiceExpandedWithEmptyDataAndInitialData() + { + $form = $this->factory->create(static::TESTED_TYPE, array('init'), array( + 'multiple' => true, + 'expanded' => true, + 'choices' => array('init', 'test'), + 'empty_data' => array('test'), + )); + + $form->submit(null); + + $this->assertSame(array('test'), $form->getData()); + } + + /** + * @group legacy + */ + public function testLegacyNullChoices() + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'multiple' => false, + 'expanded' => false, + 'choices' => null, + )); + $this->assertNull($form->getConfig()->getOption('choices')); + $this->assertFalse($form->getConfig()->getOption('multiple')); + $this->assertFalse($form->getConfig()->getOption('expanded')); + } + public function testSubmitMultipleNonExpanded() { $form = $this->factory->create(static::TESTED_TYPE, null, array( diff --git a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php index 7e591d414e456..bd3f25bffb2a6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/DataCollector/FormDataCollectorTest.php @@ -690,9 +690,9 @@ public function testCollectSubmittedDataExpandedFormsErrors() $this->assertTrue($formData['has_children_error']); $this->assertTrue($child1Data['has_children_error']); - $this->assertFalse(isset($child11Data['has_children_error']), 'The leaf data does not contains "has_children_error" property.'); + $this->assertArrayNotHasKey('has_children_error', $child11Data, 'The leaf data does not contains "has_children_error" property.'); $this->assertFalse($child2Data['has_children_error']); - $this->assertFalse(isset($child21Data['has_children_error']), 'The leaf data does not contains "has_children_error" property.'); + $this->assertArrayNotHasKey('has_children_error', $child21Data, 'The leaf data does not contains "has_children_error" property.'); } public function testReset() diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index 55b4ac278cd3a..6301d2bf8af46 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -51,6 +51,8 @@ protected function setUp() $this->serverParams = $this->getMockBuilder('Symfony\Component\Form\Extension\Validator\Util\ServerParams')->setMethods(array('getNormalizedIniPostMaxSize', 'getContentLength'))->getMock(); parent::setUp(); + + $this->constraint = new Form(); } protected function createValidator() diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index cf2d7ecbd306e..4905c73c68322 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -41,7 +41,7 @@ "symfony/doctrine-bridge": "<3.4", "symfony/framework-bundle": "<3.4", "symfony/http-kernel": "<3.4", - "symfony/twig-bridge": "<3.4" + "symfony/twig-bridge": "<3.4.5|<4.0.5,>=4.0" }, "suggest": { "symfony/validator": "For form validation.", diff --git a/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php index 896c135a53424..77bf51b1e5a03 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -599,6 +599,7 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface 'application/x-xliff+xml' => 'xlf', 'application/x-xpinstall' => 'xpi', 'application/x-xz' => 'xz', + 'application/x-zip-compressed' => 'zip', 'application/x-zmachine' => 'z1', 'application/xaml+xml' => 'xaml', 'application/xcap-diff+xml' => 'xdf', diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 93d20d1cae9b4..72b07fc94c430 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -116,7 +116,7 @@ class Request public $headers; /** - * @var string|resource + * @var string|resource|false|null */ protected $content; @@ -222,13 +222,13 @@ class Request ); /** - * @param array $query The GET parameters - * @param array $request The POST parameters - * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) - * @param array $cookies The COOKIE parameters - * @param array $files The FILES parameters - * @param array $server The SERVER parameters - * @param string|resource $content The raw body data + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data */ public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { @@ -240,13 +240,13 @@ public function __construct(array $query = array(), array $request = array(), ar * * This method also re-initializes all properties. * - * @param array $query The GET parameters - * @param array $request The POST parameters - * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) - * @param array $cookies The COOKIE parameters - * @param array $files The FILES parameters - * @param array $server The SERVER parameters - * @param string|resource $content The raw body data + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data */ public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { @@ -296,13 +296,13 @@ public static function createFromGlobals() * The information contained in the URI always take precedence * over the other information (server and parameters). * - * @param string $uri The URI - * @param string $method The HTTP method - * @param array $parameters The query (GET) or request (POST) parameters - * @param array $cookies The request cookies ($_COOKIE) - * @param array $files The request files ($_FILES) - * @param array $server The server parameters ($_SERVER) - * @param string|resource $content The raw body data + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data * * @return static */ @@ -524,7 +524,7 @@ public function __toString() */ public function overrideGlobals() { - $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&'))); + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); $_GET = $this->query->all(); $_POST = $this->request->all(); diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 9449dc10af6f5..d16530880f1fb 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -412,7 +412,7 @@ public function getContent() * * @return $this * - * @final since version 3.2 + * @final */ public function setProtocolVersion(string $version) { @@ -424,7 +424,7 @@ public function setProtocolVersion(string $version) /** * Gets the HTTP protocol version. * - * @final since version 3.2 + * @final */ public function getProtocolVersion(): string { @@ -441,7 +441,7 @@ public function getProtocolVersion(): string * * @throws \InvalidArgumentException When the HTTP status code is not valid * - * @final since version 3.2 + * @final */ public function setStatusCode(int $code, $text = null) { @@ -470,7 +470,7 @@ public function setStatusCode(int $code, $text = null) /** * Retrieves the status code for the current web response. * - * @final since version 3.2 + * @final */ public function getStatusCode(): int { @@ -482,7 +482,7 @@ public function getStatusCode(): int * * @return $this * - * @final since version 3.2 + * @final */ public function setCharset(string $charset) { @@ -494,7 +494,7 @@ public function setCharset(string $charset) /** * Retrieves the response charset. * - * @final since version 3.2 + * @final */ public function getCharset(): ?string { @@ -510,7 +510,7 @@ public function getCharset(): ?string * Responses with neither a freshness lifetime (Expires, max-age) nor cache * validator (Last-Modified, ETag) are considered uncacheable. * - * @final since version 3.3 + * @final */ public function isCacheable(): bool { @@ -532,7 +532,7 @@ public function isCacheable(): bool * origin. A response is considered fresh when it includes a Cache-Control/max-age * indicator or Expires header and the calculated age is less than the freshness lifetime. * - * @final since version 3.3 + * @final */ public function isFresh(): bool { @@ -543,7 +543,7 @@ public function isFresh(): bool * Returns true if the response includes headers that can be used to validate * the response with the origin server using a conditional GET request. * - * @final since version 3.3 + * @final */ public function isValidateable(): bool { @@ -557,7 +557,7 @@ public function isValidateable(): bool * * @return $this * - * @final since version 3.2 + * @final */ public function setPrivate() { @@ -574,7 +574,7 @@ public function setPrivate() * * @return $this * - * @final since version 3.2 + * @final */ public function setPublic() { @@ -620,7 +620,7 @@ public function isImmutable(): bool * When present, the TTL of the response should not be overridden to be * greater than the value provided by the origin. * - * @final since version 3.3 + * @final */ public function mustRevalidate(): bool { @@ -632,7 +632,7 @@ public function mustRevalidate(): bool * * @throws \RuntimeException When the header is not parseable * - * @final since version 3.2 + * @final */ public function getDate(): ?\DateTimeInterface { @@ -644,7 +644,7 @@ public function getDate(): ?\DateTimeInterface * * @return $this * - * @final since version 3.2 + * @final */ public function setDate(\DateTimeInterface $date) { @@ -661,7 +661,7 @@ public function setDate(\DateTimeInterface $date) /** * Returns the age of the response in seconds. * - * @final since version 3.2 + * @final */ public function getAge(): int { @@ -689,7 +689,7 @@ public function expire() /** * Returns the value of the Expires header as a DateTime instance. * - * @final since version 3.2 + * @final */ public function getExpires(): ?\DateTimeInterface { @@ -708,7 +708,7 @@ public function getExpires(): ?\DateTimeInterface * * @return $this * - * @final since version 3.2 + * @final */ public function setExpires(\DateTimeInterface $date = null) { @@ -735,7 +735,7 @@ public function setExpires(\DateTimeInterface $date = null) * First, it checks for a s-maxage directive, then a max-age directive, and then it falls * back on an expires header. It returns null when no maximum age can be established. * - * @final since version 3.2 + * @final */ public function getMaxAge(): ?int { @@ -761,7 +761,7 @@ public function getMaxAge(): ?int * * @return $this * - * @final since version 3.2 + * @final */ public function setMaxAge(int $value) { @@ -777,7 +777,7 @@ public function setMaxAge(int $value) * * @return $this * - * @final since version 3.2 + * @final */ public function setSharedMaxAge(int $value) { @@ -795,7 +795,7 @@ public function setSharedMaxAge(int $value) * When the responses TTL is <= 0, the response may not be served from cache without first * revalidating with the origin. * - * @final since version 3.2 + * @final */ public function getTtl(): ?int { @@ -811,7 +811,7 @@ public function getTtl(): ?int * * @return $this * - * @final since version 3.2 + * @final */ public function setTtl(int $seconds) { @@ -827,7 +827,7 @@ public function setTtl(int $seconds) * * @return $this * - * @final since version 3.2 + * @final */ public function setClientTtl(int $seconds) { @@ -841,7 +841,7 @@ public function setClientTtl(int $seconds) * * @throws \RuntimeException When the HTTP header is not parseable * - * @final since version 3.2 + * @final */ public function getLastModified(): ?\DateTimeInterface { @@ -855,7 +855,7 @@ public function getLastModified(): ?\DateTimeInterface * * @return $this * - * @final since version 3.2 + * @final */ public function setLastModified(\DateTimeInterface $date = null) { @@ -878,7 +878,7 @@ public function setLastModified(\DateTimeInterface $date = null) /** * Returns the literal value of the ETag HTTP header. * - * @final since version 3.2 + * @final */ public function getEtag(): ?string { @@ -893,7 +893,7 @@ public function getEtag(): ?string * * @return $this * - * @final since version 3.2 + * @final */ public function setEtag(string $etag = null, bool $weak = false) { @@ -919,7 +919,7 @@ public function setEtag(string $etag = null, bool $weak = false) * * @throws \InvalidArgumentException * - * @final since version 3.3 + * @final */ public function setCache(array $options) { @@ -976,7 +976,7 @@ public function setCache(array $options) * * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 * - * @final since version 3.3 + * @final */ public function setNotModified() { @@ -994,7 +994,7 @@ public function setNotModified() /** * Returns true if the response includes a Vary header. * - * @final since version 3.2 + * @final */ public function hasVary(): bool { @@ -1004,7 +1004,7 @@ public function hasVary(): bool /** * Returns an array of header names given in the Vary header. * - * @final since version 3.2 + * @final */ public function getVary(): array { @@ -1028,7 +1028,7 @@ public function getVary(): array * * @return $this * - * @final since version 3.2 + * @final */ public function setVary($headers, bool $replace = true) { @@ -1046,7 +1046,7 @@ public function setVary($headers, bool $replace = true) * * @return bool true if the Response validators match the Request, false otherwise * - * @final since version 3.3 + * @final */ public function isNotModified(Request $request): bool { @@ -1078,7 +1078,7 @@ public function isNotModified(Request $request): bool * * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html * - * @final since version 3.2 + * @final */ public function isInvalid(): bool { @@ -1088,7 +1088,7 @@ public function isInvalid(): bool /** * Is response informative? * - * @final since version 3.3 + * @final */ public function isInformational(): bool { @@ -1098,7 +1098,7 @@ public function isInformational(): bool /** * Is response successful? * - * @final since version 3.2 + * @final */ public function isSuccessful(): bool { @@ -1108,7 +1108,7 @@ public function isSuccessful(): bool /** * Is the response a redirect? * - * @final since version 3.2 + * @final */ public function isRedirection(): bool { @@ -1118,7 +1118,7 @@ public function isRedirection(): bool /** * Is there a client error? * - * @final since version 3.2 + * @final */ public function isClientError(): bool { @@ -1128,7 +1128,7 @@ public function isClientError(): bool /** * Was there a server side error? * - * @final since version 3.3 + * @final */ public function isServerError(): bool { @@ -1138,7 +1138,7 @@ public function isServerError(): bool /** * Is the response OK? * - * @final since version 3.2 + * @final */ public function isOk(): bool { @@ -1148,7 +1148,7 @@ public function isOk(): bool /** * Is the response forbidden? * - * @final since version 3.2 + * @final */ public function isForbidden(): bool { @@ -1158,7 +1158,7 @@ public function isForbidden(): bool /** * Is the response a not found error? * - * @final since version 3.2 + * @final */ public function isNotFound(): bool { @@ -1168,7 +1168,7 @@ public function isNotFound(): bool /** * Is the response a redirect of some form? * - * @final since version 3.2 + * @final */ public function isRedirect(string $location = null): bool { @@ -1178,7 +1178,7 @@ public function isRedirect(string $location = null): bool /** * Is the response empty? * - * @final since version 3.2 + * @final */ public function isEmpty(): bool { @@ -1190,7 +1190,7 @@ public function isEmpty(): bool * * Resulting level can be greater than target level if a non-removable buffer has been encountered. * - * @final since version 3.3 + * @final */ public static function closeOutputBuffers(int $targetLevel, bool $flush) { @@ -1212,7 +1212,7 @@ public static function closeOutputBuffers(int $targetLevel, bool $flush) * * @see http://support.microsoft.com/kb/323308 * - * @final since version 3.3 + * @final */ protected function ensureIEOverSSLCompatibility(Request $request) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 6748079686221..5ae3d52cc30b1 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -164,7 +164,7 @@ class PdoSessionHandler extends AbstractSessionHandler * * db_connection_options: An array of driver-specific connection options [default: array()] * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] * - * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null * @param array $options An associative array of options * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION @@ -178,6 +178,8 @@ public function __construct($pdoOrDsn = null, array $options = array()) $this->pdo = $pdoOrDsn; $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } elseif (is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) { + $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); } else { $this->dsn = $pdoOrDsn; } @@ -431,6 +433,102 @@ private function connect($dsn) $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); } + /** + * Builds a PDO DSN from a URL-like connection string. + * + * @param string $dsnOrUrl + * + * @return string + * + * @todo implement missing support for oci DSN (which look totally different from other PDO ones) + */ + private function buildDsnFromUrl($dsnOrUrl) + { + // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid + $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); + + $params = parse_url($url); + + if (false === $params) { + return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already. + } + + $params = array_map('rawurldecode', $params); + + // Override the default username and password. Values passed through options will still win over these in the constructor. + if (isset($params['user'])) { + $this->username = $params['user']; + } + + if (isset($params['pass'])) { + $this->password = $params['pass']; + } + + if (!isset($params['scheme'])) { + throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler'); + } + + $driverAliasMap = array( + 'mssql' => 'sqlsrv', + 'mysql2' => 'mysql', // Amazon RDS, for some weird reason + 'postgres' => 'pgsql', + 'postgresql' => 'pgsql', + 'sqlite3' => 'sqlite', + ); + + $driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme']; + + // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. + if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) { + $driver = substr($driver, 4); + } + + switch ($driver) { + case 'mysql': + case 'pgsql': + $dsn = $driver.':'; + + if (isset($params['host']) && '' !== $params['host']) { + $dsn .= 'host='.$params['host'].';'; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= 'port='.$params['port'].';'; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + + case 'sqlite': + return 'sqlite:'.substr($params['path'], 1); + + case 'sqlsrv': + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && '' !== $params['port']) { + $dsn .= ','.$params['port']; + } + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= ';Database='.$dbName; + } + + return $dsn; + + default: + throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme'])); + } + } + /** * Helper method to begin a transaction. * diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 53b7cedf91422..ff33797ef591d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -2064,11 +2064,11 @@ public function testMethodSafeChecksCacheable() /** * @dataProvider methodCacheableProvider */ - public function testMethodCacheable($method, $chacheable) + public function testMethodCacheable($method, $cacheable) { $request = new Request(); $request->setMethod($method); - $this->assertEquals($chacheable, $request->isMethodCacheable()); + $this->assertEquals($cacheable, $request->isMethodCacheable()); } public function methodCacheableProvider() @@ -2177,7 +2177,7 @@ class RequestContentProxy extends Request { public function getContent($asResource = false) { - return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent')); + return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent'), '', '&'); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index 75a65597a5264..d9d0efe941343 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -317,6 +317,41 @@ public function testGetConnectionConnectsIfNeeded() $this->assertInstanceOf('\PDO', $method->invoke($storage)); } + /** + * @dataProvider provideUrlDsnPairs + */ + public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null) + { + $storage = new PdoSessionHandler($url); + + $this->assertAttributeEquals($expectedDsn, 'dsn', $storage); + + if (null !== $expectedUser) { + $this->assertAttributeEquals($expectedUser, 'username', $storage); + } + + if (null !== $expectedPassword) { + $this->assertAttributeEquals($expectedPassword, 'password', $storage); + } + } + + public function provideUrlDsnPairs() + { + yield array('mysql://localhost/test', 'mysql:host=localhost;dbname=test;'); + yield array('mysql://localhost:56/test', 'mysql:host=localhost;port=56;dbname=test;'); + yield array('mysql2://root:pwd@localhost/test', 'mysql:host=localhost;dbname=test;', 'root', 'pwd'); + yield array('postgres://localhost/test', 'pgsql:host=localhost;dbname=test;'); + yield array('postgresql://localhost:5634/test', 'pgsql:host=localhost;port=5634;dbname=test;'); + yield array('postgres://root:pwd@localhost/test', 'pgsql:host=localhost;dbname=test;', 'root', 'pwd'); + yield 'sqlite relative path' => array('sqlite://localhost/tmp/test', 'sqlite:tmp/test'); + yield 'sqlite absolute path' => array('sqlite://localhost//tmp/test', 'sqlite:/tmp/test'); + yield 'sqlite relative path without host' => array('sqlite:///tmp/test', 'sqlite:tmp/test'); + yield 'sqlite absolute path without host' => array('sqlite3:////tmp/test', 'sqlite:/tmp/test'); + yield array('sqlite://localhost/:memory:', 'sqlite::memory:'); + yield array('mssql://localhost/test', 'sqlsrv:server=localhost;Database=test'); + yield array('mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test'); + } + private function createStream($content) { $stream = tmpfile(); diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php b/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php index 6aabdc0917efa..a646119d9904d 100644 --- a/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php +++ b/src/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php @@ -16,7 +16,7 @@ * * @author Dustin Dobervich * - * @final since version 3.4 + * @final */ class ChainCacheClearer implements CacheClearerInterface { diff --git a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php index b188fc5860b2b..66dbe6c4eb610 100644 --- a/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php +++ b/src/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class CacheWarmerAggregate implements CacheWarmerInterface { diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index 9cd910ad18cfe..8cb19c479041d 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpFoundation\Request; /** * Creates the service-locators required by ServiceValueResolver. @@ -148,6 +149,10 @@ public function process(ContainerBuilder $container) continue; } + if (Request::class === $type) { + continue; + } + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php index 5f0ea5c0a9c08..0a153dd943297 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php @@ -29,6 +29,8 @@ */ abstract class AbstractTestSessionListener implements EventSubscriberInterface { + private $sessionId; + public function onKernelRequest(GetResponseEvent $event) { if (!$event->isMasterRequest()) { @@ -44,7 +46,8 @@ public function onKernelRequest(GetResponseEvent $event) $cookies = $event->getRequest()->cookies; if ($cookies->has($session->getName())) { - $session->setId($cookies->get($session->getName())); + $this->sessionId = $cookies->get($session->getName()); + $session->setId($this->sessionId); } } @@ -66,9 +69,10 @@ public function onKernelResponse(FilterResponseEvent $event) $session->save(); } - if ($session instanceof Session ? !$session->isEmpty() : $wasStarted) { + if ($session instanceof Session ? !$session->isEmpty() || $session->getId() !== $this->sessionId : $wasStarted) { $params = session_get_cookie_params(); $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); + $this->sessionId = $session->getId(); } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/SessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/SessionListener.php index 39ebfd922fac6..97eca1c0b10cf 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/SessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/SessionListener.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier * - * @final since version 3.3 + * @final */ class SessionListener extends AbstractSessionListener { diff --git a/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php index 36abb422f4f6d..f859d09769671 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/TestSessionListener.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier * - * @final since version 3.3 + * @final */ class TestSessionListener extends AbstractTestSessionListener { diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 14d4d269e27dd..f3aa39982a8c9 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\GlobFileLoader; use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\Filesystem\Filesystem; @@ -31,7 +32,6 @@ use Symfony\Component\HttpKernel\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; -use Symfony\Component\Config\Loader\GlobFileLoader; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\ConfigCache; @@ -63,11 +63,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '4.0.4'; - const VERSION_ID = 40004; + const VERSION = '4.0.5'; + const VERSION_ID = 40005; const MAJOR_VERSION = 4; const MINOR_VERSION = 0; - const RELEASE_VERSION = 4; + const RELEASE_VERSION = 5; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '07/2018'; @@ -629,7 +629,6 @@ protected function prepareContainer(ContainerBuilder $container) foreach ($this->bundles as $bundle) { if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); - $extensions[] = $extension->getAlias(); } if ($this->debug) { @@ -643,6 +642,10 @@ protected function prepareContainer(ContainerBuilder $container) $this->build($container); + foreach ($container->getExtensions() as $extension) { + $extensions[] = $extension->getAlias(); + } + // ensure these extensions are implicitly loaded $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); } @@ -681,7 +684,7 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container $dumper = new PhpDumper($container); if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { - $dumper->setProxyDumper(new ProxyDumper(substr(hash('sha256', $cache->getPath()), 0, 7))); + $dumper->setProxyDumper(new ProxyDumper()); } $content = $dumper->dump(array( @@ -690,6 +693,7 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, + 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), )); $rootCode = array_pop($content); @@ -718,7 +722,7 @@ protected function getContainerLoader(ContainerInterface $container) new YamlFileLoader($container, $locator), new IniFileLoader($container, $locator), new PhpFileLoader($container, $locator), - new GlobFileLoader($locator), + new GlobFileLoader($container, $locator), new DirectoryLoader($container, $locator), new ClosureLoader($container), )); diff --git a/src/Symfony/Component/HttpKernel/Tests/ClientTest.php b/src/Symfony/Component/HttpKernel/Tests/ClientTest.php index 1ac72c73595c2..051d5d47c0265 100644 --- a/src/Symfony/Component/HttpKernel/Tests/ClientTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/ClientTest.php @@ -97,6 +97,7 @@ public function testFilterResponseSupportsStreamedResponses() public function testUploadedFile() { $source = tempnam(sys_get_temp_dir(), 'source'); + file_put_contents($source, '1'); $target = sys_get_temp_dir().'/sf.moved.file'; @unlink($target); @@ -104,8 +105,8 @@ public function testUploadedFile() $client = new Client($kernel); $files = array( - array('tmp_name' => $source, 'name' => 'original', 'type' => 'mime/original', 'size' => 123, 'error' => UPLOAD_ERR_OK), - new UploadedFile($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true), + array('tmp_name' => $source, 'name' => 'original', 'type' => 'mime/original', 'size' => 1, 'error' => UPLOAD_ERR_OK), + new UploadedFile($source, 'original', 'mime/original', 1, UPLOAD_ERR_OK, true), ); $file = null; @@ -120,7 +121,7 @@ public function testUploadedFile() $this->assertEquals('original', $file->getClientOriginalName()); $this->assertEquals('mime/original', $file->getClientMimeType()); - $this->assertEquals('123', $file->getClientSize()); + $this->assertSame(1, $file->getClientSize()); $this->assertTrue($file->isValid()); } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php index d28c6eca57d92..e8957bb3ccfac 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; @@ -22,73 +24,37 @@ class FragmentRendererPassTest extends TestCase { /** * Tests that content rendering not implementing FragmentRendererInterface - * trigger an exception. + * triggers an exception. * * @expectedException \InvalidArgumentException */ public function testContentRendererWithoutInterface() { - // one service, not implementing any interface - $services = array( - 'my_content_renderer' => array(array('alias' => 'foo')), - ); - - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - - $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); - $builder->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - - // We don't test kernel.fragment_renderer here - $builder->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->returnValue($services)); - - $builder->expects($this->atLeastOnce()) - ->method('getDefinition') - ->will($this->returnValue($definition)); + $builder = new ContainerBuilder(); + $fragmentHandlerDefinition = $builder->register('fragment.handler'); + $builder->register('my_content_renderer', 'Symfony\Component\DependencyInjection\Definition') + ->addTag('kernel.fragment_renderer', array('alias' => 'foo')); $pass = new FragmentRendererPass(); $pass->process($builder); + + $this->assertEquals(array(array('addRendererService', array('foo', 'my_content_renderer'))), $fragmentHandlerDefinition->getMethodCalls()); } public function testValidContentRenderer() { - $services = array( - 'my_content_renderer' => array(array('alias' => 'foo')), - ); - - $renderer = new Definition('', array(null)); - - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $definition->expects($this->atLeastOnce()) - ->method('getClass') - ->will($this->returnValue('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService')); - - $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'getReflectionClass'))->getMock(); - $builder->expects($this->any()) - ->method('hasDefinition') - ->will($this->returnValue(true)); - - // We don't test kernel.fragment_renderer here - $builder->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->returnValue($services)); - - $builder->expects($this->atLeastOnce()) - ->method('getDefinition') - ->will($this->onConsecutiveCalls($renderer, $definition)); - - $builder->expects($this->atLeastOnce()) - ->method('getReflectionClass') - ->with('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService') - ->will($this->returnValue(new \ReflectionClass('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService'))); + $builder = new ContainerBuilder(); + $fragmentHandlerDefinition = $builder->register('fragment.handler') + ->addArgument(null); + $builder->register('my_content_renderer', 'Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService') + ->addTag('kernel.fragment_renderer', array('alias' => 'foo')); $pass = new FragmentRendererPass(); $pass->process($builder); - $this->assertInstanceOf(Reference::class, $renderer->getArgument(0)); + $serviceLocatorDefinition = $builder->getDefinition((string) $fragmentHandlerDefinition->getArgument(0)); + $this->assertSame(ServiceLocator::class, $serviceLocatorDefinition->getClass()); + $this->assertEquals(array('foo' => new ServiceClosureArgument(new Reference('my_content_renderer'))), $serviceLocatorDefinition->getArgument(0)); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php index 81fc8b455d183..ae421d919bfd8 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php @@ -12,44 +12,39 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; class MergeExtensionConfigurationPassTest extends TestCase { public function testAutoloadMainExtension() { - $container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerBuilder')->setMethods(array('getExtensionConfig', 'loadFromExtension', 'getParameterBag', 'getDefinitions', 'getAliases', 'getExtensions'))->getMock(); - $params = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag')->getMock(); - - $container->expects($this->at(0)) - ->method('getExtensionConfig') - ->with('loaded') - ->will($this->returnValue(array(array()))); - $container->expects($this->at(1)) - ->method('getExtensionConfig') - ->with('notloaded') - ->will($this->returnValue(array())); - $container->expects($this->once()) - ->method('loadFromExtension') - ->with('notloaded', array()); - - $container->expects($this->any()) - ->method('getParameterBag') - ->will($this->returnValue($params)); - $params->expects($this->any()) - ->method('all') - ->will($this->returnValue(array())); - $container->expects($this->any()) - ->method('getDefinitions') - ->will($this->returnValue(array())); - $container->expects($this->any()) - ->method('getAliases') - ->will($this->returnValue(array())); - $container->expects($this->any()) - ->method('getExtensions') - ->will($this->returnValue(array())); - - $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded')); + $container = new ContainerBuilder(); + $container->registerExtension(new LoadedExtension()); + $container->registerExtension(new NotLoadedExtension()); + $container->loadFromExtension('loaded', array()); + + $configPass = new MergeExtensionConfigurationPass(array('loaded', 'not_loaded')); $configPass->process($container); + + $this->assertTrue($container->hasDefinition('loaded.foo')); + $this->assertTrue($container->hasDefinition('not_loaded.bar')); + } +} + +class LoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $container->register('loaded.foo'); + } +} + +class NotLoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $container->register('not_loaded.bar'); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php index 4452f48771b8b..0a2263d5a8e78 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php @@ -13,8 +13,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\EventListener\SessionListener; @@ -86,6 +88,22 @@ public function testEmptySessionDoesNotSendCookie() $this->assertSame(array(), $response->headers->getCookies()); } + public function testEmptySessionWithNewSessionIdDoesSendCookie() + { + $this->sessionHasBeenStarted(); + $this->sessionIsEmpty(); + $this->fixSessionId('456'); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('/', 'GET', array(), array(new Cookie('MOCKSESSID', '123'))); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + + $response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST); + + $this->assertNotEmpty($response->headers->getCookies()); + } + public function testUnstartedSessionIsNotSave() { $this->sessionHasNotBeenStarted(); @@ -150,6 +168,13 @@ private function sessionIsEmpty() ->will($this->returnValue(true)); } + private function fixSessionId($sessionId) + { + $this->session->expects($this->any()) + ->method('getId') + ->will($this->returnValue($sessionId)); + } + private function getSession() { $mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session') diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index f1a44de87737d..9dab664cf5cdf 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -537,14 +537,14 @@ public function testKernelReset() $kernel = new CustomProjectDirKernel(); $kernel->boot(); - $this->assertSame($containerClass, get_class($kernel->getContainer())); + $this->assertInstanceOf($containerClass, $kernel->getContainer()); $this->assertFileExists($containerFile); unlink(__DIR__.'/Fixtures/cache/custom/FixturesCustomDebugProjectContainer.php.meta'); $kernel = new CustomProjectDirKernel(function ($container) { $container->register('foo', 'stdClass')->setPublic(true); }); $kernel->boot(); - $this->assertTrue(get_class($kernel->getContainer()) !== $containerClass); + $this->assertNotInstanceOf($containerClass, $kernel->getContainer()); $this->assertFileExists($containerFile); $this->assertFileExists(dirname($containerFile).'.legacy'); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php b/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php index 99ff207567439..a1db1d9c0ec61 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php @@ -83,22 +83,22 @@ public function testStoreSpecialCharsInUrl() $profile = new Profile('simple_quote'); $profile->setUrl('http://foo.bar/\''); $this->storage->write($profile); - $this->assertTrue(false !== $this->storage->read('simple_quote'), '->write() accepts single quotes in URL'); + $this->assertNotFalse($this->storage->read('simple_quote'), '->write() accepts single quotes in URL'); $profile = new Profile('double_quote'); $profile->setUrl('http://foo.bar/"'); $this->storage->write($profile); - $this->assertTrue(false !== $this->storage->read('double_quote'), '->write() accepts double quotes in URL'); + $this->assertNotFalse($this->storage->read('double_quote'), '->write() accepts double quotes in URL'); $profile = new Profile('backslash'); $profile->setUrl('http://foo.bar/\\'); $this->storage->write($profile); - $this->assertTrue(false !== $this->storage->read('backslash'), '->write() accepts backslash in URL'); + $this->assertNotFalse($this->storage->read('backslash'), '->write() accepts backslash in URL'); $profile = new Profile('comma'); $profile->setUrl('http://foo.bar/,'); $this->storage->write($profile); - $this->assertTrue(false !== $this->storage->read('comma'), '->write() accepts comma in URL'); + $this->assertNotFalse($this->storage->read('comma'), '->write() accepts comma in URL'); } public function testStoreDuplicateToken() @@ -247,7 +247,7 @@ public function testPurge() $profile->setMethod('GET'); $this->storage->write($profile); - $this->assertTrue(false !== $this->storage->read('token1')); + $this->assertNotFalse($this->storage->read('token1')); $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET')); $profile = new Profile('token2'); @@ -256,7 +256,7 @@ public function testPurge() $profile->setMethod('GET'); $this->storage->write($profile); - $this->assertTrue(false !== $this->storage->read('token2')); + $this->assertNotFalse($this->storage->read('token2')); $this->assertCount(2, $this->storage->find('127.0.0.1', '', 10, 'GET')); $this->storage->purge(); diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index ede21bda1e99a..e96c53f978ad8 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -27,7 +27,7 @@ "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", + "symfony/dependency-injection": "^3.4.5|^4.0.5", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -44,7 +44,7 @@ }, "conflict": { "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", + "symfony/dependency-injection": "<3.4.5|<4.0.5,>=4", "symfony/var-dumper": "<3.4", "twig/twig": "<1.34|<2.4,>=2" }, diff --git a/src/Symfony/Component/Intl/README.md b/src/Symfony/Component/Intl/README.md index 806c6275e83dd..9a9cb9b45614e 100644 --- a/src/Symfony/Component/Intl/README.md +++ b/src/Symfony/Component/Intl/README.md @@ -5,7 +5,7 @@ A PHP replacement layer for the C intl extension that also provides access to the localization data of the ICU library. The replacement layer is limited to the locale "en". If you want to use other -locales, you should [install the intl PHP extension] [0] instead. +locales, you should [install the intl PHP extension][0] instead. Resources --------- diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index e906554f31386..7936a5f7b6d20 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -89,7 +89,7 @@ public function acquire($blocking = false) return true; } catch (LockConflictedException $e) { $this->dirty = false; - $this->logger->warning('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key)); + $this->logger->notice('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key)); if ($blocking) { throw $e; @@ -97,7 +97,7 @@ public function acquire($blocking = false) return false; } catch (\Exception $e) { - $this->logger->warning('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); + $this->logger->notice('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); throw new LockAcquiringException(sprintf('Failed to acquire the "%s" lock.', $this->key), 0, $e); } } @@ -123,10 +123,10 @@ public function refresh() $this->logger->info('Expiration defined for "{resource}" lock for "{ttl}" seconds.', array('resource' => $this->key, 'ttl' => $this->ttl)); } catch (LockConflictedException $e) { $this->dirty = false; - $this->logger->warning('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key)); + $this->logger->notice('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key)); throw $e; } catch (\Exception $e) { - $this->logger->warning('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); + $this->logger->notice('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); throw new LockAcquiringException(sprintf('Failed to define an expiration for the "%s" lock.', $this->key), 0, $e); } } @@ -148,7 +148,7 @@ public function release() $this->dirty = false; if ($this->store->exists($this->key)) { - $this->logger->warning('Failed to release the "{resource}" lock.', array('resource' => $this->key)); + $this->logger->notice('Failed to release the "{resource}" lock.', array('resource' => $this->key)); throw new LockReleasingException(sprintf('Failed to release the "%s" lock.', $this->key)); } } diff --git a/src/Symfony/Component/Lock/Tests/LockTest.php b/src/Symfony/Component/Lock/Tests/LockTest.php index ece5cf667963a..7913644b9a79e 100644 --- a/src/Symfony/Component/Lock/Tests/LockTest.php +++ b/src/Symfony/Component/Lock/Tests/LockTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Tests; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\Lock; @@ -192,6 +193,35 @@ public function testReleaseThrowsExceptionIfNotWellDeleted() $lock->release(); } + /** + * @expectedException \Symfony\Component\Lock\Exception\LockReleasingException + */ + public function testReleaseThrowsAndLog() + { + $key = new Key(uniqid(__METHOD__, true)); + $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + $lock = new Lock($key, $store, 10, true); + $lock->setLogger($logger); + + $logger->expects($this->atLeastOnce()) + ->method('notice') + ->with('Failed to release the "{resource}" lock.', array('resource' => $key)); + + $store + ->expects($this->once()) + ->method('delete') + ->with($key); + + $store + ->expects($this->once()) + ->method('exists') + ->with($key) + ->willReturn(true); + + $lock->release(); + } + /** * @dataProvider provideExpiredDates */ diff --git a/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php index 630ba743cc4e8..2ab030b200f5e 100644 --- a/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php @@ -22,7 +22,7 @@ abstract class AbstractStoreTest extends TestCase { /** - * @return StoreInterface; + * @return StoreInterface */ abstract protected function getStore(); diff --git a/src/Symfony/Component/Lock/Tests/Strategy/ConsensusStrategyTest.php b/src/Symfony/Component/Lock/Tests/Strategy/ConsensusStrategyTest.php index 09215f9a94d63..87e83a9925695 100644 --- a/src/Symfony/Component/Lock/Tests/Strategy/ConsensusStrategyTest.php +++ b/src/Symfony/Component/Lock/Tests/Strategy/ConsensusStrategyTest.php @@ -82,7 +82,7 @@ public function testMet($success, $failure, $total, $isMet) /** * @dataProvider provideIndeterminate */ - public function canBeMet($success, $failure, $total, $isMet) + public function testCanBeMet($success, $failure, $total, $isMet) { $this->assertSame($isMet, $this->strategy->canBeMet($failure, $total)); } diff --git a/src/Symfony/Component/Lock/Tests/Strategy/UnanimousStrategyTest.php b/src/Symfony/Component/Lock/Tests/Strategy/UnanimousStrategyTest.php index 76ea68a41e3b3..beff54a685747 100644 --- a/src/Symfony/Component/Lock/Tests/Strategy/UnanimousStrategyTest.php +++ b/src/Symfony/Component/Lock/Tests/Strategy/UnanimousStrategyTest.php @@ -82,7 +82,7 @@ public function testMet($success, $failure, $total, $isMet) /** * @dataProvider provideIndeterminate */ - public function canBeMet($success, $failure, $total, $isMet) + public function testCanBeMet($success, $failure, $total, $isMet) { $this->assertSame($isMet, $this->strategy->canBeMet($failure, $total)); } diff --git a/src/Symfony/Component/Process/PhpExecutableFinder.php b/src/Symfony/Component/Process/PhpExecutableFinder.php index aba18e9c1c6ab..7f95450a8f1fd 100644 --- a/src/Symfony/Component/Process/PhpExecutableFinder.php +++ b/src/Symfony/Component/Process/PhpExecutableFinder.php @@ -57,6 +57,10 @@ public function find($includeArgs = true) } } + if (is_executable($php = PHP_BINDIR.('\\' === DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) { + return $php; + } + $dirs = array(PHP_BINDIR); if ('\\' === DIRECTORY_SEPARATOR) { $dirs[] = 'C:\xampp\php\\'; diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 0952b804b3323..83c8a0726de35 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -192,7 +192,7 @@ public function __clone() * @throws RuntimeException When process stopped after receiving signal * @throws LogicException In case a callback is provided and output has been disabled * - * @final since version 3.3 + * @final */ public function run(callable $callback = null, array $env = array()): int { @@ -214,7 +214,7 @@ public function run(callable $callback = null, array $env = array()): int * * @throws ProcessFailedException if the process didn't terminate successfully * - * @final since version 3.3 + * @final */ public function mustRun(callable $callback = null, array $env = array()) { @@ -335,7 +335,7 @@ public function start(callable $callback = null, array $env = array()) * * @see start() * - * @final since version 3.3 + * @final */ public function restart(callable $callback = null, array $env = array()) { diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index 212c3dc41fdef..ae90a1e5e2fe1 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -25,7 +25,7 @@ * * @author Kévin Dunglas * - * @final since version 3.3 + * @final */ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface { @@ -53,6 +53,10 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property */ public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null) { + if (!class_exists(DocBlockFactory::class)) { + throw new \RuntimeException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed.', __CLASS__)); + } + $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); $this->contextFactory = new ContextFactory(); $this->phpDocTypeHelper = new PhpDocTypeHelper(); @@ -131,7 +135,9 @@ public function getTypes($class, $property, array $context = array()) $types = array(); /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */ foreach ($docBlock->getTagsByName($tag) as $tag) { - $types = array_merge($types, $this->phpDocTypeHelper->getTypes($tag->getType())); + if ($tag && null !== $tag->getType()) { + $types = array_merge($types, $this->phpDocTypeHelper->getTypes($tag->getType())); + } } if (!isset($types[0])) { @@ -188,7 +194,7 @@ private function getDocBlockFromProperty(string $class, string $property): ?DocB return null; } - return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty)); + return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty->getDeclaringClass())); } private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index 07e192fdc75ce..74f9303cbc990 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -22,7 +22,7 @@ * * @author Kévin Dunglas * - * @final since version 3.3 + * @final */ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface { @@ -92,7 +92,7 @@ public function getProperties($class, array $context = array()) $properties[$propertyName] = $propertyName; } - return array_values($properties); + return $properties ? array_values($properties) : null; } /** diff --git a/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php index 93ebb4364afed..6afbb542e18d9 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php @@ -19,7 +19,7 @@ * * @author Kévin Dunglas * - * @final since version 3.3 + * @final */ class SerializerExtractor implements PropertyListExtractorInterface { diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php index 71edb47e5b4eb..ecb6adde2a980 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php +++ b/src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php @@ -18,7 +18,7 @@ * * @author Kévin Dunglas * - * @final since version 3.3 + * @final */ class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface { diff --git a/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php b/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php index f2d3a82964dc0..5902d38621229 100644 --- a/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php +++ b/src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php @@ -16,7 +16,7 @@ * * @author Kévin Dunglas * - * @final since version 3.3 + * @final */ class PropertyInfoExtractor implements PropertyInfoExtractorInterface { diff --git a/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoPassTest.php b/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoPassTest.php index 21eace4d61254..031e34af97451 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoPassTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoPassTest.php @@ -54,24 +54,16 @@ public function provideTags() public function testReturningEmptyArrayWhenNoService() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds'))->getMock(); - - $container - ->expects($this->any()) - ->method('findTaggedServiceIds') - ->will($this->returnValue(array())) - ; + $container = new ContainerBuilder(); + $propertyInfoExtractorDefinition = $container->register('property_info') + ->setArguments(array(array(), array(), array(), array())); $propertyInfoPass = new PropertyInfoPass(); + $propertyInfoPass->process($container); - $method = new \ReflectionMethod( - 'Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass', - 'findAndSortTaggedServices' - ); - $method->setAccessible(true); - - $actual = $method->invoke($propertyInfoPass, 'tag', $container); - - $this->assertEquals(array(), $actual); + $this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(0)); + $this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(1)); + $this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(2)); + $this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(3)); } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php index 9cc0a8d6e1749..dab463f7e5105 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php @@ -40,6 +40,11 @@ public function testExtract($property, array $type = null, $shortDescription, $l $this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property)); } + public function testParamTagTypeIsOmitted() + { + $this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType')); + } + /** * @dataProvider typesWithCustomPrefixesProvider */ @@ -176,3 +181,15 @@ class EmptyDocBlock { public $foo; } + +class OmittedParamTagTypeDocBlock +{ + /** + * The type is omitted here to ensure that the extractor doesn't choke on missing types. + * + * @param $omittedTagType + */ + public function setOmittedType(array $omittedTagType) + { + } +} diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php index 513504b68d1b3..d73c6ec247bae 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php @@ -59,6 +59,8 @@ public function testGetProperties() ), $this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy') ); + + $this->assertNull($this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\NoProperties')); } public function testGetPropertiesWithCustomPrefixes() diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NoProperties.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NoProperties.php new file mode 100644 index 0000000000000..177bbe4df0f03 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/NoProperties.php @@ -0,0 +1,21 @@ + + * + * 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; + +/** + * @author Kévin Dunglas + */ +class NoProperties +{ +} diff --git a/src/Symfony/Component/PropertyInfo/Type.php b/src/Symfony/Component/PropertyInfo/Type.php index 51c9a2d8684cc..71aa162f70b79 100644 --- a/src/Symfony/Component/PropertyInfo/Type.php +++ b/src/Symfony/Component/PropertyInfo/Type.php @@ -16,7 +16,7 @@ * * @author Kévin Dunglas * - * @final since version 3.3 + * @final */ class Type { diff --git a/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php index 4166fce62694b..5072668ac7b80 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php @@ -23,13 +23,15 @@ class CollectionConfigurator use Traits\RouteTrait; private $parent; + private $parentConfigurator; - public function __construct(RouteCollection $parent, string $name) + public function __construct(RouteCollection $parent, string $name, self $parentConfigurator = null) { $this->parent = $parent; $this->name = $name; $this->collection = new RouteCollection(); $this->route = new Route(''); + $this->parentConfigurator = $parentConfigurator; // for GC control } public function __destruct() @@ -45,7 +47,7 @@ final public function add(string $name, string $path): RouteConfigurator { $this->collection->add($this->name.$name, $route = clone $this->route); - return new RouteConfigurator($this->collection, $route->setPath($path), $this->name); + return new RouteConfigurator($this->collection, $route->setPath($path), $this->name, $this); } /** @@ -55,7 +57,7 @@ final public function add(string $name, string $path): RouteConfigurator */ final public function collection($name = '') { - return new self($this->collection, $this->name.$name); + return new self($this->collection, $this->name.$name, $this); } /** diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php index df54a489e6fad..d0e381ad2090b 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php @@ -22,10 +22,13 @@ class RouteConfigurator use Traits\AddTrait; use Traits\RouteTrait; - public function __construct(RouteCollection $collection, Route $route, string $name = '') + private $parentConfigurator; + + public function __construct(RouteCollection $collection, Route $route, string $name = '', CollectionConfigurator $parentConfigurator = null) { $this->collection = $collection; $this->route = $route; $this->name = $name; + $this->parentConfigurator = $parentConfigurator; // for GC control } } diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php index 779bacfa0eb45..5a3a2cd897117 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php @@ -29,9 +29,10 @@ trait AddTrait */ final public function add(string $name, string $path): RouteConfigurator { + $parentConfigurator = $this instanceof RouteConfigurator ? $this->parentConfigurator : null; $this->collection->add($this->name.$name, $route = new Route($path)); - return new RouteConfigurator($this->collection, $route); + return new RouteConfigurator($this->collection, $route, '', $parentConfigurator); } /** diff --git a/src/Symfony/Component/Routing/Loader/GlobFileLoader.php b/src/Symfony/Component/Routing/Loader/GlobFileLoader.php new file mode 100644 index 0000000000000..03ee341b98250 --- /dev/null +++ b/src/Symfony/Component/Routing/Loader/GlobFileLoader.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * GlobFileLoader loads files from a glob pattern. + * + * @author Nicolas Grekas + */ +class GlobFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + $collection = new RouteCollection(); + + foreach ($this->glob($resource, false, $globResource) as $path => $info) { + $collection->addCollection($this->import($path)); + } + + $collection->addResource($globResource); + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'glob' === $type; + } +} diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php index acb9eddb34de8..f7dfe1d901ad5 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -102,15 +102,13 @@ public function match(\$rawPathinfo) \$pathinfo = rawurldecode(\$rawPathinfo); \$trimmedPathinfo = rtrim(\$pathinfo, '/'); \$context = \$this->context; - \$request = \$this->request; + \$request = \$this->request ?: \$this->createRequest(\$pathinfo); \$requestMethod = \$canonicalMethod = \$context->getMethod(); - \$scheme = \$context->getScheme(); if ('HEAD' === \$requestMethod) { \$canonicalMethod = 'GET'; } - $code throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); @@ -239,7 +237,7 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren $hostMatches = false; $methods = $route->getMethods(); - $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods) || in_array('GET', $methods)); + $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('GET', $methods)); $regex = $compiledRoute->getRegex(); if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.('u' === substr($regex, -1) ? 'u' : ''), $regex, $m)) { @@ -281,59 +279,6 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name); - if ($methods) { - if (1 === count($methods)) { - if ('HEAD' === $methods[0]) { - $code .= <<redirect(\$rawPathinfo.'/', '$name')); } @@ -369,29 +318,69 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren EOF; } + if ($methods) { + $methodVariable = in_array('GET', $methods) ? '$canonicalMethod' : '$requestMethod'; + $methods = implode("', '", $methods); + } + if ($schemes = $route->getSchemes()) { if (!$supportsRedirections) { throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); } $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); - $code .= <<getScheme()]); + if (!in_array($methodVariable, array('$methods'))) { + if (\$hasRequiredScheme) { + \$allow = array_merge(\$allow, array('$methods')); + } + goto $gotoname; + } + if (!\$hasRequiredScheme) { + if ('GET' !== \$canonicalMethod) { + goto $gotoname; + } + + return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); + } + + +EOF; + } else { + $code .= <<getScheme()])) { + if ('GET' !== \$canonicalMethod) { + goto $gotoname; + } + return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); } +EOF; + } + } elseif ($methods) { + $code .= <<handleRouteRequirements($pathinfo, $name, $route); + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + // check HTTP method requirement if ($requiredMethods = $route->getMethods()) { // HEAD and GET are equivalent as per RFC @@ -143,18 +149,14 @@ protected function matchCollection($pathinfo, RouteCollection $routes) } if (!in_array($method, $requiredMethods)) { - $this->allow = array_merge($this->allow, $requiredMethods); + if (self::REQUIREMENT_MATCH === $status[0]) { + $this->allow = array_merge($this->allow, $requiredMethods); + } continue; } } - $status = $this->handleRouteRequirements($pathinfo, $name, $route); - - if (self::REQUIREMENT_MISMATCH === $status[0]) { - continue; - } - return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array())); } } diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index feabf234bc6d4..e22cbc5f2e0c7 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -117,7 +117,7 @@ public function remove($name) * Adds a route collection at the end of the current set by appending all * routes of the added collection. */ - public function addCollection(RouteCollection $collection) + public function addCollection(self $collection) { // we need to remove all routes with the same names first because just replacing them // would not place the new route at the end of the merged array diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index b89efafcf8182..42ddde0077b1a 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -208,7 +208,7 @@ private static function compilePattern(Route $route, $pattern, $isHost) for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { $regexp .= self::computeRegexp($tokens, $i, $firstOptional); } - $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''); + $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'sD'.($isHost ? 'i' : ''); // enable Utf8 matching if really required if ($needsUtf8) { diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php index 839c7d137255b..59253f0749da7 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php @@ -21,15 +21,13 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if ('/' === $pathinfo) { throw new Symfony\Component\Routing\Exception\NoConfigurationException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php index 5d7dca8e55d07..33b86e011b20c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -21,18 +21,16 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if (0 === strpos($pathinfo, '/foo')) { // foo - if (preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + if (preg_match('#^/foo/(?Pbaz|symfony)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); } @@ -45,24 +43,26 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/bar')) { // bar - if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (preg_match('#^/bar/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + if (!in_array($canonicalMethod, array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); goto not_bar; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + return $ret; } not_bar: // barhead - if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_barhead; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + return $ret; } not_barhead: @@ -88,36 +88,38 @@ public function match($rawPathinfo) } // baz4 - if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); } // baz5 - if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_baz5; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + return $ret; } not_baz5: // baz.baz6 - if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('PUT' !== $canonicalMethod) { - $allow[] = 'PUT'; + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + if (!in_array($requestMethod, array('PUT'))) { + $allow = array_merge($allow, array('PUT')); goto not_bazbaz6; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + return $ret; } not_bazbaz6: } // quoter - if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P[\']+)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); } @@ -129,30 +131,30 @@ public function match($rawPathinfo) if (0 === strpos($pathinfo, '/a')) { if (0 === strpos($pathinfo, '/a/b\'b')) { // foo1 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); } // bar1 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); } } // overridden - if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/(?P.*)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); } if (0 === strpos($pathinfo, '/a/b\'b')) { // foo2 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); } // bar2 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); } @@ -162,7 +164,7 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/multi')) { // helloWorld - if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); } @@ -179,12 +181,12 @@ public function match($rawPathinfo) } // foo3 - if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); } // bar3 - if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); } @@ -195,7 +197,7 @@ public function match($rawPathinfo) } // foo4 - if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/aba/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); } @@ -203,7 +205,7 @@ public function match($rawPathinfo) $host = $context->getHost(); - if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { // route1 if ('/route1' === $pathinfo) { return array('_route' => 'route1'); @@ -216,7 +218,7 @@ public function match($rawPathinfo) } - if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) { // route3 if ('/c2/route3' === $pathinfo) { return array('_route' => 'route3'); @@ -224,7 +226,7 @@ public function match($rawPathinfo) } - if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { // route4 if ('/route4' === $pathinfo) { return array('_route' => 'route4'); @@ -232,7 +234,7 @@ public function match($rawPathinfo) } - if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { // route5 if ('/route5' === $pathinfo) { return array('_route' => 'route5'); @@ -245,7 +247,7 @@ public function match($rawPathinfo) return array('_route' => 'route6'); } - if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) { if (0 === strpos($pathinfo, '/route1')) { // route11 if ('/route11' === $pathinfo) { @@ -258,12 +260,12 @@ public function match($rawPathinfo) } // route13 - if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); } // route14 - if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); } @@ -271,16 +273,16 @@ public function match($rawPathinfo) } - if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { // route15 - if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); } } // route16 - if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); } @@ -296,12 +298,12 @@ public function match($rawPathinfo) if (0 === strpos($pathinfo, '/a/b')) { // b - if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); } // c - if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php index 49ce082a81733..6800b7f169aa0 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -21,18 +21,16 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if (0 === strpos($pathinfo, '/foo')) { // foo - if (preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + if (preg_match('#^/foo/(?Pbaz|symfony)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); } @@ -45,24 +43,26 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/bar')) { // bar - if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (preg_match('#^/bar/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + if (!in_array($canonicalMethod, array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); goto not_bar; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + return $ret; } not_bar: // barhead - if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_barhead; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + return $ret; } not_barhead: @@ -83,51 +83,63 @@ public function match($rawPathinfo) // baz3 if ('/test/baz3' === $trimmedPathinfo) { $ret = array('_route' => 'baz3'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_baz3; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz3')); } return $ret; } + not_baz3: } // baz4 - if (preg_match('#^/test/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (preg_match('#^/test/(?P[^/]++)/?$#sD', $pathinfo, $matches)) { $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_baz4; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz4')); } return $ret; } + not_baz4: // baz5 - if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_baz5; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + return $ret; } not_baz5: // baz.baz6 - if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('PUT' !== $canonicalMethod) { - $allow[] = 'PUT'; + if (preg_match('#^/test/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + if (!in_array($requestMethod, array('PUT'))) { + $allow = array_merge($allow, array('PUT')); goto not_bazbaz6; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + return $ret; } not_bazbaz6: } // quoter - if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P[\']+)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); } @@ -139,30 +151,30 @@ public function match($rawPathinfo) if (0 === strpos($pathinfo, '/a')) { if (0 === strpos($pathinfo, '/a/b\'b')) { // foo1 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); } // bar1 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); } } // overridden - if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/(?P.*)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); } if (0 === strpos($pathinfo, '/a/b\'b')) { // foo2 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); } // bar2 - if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b\'b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); } @@ -172,19 +184,24 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/multi')) { // helloWorld - if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); } // hey if ('/multi/hey' === $trimmedPathinfo) { $ret = array('_route' => 'hey'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_hey; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'hey')); } return $ret; } + not_hey: // overridden2 if ('/multi/new' === $pathinfo) { @@ -194,12 +211,12 @@ public function match($rawPathinfo) } // foo3 - if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); } // bar3 - if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); } @@ -210,7 +227,7 @@ public function match($rawPathinfo) } // foo4 - if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/aba/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); } @@ -218,7 +235,7 @@ public function match($rawPathinfo) $host = $context->getHost(); - if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { // route1 if ('/route1' === $pathinfo) { return array('_route' => 'route1'); @@ -231,7 +248,7 @@ public function match($rawPathinfo) } - if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^b\\.example\\.com$#sDi', $host, $hostMatches)) { // route3 if ('/c2/route3' === $pathinfo) { return array('_route' => 'route3'); @@ -239,7 +256,7 @@ public function match($rawPathinfo) } - if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^a\\.example\\.com$#sDi', $host, $hostMatches)) { // route4 if ('/route4' === $pathinfo) { return array('_route' => 'route4'); @@ -247,7 +264,7 @@ public function match($rawPathinfo) } - if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { // route5 if ('/route5' === $pathinfo) { return array('_route' => 'route5'); @@ -260,7 +277,7 @@ public function match($rawPathinfo) return array('_route' => 'route6'); } - if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#sDi', $host, $hostMatches)) { if (0 === strpos($pathinfo, '/route1')) { // route11 if ('/route11' === $pathinfo) { @@ -273,12 +290,12 @@ public function match($rawPathinfo) } // route13 - if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); } // route14 - if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); } @@ -286,16 +303,16 @@ public function match($rawPathinfo) } - if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + if (preg_match('#^c\\.example\\.com$#sDi', $host, $hostMatches)) { // route15 - if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); } } // route16 - if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); } @@ -311,12 +328,12 @@ public function match($rawPathinfo) if (0 === strpos($pathinfo, '/a/b')) { // b - if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/a/b/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); } // c - if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); } @@ -326,23 +343,33 @@ public function match($rawPathinfo) if ('/secure' === $pathinfo) { $ret = array('_route' => 'secure'); $requiredSchemes = array ( 'https' => 0,); - if (!isset($requiredSchemes[$scheme])) { + if (!isset($requiredSchemes[$context->getScheme()])) { + if ('GET' !== $canonicalMethod) { + goto not_secure; + } + return array_replace($ret, $this->redirect($rawPathinfo, 'secure', key($requiredSchemes))); } return $ret; } + not_secure: // nonsecure if ('/nonsecure' === $pathinfo) { $ret = array('_route' => 'nonsecure'); $requiredSchemes = array ( 'http' => 0,); - if (!isset($requiredSchemes[$scheme])) { + if (!isset($requiredSchemes[$context->getScheme()])) { + if ('GET' !== $canonicalMethod) { + goto not_nonsecure; + } + return array_replace($ret, $this->redirect($rawPathinfo, 'nonsecure', key($requiredSchemes))); } return $ret; } + not_nonsecure: throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php index ae54956312554..cfa6d131a057a 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php @@ -21,15 +21,13 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if (0 === strpos($pathinfo, '/rootprefix')) { // static if ('/rootprefix/test' === $pathinfo) { @@ -37,7 +35,7 @@ public function match($rawPathinfo) } // dynamic - if (preg_match('#^/rootprefix/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/rootprefix/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ()); } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php index 50da489fb2a0b..6670eeaf1200c 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php @@ -21,79 +21,83 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - // just_head if ('/just_head' === $pathinfo) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + $ret = array('_route' => 'just_head'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_just_head; } - return array('_route' => 'just_head'); + return $ret; } not_just_head: // head_and_get if ('/head_and_get' === $pathinfo) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + $ret = array('_route' => 'head_and_get'); + if (!in_array($canonicalMethod, array('HEAD', 'GET'))) { + $allow = array_merge($allow, array('HEAD', 'GET')); goto not_head_and_get; } - return array('_route' => 'head_and_get'); + return $ret; } not_head_and_get: // get_and_head if ('/get_and_head' === $pathinfo) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + $ret = array('_route' => 'get_and_head'); + if (!in_array($canonicalMethod, array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); goto not_get_and_head; } - return array('_route' => 'get_and_head'); + return $ret; } not_get_and_head: // post_and_head - if ('/post_and_get' === $pathinfo) { + if ('/post_and_head' === $pathinfo) { + $ret = array('_route' => 'post_and_head'); if (!in_array($requestMethod, array('POST', 'HEAD'))) { $allow = array_merge($allow, array('POST', 'HEAD')); goto not_post_and_head; } - return array('_route' => 'post_and_head'); + return $ret; } not_post_and_head: if (0 === strpos($pathinfo, '/put_and_post')) { // put_and_post if ('/put_and_post' === $pathinfo) { + $ret = array('_route' => 'put_and_post'); if (!in_array($requestMethod, array('PUT', 'POST'))) { $allow = array_merge($allow, array('PUT', 'POST')); goto not_put_and_post; } - return array('_route' => 'put_and_post'); + return $ret; } not_put_and_post: // put_and_get_and_head if ('/put_and_post' === $pathinfo) { - if (!in_array($canonicalMethod, array('PUT', 'GET'))) { - $allow = array_merge($allow, array('PUT', 'GET')); + $ret = array('_route' => 'put_and_get_and_head'); + if (!in_array($canonicalMethod, array('PUT', 'GET', 'HEAD'))) { + $allow = array_merge($allow, array('PUT', 'GET', 'HEAD')); goto not_put_and_get_and_head; } - return array('_route' => 'put_and_get_and_head'); + return $ret; } not_put_and_get_and_head: diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php index 51be5b0bc886f..ab4d6da7bdaff 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php @@ -21,15 +21,13 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if (0 === strpos($pathinfo, '/a')) { // a_first if ('/a/11' === $pathinfo) { @@ -49,7 +47,7 @@ public function match($rawPathinfo) } // a_wildcard - if (preg_match('#^/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (preg_match('#^/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ()); } @@ -57,37 +55,52 @@ public function match($rawPathinfo) // a_fourth if ('/a/44' === $trimmedPathinfo) { $ret = array('_route' => 'a_fourth'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_a_fourth; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fourth')); } return $ret; } + not_a_fourth: // a_fifth if ('/a/55' === $trimmedPathinfo) { $ret = array('_route' => 'a_fifth'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_a_fifth; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fifth')); } return $ret; } + not_a_fifth: // a_sixth if ('/a/66' === $trimmedPathinfo) { $ret = array('_route' => 'a_sixth'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_a_sixth; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_sixth')); } return $ret; } + not_a_sixth: } // nested_wildcard - if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'nested_wildcard')), array ()); } @@ -95,32 +108,47 @@ public function match($rawPathinfo) // nested_a if ('/nested/group/a' === $trimmedPathinfo) { $ret = array('_route' => 'nested_a'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_nested_a; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_a')); } return $ret; } + not_nested_a: // nested_b if ('/nested/group/b' === $trimmedPathinfo) { $ret = array('_route' => 'nested_b'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_nested_b; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_b')); } return $ret; } + not_nested_b: // nested_c if ('/nested/group/c' === $trimmedPathinfo) { $ret = array('_route' => 'nested_c'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_nested_c; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_c')); } return $ret; } + not_nested_c: } @@ -128,32 +156,47 @@ public function match($rawPathinfo) // slashed_a if ('/slashed/group' === $trimmedPathinfo) { $ret = array('_route' => 'slashed_a'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_slashed_a; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_a')); } return $ret; } + not_slashed_a: // slashed_b if ('/slashed/group/b' === $trimmedPathinfo) { $ret = array('_route' => 'slashed_b'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_slashed_b; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_b')); } return $ret; } + not_slashed_b: // slashed_c if ('/slashed/group/c' === $trimmedPathinfo) { $ret = array('_route' => 'slashed_c'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_slashed_c; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_c')); } return $ret; } + not_slashed_c: } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php index 933525699c0e7..61b379ae38727 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php @@ -21,15 +21,13 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if (0 === strpos($pathinfo, '/trailing/simple')) { // simple_trailing_slash_no_methods if ('/trailing/simple/no-methods/' === $pathinfo) { @@ -38,34 +36,37 @@ public function match($rawPathinfo) // simple_trailing_slash_GET_method if ('/trailing/simple/get-method/' === $pathinfo) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + $ret = array('_route' => 'simple_trailing_slash_GET_method'); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_simple_trailing_slash_GET_method; } - return array('_route' => 'simple_trailing_slash_GET_method'); + return $ret; } not_simple_trailing_slash_GET_method: // simple_trailing_slash_HEAD_method if ('/trailing/simple/head-method/' === $pathinfo) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + $ret = array('_route' => 'simple_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_simple_trailing_slash_HEAD_method; } - return array('_route' => 'simple_trailing_slash_HEAD_method'); + return $ret; } not_simple_trailing_slash_HEAD_method: // simple_trailing_slash_POST_method if ('/trailing/simple/post-method/' === $pathinfo) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + $ret = array('_route' => 'simple_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_simple_trailing_slash_POST_method; } - return array('_route' => 'simple_trailing_slash_POST_method'); + return $ret; } not_simple_trailing_slash_POST_method: @@ -73,40 +74,43 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/trailing/regex')) { // regex_trailing_slash_no_methods - if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); } // regex_trailing_slash_GET_method - if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_regex_trailing_slash_GET_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + return $ret; } not_regex_trailing_slash_GET_method: // regex_trailing_slash_HEAD_method - if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_regex_trailing_slash_HEAD_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + return $ret; } not_regex_trailing_slash_HEAD_method: // regex_trailing_slash_POST_method - if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_regex_trailing_slash_POST_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + return $ret; } not_regex_trailing_slash_POST_method: @@ -120,34 +124,37 @@ public function match($rawPathinfo) // simple_not_trailing_slash_GET_method if ('/not-trailing/simple/get-method' === $pathinfo) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + $ret = array('_route' => 'simple_not_trailing_slash_GET_method'); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_simple_not_trailing_slash_GET_method; } - return array('_route' => 'simple_not_trailing_slash_GET_method'); + return $ret; } not_simple_not_trailing_slash_GET_method: // simple_not_trailing_slash_HEAD_method if ('/not-trailing/simple/head-method' === $pathinfo) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + $ret = array('_route' => 'simple_not_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_simple_not_trailing_slash_HEAD_method; } - return array('_route' => 'simple_not_trailing_slash_HEAD_method'); + return $ret; } not_simple_not_trailing_slash_HEAD_method: // simple_not_trailing_slash_POST_method if ('/not-trailing/simple/post-method' === $pathinfo) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + $ret = array('_route' => 'simple_not_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_simple_not_trailing_slash_POST_method; } - return array('_route' => 'simple_not_trailing_slash_POST_method'); + return $ret; } not_simple_not_trailing_slash_POST_method: @@ -155,40 +162,43 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { // regex_not_trailing_slash_no_methods - if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); } // regex_not_trailing_slash_GET_method - if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_regex_not_trailing_slash_GET_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + return $ret; } not_regex_not_trailing_slash_GET_method: // regex_not_trailing_slash_HEAD_method - if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_regex_not_trailing_slash_HEAD_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + return $ret; } not_regex_not_trailing_slash_HEAD_method: // regex_not_trailing_slash_POST_method - if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_regex_not_trailing_slash_POST_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + return $ret; } not_regex_not_trailing_slash_POST_method: diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php index bceee6f3a329d..cdd1dbaa33e2f 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php @@ -21,36 +21,43 @@ public function match($rawPathinfo) $pathinfo = rawurldecode($rawPathinfo); $trimmedPathinfo = rtrim($pathinfo, '/'); $context = $this->context; - $request = $this->request; + $request = $this->request ?: $this->createRequest($pathinfo); $requestMethod = $canonicalMethod = $context->getMethod(); - $scheme = $context->getScheme(); if ('HEAD' === $requestMethod) { $canonicalMethod = 'GET'; } - if (0 === strpos($pathinfo, '/trailing/simple')) { // simple_trailing_slash_no_methods if ('/trailing/simple/no-methods' === $trimmedPathinfo) { $ret = array('_route' => 'simple_trailing_slash_no_methods'); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_simple_trailing_slash_no_methods; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_no_methods')); } return $ret; } + not_simple_trailing_slash_no_methods: // simple_trailing_slash_GET_method if ('/trailing/simple/get-method' === $trimmedPathinfo) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + $ret = array('_route' => 'simple_trailing_slash_GET_method'); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { goto not_simple_trailing_slash_GET_method; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_GET_method')); } - $ret = array('_route' => 'simple_trailing_slash_GET_method'); - if (substr($pathinfo, -1) !== '/') { - return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_GET_method')); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_simple_trailing_slash_GET_method; } return $ret; @@ -58,15 +65,11 @@ public function match($rawPathinfo) not_simple_trailing_slash_GET_method: // simple_trailing_slash_HEAD_method - if ('/trailing/simple/head-method' === $trimmedPathinfo) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; - goto not_simple_trailing_slash_HEAD_method; - } - + if ('/trailing/simple/head-method/' === $pathinfo) { $ret = array('_route' => 'simple_trailing_slash_HEAD_method'); - if (substr($pathinfo, -1) !== '/') { - return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_HEAD_method')); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_simple_trailing_slash_HEAD_method; } return $ret; @@ -75,12 +78,13 @@ public function match($rawPathinfo) // simple_trailing_slash_POST_method if ('/trailing/simple/post-method/' === $pathinfo) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + $ret = array('_route' => 'simple_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_simple_trailing_slash_POST_method; } - return array('_route' => 'simple_trailing_slash_POST_method'); + return $ret; } not_simple_trailing_slash_POST_method: @@ -88,25 +92,34 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/trailing/regex')) { // regex_trailing_slash_no_methods - if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#sD', $pathinfo, $matches)) { $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); - if (substr($pathinfo, -1) !== '/') { + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { + goto not_regex_trailing_slash_no_methods; + } else { return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_no_methods')); } return $ret; } + not_regex_trailing_slash_no_methods: // regex_trailing_slash_GET_method - if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/?$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/?$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + if ('/' === substr($pathinfo, -1)) { + // no-op + } elseif ('GET' !== $canonicalMethod) { goto not_regex_trailing_slash_GET_method; + } else { + return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_GET_method')); } - $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); - if (substr($pathinfo, -1) !== '/') { - return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_GET_method')); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); + goto not_regex_trailing_slash_GET_method; } return $ret; @@ -114,15 +127,11 @@ public function match($rawPathinfo) not_regex_trailing_slash_GET_method: // regex_trailing_slash_HEAD_method - if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/?$#s', $pathinfo, $matches)) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; - goto not_regex_trailing_slash_HEAD_method; - } - + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); - if (substr($pathinfo, -1) !== '/') { - return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_HEAD_method')); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); + goto not_regex_trailing_slash_HEAD_method; } return $ret; @@ -130,13 +139,14 @@ public function match($rawPathinfo) not_regex_trailing_slash_HEAD_method: // regex_trailing_slash_POST_method - if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_regex_trailing_slash_POST_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + return $ret; } not_regex_trailing_slash_POST_method: @@ -150,34 +160,37 @@ public function match($rawPathinfo) // simple_not_trailing_slash_GET_method if ('/not-trailing/simple/get-method' === $pathinfo) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + $ret = array('_route' => 'simple_not_trailing_slash_GET_method'); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_simple_not_trailing_slash_GET_method; } - return array('_route' => 'simple_not_trailing_slash_GET_method'); + return $ret; } not_simple_not_trailing_slash_GET_method: // simple_not_trailing_slash_HEAD_method if ('/not-trailing/simple/head-method' === $pathinfo) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + $ret = array('_route' => 'simple_not_trailing_slash_HEAD_method'); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_simple_not_trailing_slash_HEAD_method; } - return array('_route' => 'simple_not_trailing_slash_HEAD_method'); + return $ret; } not_simple_not_trailing_slash_HEAD_method: // simple_not_trailing_slash_POST_method if ('/not-trailing/simple/post-method' === $pathinfo) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + $ret = array('_route' => 'simple_not_trailing_slash_POST_method'); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_simple_not_trailing_slash_POST_method; } - return array('_route' => 'simple_not_trailing_slash_POST_method'); + return $ret; } not_simple_not_trailing_slash_POST_method: @@ -185,40 +198,43 @@ public function match($rawPathinfo) elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { // regex_not_trailing_slash_no_methods - if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#sD', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); } // regex_not_trailing_slash_GET_method - if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('GET' !== $canonicalMethod) { - $allow[] = 'GET'; + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + if (!in_array($canonicalMethod, array('GET'))) { + $allow = array_merge($allow, array('GET')); goto not_regex_not_trailing_slash_GET_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + return $ret; } not_regex_not_trailing_slash_GET_method: // regex_not_trailing_slash_HEAD_method - if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('HEAD' !== $requestMethod) { - $allow[] = 'HEAD'; + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + if (!in_array($requestMethod, array('HEAD'))) { + $allow = array_merge($allow, array('HEAD')); goto not_regex_not_trailing_slash_HEAD_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + return $ret; } not_regex_not_trailing_slash_HEAD_method: // regex_not_trailing_slash_POST_method - if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { - if ('POST' !== $canonicalMethod) { - $allow[] = 'POST'; + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#sD', $pathinfo, $matches)) { + $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + if (!in_array($requestMethod, array('POST'))) { + $allow = array_merge($allow, array('POST')); goto not_regex_not_trailing_slash_POST_method; } - return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + return $ret; } not_regex_not_trailing_slash_POST_method: diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php index 04f6d7ed6eab2..0780c9fa80f1d 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl.php @@ -4,6 +4,7 @@ return function (RoutingConfigurator $routes) { $routes + ->collection() ->add('foo', '/foo') ->condition('abc') ->options(array('utf8' => true)) diff --git a/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php new file mode 100644 index 0000000000000..08d806a8a4af6 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Loader/GlobFileLoaderTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\GlobFileLoader; +use Symfony\Component\Routing\RouteCollection; + +class GlobFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new GlobFileLoader(new FileLocator()); + + $this->assertTrue($loader->supports('any-path', 'glob'), '->supports() returns true if the resource has the glob type'); + $this->assertFalse($loader->supports('any-path'), '->supports() returns false if the resource is not of glob type'); + } + + public function testLoadAddsTheGlobResourceToTheContainer() + { + $loader = new GlobFileLoaderWithoutImport(new FileLocator()); + $collection = $loader->load(__DIR__.'/../Fixtures/directory/*.yml'); + + $this->assertEquals(new GlobResource(__DIR__.'/../Fixtures/directory', '/*.yml', false), $collection->getResources()[0]); + } +} + +class GlobFileLoaderWithoutImport extends GlobFileLoader +{ + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) + { + return new RouteCollection(); + } +} diff --git a/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php new file mode 100644 index 0000000000000..cfbb524d3aa79 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class DumpedRedirectableUrlMatcherTest extends RedirectableUrlMatcherTest +{ + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + static $i = 0; + + $class = 'DumpedRedirectableUrlMatcher'.++$i; + $dumper = new PhpMatcherDumper($routes); + eval('?>'.$dumper->dump(array('class' => $class, 'base_class' => 'Symfony\Component\Routing\Tests\Matcher\TestDumpedRedirectableUrlMatcher'))); + + return $this->getMockBuilder($class) + ->setConstructorArgs(array($context ?: new RequestContext())) + ->setMethods(array('redirect')) + ->getMock(); + } +} + +class TestDumpedRedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + return array(); + } +} diff --git a/src/Symfony/Component/Routing/Tests/Matcher/DumpedUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/DumpedUrlMatcherTest.php new file mode 100644 index 0000000000000..880b2b13b1112 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Matcher/DumpedUrlMatcherTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class DumpedUrlMatcherTest extends UrlMatcherTest +{ + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface. + */ + public function testSchemeRequirement() + { + parent::testSchemeRequirement(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface. + */ + public function testSchemeAndMethodMismatch() + { + parent::testSchemeRequirement(); + } + + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + static $i = 0; + + $class = 'DumpedUrlMatcher'.++$i; + $dumper = new PhpMatcherDumper($routes); + eval('?>'.$dumper->dump(array('class' => $class))); + + return new $class($context ?: new RequestContext()); + } +} diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php index e4c18c47b144f..f29a6d6a30a17 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -354,7 +354,7 @@ public function getRouteCollections() array('GET', 'HEAD') )); $headMatchCasesCollection->add('post_and_head', new Route( - '/post_and_get', + '/post_and_head', array(), array(), array(), diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php index 0948e002adf8a..7984391e1210f 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -11,19 +11,18 @@ namespace Symfony\Component\Routing\Tests\Matcher; -use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RequestContext; -class RedirectableUrlMatcherTest extends TestCase +class RedirectableUrlMatcherTest extends UrlMatcherTest { - public function testRedirectWhenNoSlash() + public function testMissingTrailingSlash() { $coll = new RouteCollection(); $coll->add('foo', new Route('/foo/')); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher = $this->getUrlMatcher($coll); $matcher->expects($this->once())->method('redirect')->will($this->returnValue(array())); $matcher->match('/foo'); } @@ -38,7 +37,7 @@ public function testRedirectWhenNoSlashForNonSafeMethod() $context = new RequestContext(); $context->setMethod('POST'); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, $context)); + $matcher = $this->getUrlMatcher($coll, $context); $matcher->match('/foo'); } @@ -47,7 +46,7 @@ public function testSchemeRedirectRedirectsToFirstScheme() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('FTP', 'HTTPS'))); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher = $this->getUrlMatcher($coll); $matcher ->expects($this->once()) ->method('redirect') @@ -57,12 +56,12 @@ public function testSchemeRedirectRedirectsToFirstScheme() $matcher->match('/foo'); } - public function testNoSchemaRedirectIfOnOfMultipleSchemesMatches() + public function testNoSchemaRedirectIfOneOfMultipleSchemesMatches() { $coll = new RouteCollection(); $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https', 'http'))); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher = $this->getUrlMatcher($coll); $matcher ->expects($this->never()) ->method('redirect'); @@ -74,7 +73,7 @@ public function testSchemeRedirectWithParams() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo/{bar}', array(), array(), array(), '', array('https'))); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher = $this->getUrlMatcher($coll); $matcher ->expects($this->once()) ->method('redirect') @@ -89,7 +88,7 @@ public function testSlashRedirectWithParams() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo/{bar}/')); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher = $this->getUrlMatcher($coll); $matcher ->expects($this->once()) ->method('redirect') @@ -104,8 +103,22 @@ public function testRedirectPreservesUrlEncoding() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo:bar/')); - $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher = $this->getUrlMatcher($coll); $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/')->willReturn(array()); $matcher->match('/foo%3Abar'); } + + public function testSchemeRequirement() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); + $matcher = $this->getUrlMatcher($coll, new RequestContext()); + $matcher->expects($this->once())->method('redirect')->with('/foo', 'foo', 'https')->willReturn(array()); + $this->assertSame(array('_route' => 'foo'), $matcher->match('/foo')); + } + + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext())); + } } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 8545c2c29d83d..1bd75fa339c21 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -26,7 +26,7 @@ public function testNoMethodSoAllowed() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo')); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertInternalType('array', $matcher->match('/foo')); } @@ -35,7 +35,7 @@ public function testMethodNotAllowed() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('post'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); try { $matcher->match('/foo'); @@ -50,7 +50,7 @@ public function testHeadAllowedWhenRequirementContainsGet() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get'))); - $matcher = new UrlMatcher($coll, new RequestContext('', 'head')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'head')); $this->assertInternalType('array', $matcher->match('/foo')); } @@ -60,7 +60,7 @@ public function testMethodNotAllowedAggregatesAllowedMethods() $coll->add('foo1', new Route('/foo', array(), array(), array(), '', array(), array('post'))); $coll->add('foo2', new Route('/foo', array(), array(), array(), '', array(), array('put', 'delete'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); try { $matcher->match('/foo'); @@ -75,7 +75,7 @@ public function testMatch() // test the patterns are matched and parameters are returned $collection = new RouteCollection(); $collection->add('foo', new Route('/foo/{bar}')); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); try { $matcher->match('/no-match'); $this->fail(); @@ -86,17 +86,17 @@ public function testMatch() // test that defaults are merged $collection = new RouteCollection(); $collection->add('foo', new Route('/foo/{bar}', array('def' => 'test'))); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz')); // test that route "method" is ignored if no method is given in the context $collection = new RouteCollection(); $collection->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get', 'head'))); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertInternalType('array', $matcher->match('/foo')); // route does not match with POST method context - $matcher = new UrlMatcher($collection, new RequestContext('', 'post')); + $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'post')); try { $matcher->match('/foo'); $this->fail(); @@ -104,28 +104,28 @@ public function testMatch() } // route does match with GET or HEAD method context - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertInternalType('array', $matcher->match('/foo')); - $matcher = new UrlMatcher($collection, new RequestContext('', 'head')); + $matcher = $this->getUrlMatcher($collection, new RequestContext('', 'head')); $this->assertInternalType('array', $matcher->match('/foo')); // route with an optional variable as the first segment $collection = new RouteCollection(); $collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar'))); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo')); $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo')); $collection = new RouteCollection(); $collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar'))); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo')); $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/')); // route with only optional variables $collection = new RouteCollection(); $collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array())); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/')); $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a')); $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b')); @@ -138,7 +138,7 @@ public function testMatchWithPrefixes() $collection->addPrefix('/b'); $collection->addPrefix('/a'); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo')); } @@ -149,7 +149,7 @@ public function testMatchWithDynamicPrefix() $collection->addPrefix('/b'); $collection->addPrefix('/{_locale}'); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo')); } @@ -158,17 +158,29 @@ public function testMatchSpecialRouteName() $collection = new RouteCollection(); $collection->add('$péß^a|', new Route('/bar')); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar')); } + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testTrailingEncodedNewlineIsNotOverlooked() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $matcher = $this->getUrlMatcher($collection); + $matcher->match('/foo%0a'); + } + public function testMatchNonAlpha() { $collection = new RouteCollection(); $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-'; $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'), array('utf8' => true))); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar')); $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar')); } @@ -178,7 +190,7 @@ public function testMatchWithDotMetacharacterInRequirements() $collection = new RouteCollection(); $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+'))); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched'); } @@ -192,7 +204,7 @@ public function testMatchOverriddenRoute() $collection->addCollection($collection1); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); $this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1')); $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); @@ -205,12 +217,12 @@ public function testMatchRegression() $coll->add('foo', new Route('/foo/{foo}')); $coll->add('bar', new Route('/foo/bar/{foo}')); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar')); $collection = new RouteCollection(); $collection->add('foo', new Route('/{bar}')); - $matcher = new UrlMatcher($collection, new RequestContext()); + $matcher = $this->getUrlMatcher($collection); try { $matcher->match('/'); $this->fail(); @@ -223,7 +235,7 @@ public function testDefaultRequirementForOptionalVariables() $coll = new RouteCollection(); $coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml')); } @@ -232,7 +244,7 @@ public function testMatchingIsEager() $coll = new RouteCollection(); $coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-')); } @@ -241,7 +253,7 @@ public function testAdjacentVariables() $coll = new RouteCollection(); $coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); // 'w' eagerly matches as much as possible and the other variables match the remaining chars. // This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement. // Otherwise they would also consume '.xml' and _format would never match as it's an optional variable. @@ -260,7 +272,7 @@ public function testOptionalVariableWithNoRealSeparator() { $coll = new RouteCollection(); $coll->add('test', new Route('/get{what}', array('what' => 'All'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get')); $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites')); @@ -275,7 +287,7 @@ public function testRequiredVariableWithNoRealSeparator() { $coll = new RouteCollection(); $coll->add('test', new Route('/get{what}Suffix')); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix')); } @@ -284,7 +296,7 @@ public function testDefaultRequirementOfVariable() { $coll = new RouteCollection(); $coll->add('test', new Route('/{page}.{_format}')); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html')); } @@ -296,7 +308,7 @@ public function testDefaultRequirementOfVariableDisallowsSlash() { $coll = new RouteCollection(); $coll->add('test', new Route('/{page}.{_format}')); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $matcher->match('/index.sl/ash'); } @@ -308,7 +320,7 @@ public function testDefaultRequirementOfVariableDisallowsNextSeparator() { $coll = new RouteCollection(); $coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $matcher->match('/do.t.html'); } @@ -320,7 +332,7 @@ public function testSchemeRequirement() { $coll = new RouteCollection(); $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $matcher->match('/foo'); } @@ -333,7 +345,7 @@ public function testCondition() $route = new Route('/foo'); $route->setCondition('context.getMethod() == "POST"'); $coll->add('foo', $route); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $matcher->match('/foo'); } @@ -343,7 +355,7 @@ public function testRequestCondition() $route = new Route('/foo/{bar}'); $route->setCondition('request.getBaseUrl() == "/sub/front.php" and request.getPathInfo() == "/foo/bar"'); $coll->add('foo', $route); - $matcher = new UrlMatcher($coll, new RequestContext('/sub/front.php')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('/sub/front.php')); $this->assertEquals(array('bar' => 'bar', '_route' => 'foo'), $matcher->match('/foo/bar')); } @@ -352,7 +364,7 @@ public function testDecodeOnce() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo/{foo}')); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523')); } @@ -368,7 +380,7 @@ public function testCannotRelyOnPrefix() $coll->addCollection($subColl); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new')); } @@ -377,7 +389,7 @@ public function testWithHost() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); - $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); } @@ -388,10 +400,10 @@ public function testWithHostOnRouteCollection() $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net')); $coll->setHost('{locale}.example.com'); - $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); - $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar')); } @@ -403,7 +415,7 @@ public function testWithOutHostHostDoesNotMatch() $coll = new RouteCollection(); $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); - $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); $matcher->match('/foo/bar'); } @@ -415,7 +427,7 @@ public function testPathIsCaseSensitive() $coll = new RouteCollection(); $coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE'))); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); $matcher->match('/en'); } @@ -424,7 +436,7 @@ public function testHostIsCaseInsensitive() $coll = new RouteCollection(); $coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com')); - $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); $this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/')); } @@ -435,7 +447,48 @@ public function testNoConfiguration() { $coll = new RouteCollection(); - $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher = $this->getUrlMatcher($coll); + $matcher->match('/'); + } + + public function testNestedCollections() + { + $coll = new RouteCollection(); + + $subColl = new RouteCollection(); + $subColl->add('a', new Route('/a')); + $subColl->add('b', new Route('/b')); + $subColl->add('c', new Route('/c')); + $subColl->addPrefix('/p'); + $coll->addCollection($subColl); + + $coll->add('baz', new Route('/{baz}')); + + $subColl = new RouteCollection(); + $subColl->add('buz', new Route('/buz')); + $subColl->addPrefix('/prefix'); + $coll->addCollection($subColl); + + $matcher = $this->getUrlMatcher($coll); + $this->assertEquals(array('_route' => 'a'), $matcher->match('/p/a')); + $this->assertEquals(array('_route' => 'baz', 'baz' => 'p'), $matcher->match('/p')); + $this->assertEquals(array('_route' => 'buz'), $matcher->match('/prefix/buz')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testSchemeAndMethodMismatch() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array(), array(), null, array('https'), array('POST'))); + + $matcher = $this->getUrlMatcher($coll); $matcher->match('/'); } + + protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null) + { + return new UrlMatcher($routes, $context ?: new RequestContext()); + } } diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index f6af600bd4221..76a042d670b29 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -357,7 +357,7 @@ public function testAddsThePrefixOnlyOnceWhenLoadingMultipleCollections() $routeCollectionBuilder->import('/directory/recurse/*', '/other/', 'glob'); $routes = $routeCollectionBuilder->build()->all(); - $this->assertEquals(2, count($routes)); + $this->assertCount(2, $routes); $this->assertEquals('/other/a', $routes['a']->getPath()); $this->assertEquals('/other/b', $routes['b']->getPath()); } diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index 0d114ec6a4d69..d9db127da91a8 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -38,7 +38,7 @@ public function provideCompileData() array( 'Static route', array('/foo'), - '/foo', '#^/foo$#s', array(), array( + '/foo', '#^/foo$#sD', array(), array( array('text', '/foo'), ), ), @@ -46,7 +46,7 @@ public function provideCompileData() array( 'Route with a variable', array('/foo/{bar}'), - '/foo', '#^/foo/(?P[^/]++)$#s', array('bar'), array( + '/foo', '#^/foo/(?P[^/]++)$#sD', array('bar'), array( array('variable', '/', '[^/]++', 'bar'), array('text', '/foo'), ), @@ -55,7 +55,7 @@ public function provideCompileData() array( 'Route with a variable that has a default value', array('/foo/{bar}', array('bar' => 'bar')), - '/foo', '#^/foo(?:/(?P[^/]++))?$#s', array('bar'), array( + '/foo', '#^/foo(?:/(?P[^/]++))?$#sD', array('bar'), array( array('variable', '/', '[^/]++', 'bar'), array('text', '/foo'), ), @@ -64,7 +64,7 @@ public function provideCompileData() array( 'Route with several variables', array('/foo/{bar}/{foobar}'), - '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#sD', array('bar', 'foobar'), array( array('variable', '/', '[^/]++', 'foobar'), array('variable', '/', '[^/]++', 'bar'), array('text', '/foo'), @@ -74,7 +74,7 @@ public function provideCompileData() array( 'Route with several variables that have default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), - '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#s', array('bar', 'foobar'), array( + '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#sD', array('bar', 'foobar'), array( array('variable', '/', '[^/]++', 'foobar'), array('variable', '/', '[^/]++', 'bar'), array('text', '/foo'), @@ -84,7 +84,7 @@ public function provideCompileData() array( 'Route with several variables but some of them have no default values', array('/foo/{bar}/{foobar}', array('bar' => 'bar')), - '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#sD', array('bar', 'foobar'), array( array('variable', '/', '[^/]++', 'foobar'), array('variable', '/', '[^/]++', 'bar'), array('text', '/foo'), @@ -94,7 +94,7 @@ public function provideCompileData() array( 'Route with an optional variable as the first segment', array('/{bar}', array('bar' => 'bar')), - '', '#^/(?P[^/]++)?$#s', array('bar'), array( + '', '#^/(?P[^/]++)?$#sD', array('bar'), array( array('variable', '/', '[^/]++', 'bar'), ), ), @@ -102,7 +102,7 @@ public function provideCompileData() array( 'Route with a requirement of 0', array('/{bar}', array('bar' => null), array('bar' => '0')), - '', '#^/(?P0)?$#s', array('bar'), array( + '', '#^/(?P0)?$#sD', array('bar'), array( array('variable', '/', '0', 'bar'), ), ), @@ -110,7 +110,7 @@ public function provideCompileData() array( 'Route with an optional variable as the first segment with requirements', array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')), - '', '#^/(?P(foo|bar))?$#s', array('bar'), array( + '', '#^/(?P(foo|bar))?$#sD', array('bar'), array( array('variable', '/', '(foo|bar)', 'bar'), ), ), @@ -118,7 +118,7 @@ public function provideCompileData() array( 'Route with only optional variables', array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')), - '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#s', array('foo', 'bar'), array( + '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#sD', array('foo', 'bar'), array( array('variable', '/', '[^/]++', 'bar'), array('variable', '/', '[^/]++', 'foo'), ), @@ -127,7 +127,7 @@ public function provideCompileData() array( 'Route with a variable in last position', array('/foo-{bar}'), - '/foo-', '#^/foo\-(?P[^/]++)$#s', array('bar'), array( + '/foo-', '#^/foo\-(?P[^/]++)$#sD', array('bar'), array( array('variable', '-', '[^/]++', 'bar'), array('text', '/foo'), ), @@ -136,7 +136,7 @@ public function provideCompileData() array( 'Route with nested placeholders', array('/{static{var}static}'), - '/{static', '#^/\{static(?P[^/]+)static\}$#s', array('var'), array( + '/{static', '#^/\{static(?P[^/]+)static\}$#sD', array('var'), array( array('text', 'static}'), array('variable', '', '[^/]+', 'var'), array('text', '/{static'), @@ -146,7 +146,7 @@ public function provideCompileData() array( 'Route without separator between variables', array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), - '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#s', array('w', 'x', 'y', 'z', '_format'), array( + '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#sD', array('w', 'x', 'y', 'z', '_format'), array( array('variable', '.', '[^/]++', '_format'), array('variable', '', '[^/\.]++', 'z'), array('variable', '', '(y|Y)', 'y'), @@ -158,7 +158,7 @@ public function provideCompileData() array( 'Route with a format', array('/foo/{bar}.{_format}'), - '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#s', array('bar', '_format'), array( + '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#sD', array('bar', '_format'), array( array('variable', '.', '[^/]++', '_format'), array('variable', '/', '[^/\.]++', 'bar'), array('text', '/foo'), @@ -168,7 +168,7 @@ public function provideCompileData() array( 'Static non UTF-8 route', array("/fo\xE9"), - "/fo\xE9", "#^/fo\xE9$#s", array(), array( + "/fo\xE9", "#^/fo\xE9$#sD", array(), array( array('text', "/fo\xE9"), ), ), @@ -176,7 +176,7 @@ public function provideCompileData() array( 'Route with an explicit UTF-8 requirement', array('/{bar}', array('bar' => null), array('bar' => '.'), array('utf8' => true)), - '', '#^/(?P.)?$#su', array('bar'), array( + '', '#^/(?P.)?$#sDu', array('bar'), array( array('variable', '/', '.', 'bar', true), ), ), @@ -205,7 +205,7 @@ public function provideCompileImplicitUtf8Data() array( 'Static UTF-8 route', array('/foé'), - '/foé', '#^/foé$#su', array(), array( + '/foé', '#^/foé$#sDu', array(), array( array('text', '/foé'), ), 'patterns', @@ -214,7 +214,7 @@ public function provideCompileImplicitUtf8Data() array( 'Route with an implicit UTF-8 requirement', array('/{bar}', array('bar' => null), array('bar' => 'é')), - '', '#^/(?Pé)?$#su', array('bar'), array( + '', '#^/(?Pé)?$#sDu', array('bar'), array( array('variable', '/', 'é', 'bar', true), ), 'requirements', @@ -223,7 +223,7 @@ public function provideCompileImplicitUtf8Data() array( 'Route with a UTF-8 class requirement', array('/{bar}', array('bar' => null), array('bar' => '\pM')), - '', '#^/(?P\pM)?$#su', array('bar'), array( + '', '#^/(?P\pM)?$#sDu', array('bar'), array( array('variable', '/', '\pM', 'bar', true), ), 'requirements', @@ -232,7 +232,7 @@ public function provideCompileImplicitUtf8Data() array( 'Route with a UTF-8 separator', array('/foo/{bar}§{_format}', array(), array(), array('compiler_class' => Utf8RouteCompiler::class)), - '/foo', '#^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$#su', array('bar', '_format'), array( + '/foo', '#^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$#sDu', array('bar', '_format'), array( array('variable', '§', '[^/]++', '_format', true), array('variable', '/', '[^/§]++', 'bar', true), array('text', '/foo'), @@ -326,21 +326,21 @@ public function provideCompileWithHostData() array( 'Route with host pattern', array('/hello', array(), array(), array(), 'www.example.com'), - '/hello', '#^/hello$#s', array(), array(), array( + '/hello', '#^/hello$#sD', array(), array(), array( array('text', '/hello'), ), - '#^www\.example\.com$#si', array(), array( + '#^www\.example\.com$#sDi', array(), array( array('text', 'www.example.com'), ), ), array( 'Route with host pattern and some variables', array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'), - '/hello', '#^/hello/(?P[^/]++)$#s', array('tld', 'name'), array('name'), array( + '/hello', '#^/hello/(?P[^/]++)$#sD', array('tld', 'name'), array('name'), array( array('variable', '/', '[^/]++', 'name'), array('text', '/hello'), ), - '#^www\.example\.(?P[^\.]++)$#si', array('tld'), array( + '#^www\.example\.(?P[^\.]++)$#sDi', array('tld'), array( array('variable', '.', '[^\.]++', 'tld'), array('text', 'www.example'), ), @@ -348,10 +348,10 @@ public function provideCompileWithHostData() array( 'Route with variable at beginning of host', array('/hello', array(), array(), array(), '{locale}.example.{tld}'), - '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + '/hello', '#^/hello$#sD', array('locale', 'tld'), array(), array( array('text', '/hello'), ), - '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#sDi', array('locale', 'tld'), array( array('variable', '.', '[^\.]++', 'tld'), array('text', '.example'), array('variable', '', '[^\.]++', 'locale'), @@ -360,10 +360,10 @@ public function provideCompileWithHostData() array( 'Route with host variables that has a default value', array('/hello', array('locale' => 'a', 'tld' => 'b'), array(), array(), '{locale}.example.{tld}'), - '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + '/hello', '#^/hello$#sD', array('locale', 'tld'), array(), array( array('text', '/hello'), ), - '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#sDi', array('locale', 'tld'), array( array('variable', '.', '[^\.]++', 'tld'), array('text', '.example'), array('variable', '', '[^\.]++', 'locale'), diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php index ff7e320c5fc95..c7af058e3b09d 100644 --- a/src/Symfony/Component/Routing/Tests/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteTest.php @@ -245,7 +245,7 @@ public function testSerializeWhenCompiledWithClass() */ public function testSerializedRepresentationKeepsWorking() { - $serialized = 'C:31:"Symfony\Component\Routing\Route":934:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":569:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:30:"#^/prefix(?:/(?P\d+))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:39:"#^(?P[^\.]++)\.example\.net$#si";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}'; + $serialized = 'C:31:"Symfony\Component\Routing\Route":936:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":571:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:31:"#^/prefix(?:/(?P\d+))?$#sD";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:40:"#^(?P[^\.]++)\.example\.net$#sDi";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}'; $unserialized = unserialize($serialized); $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); diff --git a/src/Symfony/Component/Routing/Tests/RouterTest.php b/src/Symfony/Component/Routing/Tests/RouterTest.php index 409959eec07dc..3e3d43f82cc86 100644 --- a/src/Symfony/Component/Routing/Tests/RouterTest.php +++ b/src/Symfony/Component/Routing/Tests/RouterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Routing\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Router; use Symfony\Component\HttpFoundation\Request; @@ -83,7 +84,7 @@ public function testThatRouteCollectionIsLoaded() { $this->router->setOption('resource_type', 'ResourceType'); - $routeCollection = $this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock(); + $routeCollection = new RouteCollection(); $this->loader->expects($this->once()) ->method('load')->with('routing.yml', 'ResourceType') @@ -101,7 +102,7 @@ public function testMatcherIsCreatedIfCacheIsNotConfigured($option) $this->loader->expects($this->once()) ->method('load')->with('routing.yml', null) - ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock())); + ->will($this->returnValue(new RouteCollection())); $this->assertInstanceOf('Symfony\\Component\\Routing\\Matcher\\UrlMatcher', $this->router->getMatcher()); } @@ -123,7 +124,7 @@ public function testGeneratorIsCreatedIfCacheIsNotConfigured($option) $this->loader->expects($this->once()) ->method('load')->with('routing.yml', null) - ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock())); + ->will($this->returnValue(new RouteCollection())); $this->assertInstanceOf('Symfony\\Component\\Routing\\Generator\\UrlGenerator', $this->router->getGenerator()); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php index e2b49614dff4f..662135a85151e 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Security\Core\Authentication\Token; -use Symfony\Component\Security\Core\Role\RoleInterface; +use Symfony\Component\Security\Core\Role\Role; /** * PreAuthenticatedToken implements a pre-authenticated token. diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php index 1270cc2b4ce8f..1959a1bdf98c4 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authentication\Token; +use Symfony\Component\Security\Core\Role\Role; + /** * UsernamePasswordToken implements a username and password token. * diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.lt.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.lt.xlf index 0b426dcc01f6e..b7909033a6e67 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.lt.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.lt.xlf @@ -8,7 +8,7 @@ Authentication credentials could not be found. - Nepavyko rasti autentifikacijos duomneų. + Nepavyko rasti autentifikacijos duomenų. Authentication request could not be processed due to a system problem. diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf new file mode 100644 index 0000000000000..c74b10aea83ab --- /dev/null +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.tl.xlf @@ -0,0 +1,71 @@ + + + + + + An authentication exception occurred. + Isang pambihirang pagpaptunay ang nangyari. + + + Authentication credentials could not be found. + Hindi mahanap ang mga kinakailangan na dokumento para sa pagpapatunay. + + + Authentication request could not be processed due to a system problem. + Hindi maproseso ang iyong hiling dahil may problema sa sistema. + + + Invalid credentials. + Hindi balidong mga dokumento. + + + Cookie has already been used by someone else. + Ang Cookie ay ginamit na ng ibang tao. + + + Not privileged to request the resource. + Walang pribilehiyo upang humingi ng mga bagong mapagkukunan. + + + Invalid CSRF token. + Hindi balidong mga token ng CSRF. + + + Digest nonce has expired. + Na-expire na ang Digest nonce. + + + No authentication provider found to support the authentication token. + Walang nakitang nagbibibagay ng suporta sa token ng pagpapatunay. + + + No session available, it either timed out or cookies are not enabled. + Walang sesyon ng magagamit, ito ay nawalan ng oras o hindi pinagana. + + + No token could be found. + Walang token na nahanap. + + + Username could not be found. + Walang username na makita. + + + Account has expired. + Ang akawnt ay nag-expire na. + + + Credentials have expired. + .ng mga kinakailangang dokumento ay nag expire na. + + + Account is disabled. + Ang akawnt ay hindi pinagana. + + + Account is locked. + ng akawnt ay nakasara. + + + + diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 2a116e375d8a2..4346754cbc55a 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -30,7 +30,7 @@ * * @author Ryan Weaver * - * @final since version 3.4 + * @final */ class GuardAuthenticatorHandler { diff --git a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php index 122eea7ee00f0..14dadf3f058d3 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -85,9 +85,13 @@ protected function attemptAuthentication(Request $request) } } - $requestBag = $this->options['post_only'] ? $request->request : $request; - $username = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['username_parameter']); - $password = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['password_parameter']); + if ($this->options['post_only']) { + $username = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']); + $password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']); + } else { + $username = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']); + $password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']); + } if (!\is_string($username) || (\is_object($username) && !\method_exists($username, '__toString'))) { throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($username))); diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 548ce7ce91900..7de596db27a78 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -95,7 +95,7 @@ public function handle(GetResponseEvent $event) if (!$this->stateless) { $request->query->remove($this->usernameParameter); - $request->server->set('QUERY_STRING', http_build_query($request->query->all())); + $request->server->set('QUERY_STRING', http_build_query($request->query->all(), '', '&')); $response = new RedirectResponse($request->getUri(), 302); $event->setResponse($response); diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index cd3fdd6798b1a..c4ee1bffaf317 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -77,9 +77,13 @@ protected function attemptAuthentication(Request $request) } } - $requestBag = $this->options['post_only'] ? $request->request : $request; - $username = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['username_parameter']); - $password = ParameterBagUtils::getParameterBagValue($requestBag, $this->options['password_parameter']); + if ($this->options['post_only']) { + $username = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']); + $password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']); + } else { + $username = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']); + $password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']); + } if (!\is_string($username) || (\is_object($username) && !\method_exists($username, '__toString'))) { throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($username))); diff --git a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php index 040fe52775aa0..e81191823cfe0 100644 --- a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php +++ b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php @@ -114,7 +114,7 @@ private function generateLogoutUrl($key, $referenceType) $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBaseUrl().$logoutPath; if (!empty($parameters)) { - $url .= '?'.http_build_query($parameters); + $url .= '?'.http_build_query($parameters, '', '&'); } } else { if (!$this->router) { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php index f3962a391ed98..6ab543225c697 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php @@ -77,14 +77,14 @@ public function testHandleWhenUsernameLength($username, $ok) } /** + * @dataProvider postOnlyDataProvider * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException * @expectedExceptionMessage The key "_username" must be a string, "array" given. */ - public function testHandleNonStringUsername() + public function testHandleNonStringUsername($postOnly) { $request = Request::create('/login_check', 'POST', array('_username' => array())); $request->setSession($this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock()); - $listener = new UsernamePasswordFormAuthenticationListener( new TokenStorage(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(), @@ -93,14 +93,20 @@ public function testHandleNonStringUsername() 'foo', new DefaultAuthenticationSuccessHandler($httpUtils), new DefaultAuthenticationFailureHandler($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $httpUtils), - array('require_previous_session' => false) + array('require_previous_session' => false, 'post_only' => $postOnly) ); - $event = new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); - $listener->handle($event); } + public function postOnlyDataProvider() + { + return array( + array(true), + array(false), + ); + } + public function getUsernameForLength() { return array( diff --git a/src/Symfony/Component/Serializer/Annotation/Groups.php b/src/Symfony/Component/Serializer/Annotation/Groups.php index 432f7f9efcb57..7a9b0bd2c1052 100644 --- a/src/Symfony/Component/Serializer/Annotation/Groups.php +++ b/src/Symfony/Component/Serializer/Annotation/Groups.php @@ -34,13 +34,13 @@ class Groups public function __construct(array $data) { if (!isset($data['value']) || !$data['value']) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', get_class($this))); + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', \get_class($this))); } $value = (array) $data['value']; foreach ($value as $group) { - if (!is_string($group)) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', get_class($this))); + if (!\is_string($group)) { + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', \get_class($this))); } } diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 853e00eb2bbec..d71b21057704e 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -21,6 +21,7 @@ CHANGELOG * added getter for extra attributes in `ExtraAttributesException` * improved `CsvEncoder` to handle variable nested structures * CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable + * added `$context` when checking for encoding, decoding and normalizing in `Serializer` 3.3.0 ----- diff --git a/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php b/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php index 545841fffd48b..d4cfa4b11c9fa 100644 --- a/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php +++ b/src/Symfony/Component/Serializer/Encoder/ChainDecoder.php @@ -20,7 +20,7 @@ * @author Johannes M. Schmitt * @author Lukas Kahwe Smith * - * @final since version 3.3. + * @final */ class ChainDecoder implements ContextAwareDecoderInterface { diff --git a/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php b/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php index 2737f6eba695f..9745587c95802 100644 --- a/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/ChainEncoder.php @@ -20,7 +20,7 @@ * @author Johannes M. Schmitt * @author Lukas Kahwe Smith * - * @final since version 3.3. + * @final */ class ChainEncoder implements ContextAwareEncoderInterface { diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index b15b1946d7a5e..2fac8ce1bac6e 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -41,8 +41,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa /** * Construct new XmlEncoder and allow to change the root node element name. * - * @param string $rootNodeName - * @param int|null $loadOptions A bit field of LIBXML_* constants + * @param int|null $loadOptions A bit field of LIBXML_* constants */ public function __construct(string $rootNodeName = 'response', int $loadOptions = null) { @@ -245,17 +244,17 @@ private function parseXml(\DOMNode $node, array $context = array()) $value = $this->parseXmlValue($node, $context); - if (!count($data)) { + if (!\count($data)) { return $value; } - if (!is_array($value)) { + if (!\is_array($value)) { $data['#'] = $value; return $data; } - if (1 === count($value) && key($value)) { + if (1 === \count($value) && key($value)) { $data[key($value)] = current($value); return $data; @@ -310,7 +309,7 @@ private function parseXmlValue(\DOMNode $node, array $context = array()) return $node->nodeValue; } - if (1 === $node->childNodes->length && in_array($node->firstChild->nodeType, array(XML_TEXT_NODE, XML_CDATA_SECTION_NODE))) { + if (1 === $node->childNodes->length && \in_array($node->firstChild->nodeType, array(XML_TEXT_NODE, XML_CDATA_SECTION_NODE))) { return $node->firstChild->nodeValue; } @@ -335,7 +334,7 @@ private function parseXmlValue(\DOMNode $node, array $context = array()) } foreach ($value as $key => $val) { - if (is_array($val) && 1 === count($val)) { + if (\is_array($val) && 1 === \count($val)) { $value[$key] = current($val); } } @@ -346,7 +345,6 @@ private function parseXmlValue(\DOMNode $node, array $context = array()) /** * Parse the data and convert it to DOMElements. * - * @param \DOMNode $parentNode * @param array|object $data * * @throws NotEncodableValueException @@ -355,7 +353,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = { $append = true; - if (is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) { + if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) { foreach ($data as $key => $data) { //Ah this is the magic @ attribute types. if (0 === strpos($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) { @@ -365,7 +363,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = $parentNode->setAttribute($attributeName, $data); } elseif ('#' === $key) { $append = $this->selectNodeType($parentNode, $data); - } elseif (is_array($data) && false === is_numeric($key)) { + } elseif (\is_array($data) && false === is_numeric($key)) { // Is this array fully numeric keys? if (ctype_digit(implode('', array_keys($data)))) { /* @@ -389,7 +387,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = return $append; } - if (is_object($data)) { + if (\is_object($data)) { $data = $this->serializer->normalize($data, $this->format, $this->context); if (null !== $data && !is_scalar($data)) { return $this->buildXml($parentNode, $data, $xmlRootNodeName); @@ -412,7 +410,6 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = /** * Selects the type of node to create and appends it to the parent. * - * @param \DOMNode $parentNode * @param array|object $data */ private function appendNode(\DOMNode $parentNode, $data, string $nodeName, string $key = null): bool @@ -441,29 +438,28 @@ private function needsCdataWrapping(string $val): bool /** * Tests the value being passed and decide what sort of element to create. * - * @param \DOMNode $node - * @param mixed $val + * @param mixed $val * * @throws NotEncodableValueException */ private function selectNodeType(\DOMNode $node, $val): bool { - if (is_array($val)) { + if (\is_array($val)) { return $this->buildXml($node, $val); } elseif ($val instanceof \SimpleXMLElement) { $child = $this->dom->importNode(dom_import_simplexml($val), true); $node->appendChild($child); } elseif ($val instanceof \Traversable) { $this->buildXml($node, $val); - } elseif (is_object($val)) { + } elseif (\is_object($val)) { return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context)); } elseif (is_numeric($val)) { return $this->appendText($node, (string) $val); - } elseif (is_string($val) && $this->needsCdataWrapping($val)) { + } elseif (\is_string($val) && $this->needsCdataWrapping($val)) { return $this->appendCData($node, $val); - } elseif (is_string($val)) { + } elseif (\is_string($val)) { return $this->appendText($node, $val); - } elseif (is_bool($val)) { + } elseif (\is_bool($val)) { return $this->appendText($node, (int) $val); } elseif ($val instanceof \DOMNode) { $child = $this->dom->importNode($val, true); diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 64e3812465459..f0fd5f2a6ad48 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -59,7 +59,7 @@ public function getName() */ public function addGroup($group) { - if (!in_array($group, $this->groups)) { + if (!\in_array($group, $this->groups)) { $this->groups[] = $group; } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/LoaderChain.php b/src/Symfony/Component/Serializer/Mapping/Loader/LoaderChain.php index ef7e19cdea9c4..1890a9d84c530 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/LoaderChain.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/LoaderChain.php @@ -40,7 +40,7 @@ public function __construct(array $loaders) { foreach ($loaders as $loader) { if (!$loader instanceof LoaderInterface) { - throw new MappingException(sprintf('Class %s is expected to implement LoaderInterface', get_class($loader))); + throw new MappingException(sprintf('Class %s is expected to implement LoaderInterface', \get_class($loader))); } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php index 970241d34767f..ca9d43e15b793 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php @@ -30,7 +30,7 @@ class YamlFileLoader extends FileLoader * * @var array */ - private $classes = null; + private $classes; /** * {@inheritdoc} @@ -51,7 +51,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) $yaml = $this->classes[$classMetadata->getName()]; - if (isset($yaml['attributes']) && is_array($yaml['attributes'])) { + if (isset($yaml['attributes']) && \is_array($yaml['attributes'])) { $attributesMetadata = $classMetadata->getAttributesMetadata(); foreach ($yaml['attributes'] as $attribute => $data) { @@ -63,12 +63,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) } if (isset($data['groups'])) { - if (!is_array($data['groups'])) { + if (!\is_array($data['groups'])) { throw new MappingException(sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } foreach ($data['groups'] as $group) { - if (!is_string($group)) { + if (!\is_string($group)) { throw new MappingException(sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } @@ -119,7 +119,7 @@ private function getClassesFromYaml() return array(); } - if (!is_array($classes)) { + if (!\is_array($classes)) { throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file)); } diff --git a/src/Symfony/Component/Serializer/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/CamelCaseToSnakeCaseNameConverter.php index c433e098c7312..903dc6f43a8d3 100644 --- a/src/Symfony/Component/Serializer/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -36,7 +36,7 @@ public function __construct(array $attributes = null, bool $lowerCamelCase = tru */ public function normalize($propertyName) { - if (null === $this->attributes || in_array($propertyName, $this->attributes)) { + if (null === $this->attributes || \in_array($propertyName, $this->attributes)) { return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); } @@ -56,7 +56,7 @@ public function denormalize($propertyName) $camelCasedName = lcfirst($camelCasedName); } - if (null === $this->attributes || in_array($camelCasedName, $this->attributes)) { + if (null === $this->attributes || \in_array($camelCasedName, $this->attributes)) { return $camelCasedName; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index c43f0598a820a..0a9f2e9b2c9c4 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -121,7 +121,7 @@ public function setCircularReferenceHandler(callable $circularReferenceHandler) public function setCallbacks(array $callbacks) { foreach ($callbacks as $attribute => $callback) { - if (!is_callable($callback)) { + if (!\is_callable($callback)) { throw new InvalidArgumentException(sprintf( 'The given callback for attribute "%s" is not callable.', $attribute @@ -189,10 +189,10 @@ protected function isCircularReference($object, &$context) protected function handleCircularReference($object) { if ($this->circularReferenceHandler) { - return call_user_func($this->circularReferenceHandler, $object); + return \call_user_func($this->circularReferenceHandler, $object); } - throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', get_class($object), $this->circularReferenceLimit)); + throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit)); } /** @@ -211,7 +211,7 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu } $groups = false; - if (isset($context[static::GROUPS]) && is_array($context[static::GROUPS])) { + if (isset($context[static::GROUPS]) && \is_array($context[static::GROUPS])) { $groups = $context[static::GROUPS]; } elseif (!isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) || $context[static::ALLOW_EXTRA_ATTRIBUTES]) { return false; @@ -222,7 +222,7 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu $name = $attributeMetadata->getName(); if ( - (false === $groups || count(array_intersect($attributeMetadata->getGroups(), $groups))) && + (false === $groups || array_intersect($attributeMetadata->getGroups(), $groups)) && $this->isAllowedAttribute($classOrObject, $name, null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; @@ -326,11 +326,11 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref $paramName = $constructorParameter->name; $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName; - $allowed = false === $allowedAttributes || in_array($paramName, $allowedAttributes); + $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); if ($constructorParameter->isVariadic()) { if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) { - if (!is_array($data[$paramName])) { + if (!\is_array($data[$paramName])) { throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.', $class, $constructorParameter->name)); } diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php index af4771348193c..88424134466ca 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -22,7 +22,7 @@ * * @author Alexander M. Turek * - * @final since version 3.3. + * @final */ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, SerializerAwareInterface { diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index 3307fbec1a718..a6b8327be1ff9 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -74,7 +74,7 @@ private function supports(string $class): bool */ private function isGetMethod(\ReflectionMethod $method): bool { - $methodLength = strlen($method->name); + $methodLength = \strlen($method->name); return !$method->isStatic() && @@ -119,17 +119,17 @@ protected function getAttributeValue($object, $attribute, $format = null, array $ucfirsted = ucfirst($attribute); $getter = 'get'.$ucfirsted; - if (is_callable(array($object, $getter))) { + if (\is_callable(array($object, $getter))) { return $object->$getter(); } $isser = 'is'.$ucfirsted; - if (is_callable(array($object, $isser))) { + if (\is_callable(array($object, $isser))) { return $object->$isser(); } $haser = 'has'.$ucfirsted; - if (is_callable(array($object, $haser))) { + if (\is_callable(array($object, $haser))) { return $object->$haser(); } } @@ -140,10 +140,10 @@ protected function getAttributeValue($object, $attribute, $format = null, array protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array()) { $setter = 'set'.ucfirst($attribute); - $key = get_class($object).':'.$setter; + $key = \get_class($object).':'.$setter; if (!isset(self::$setterAccessibleCache[$key])) { - self::$setterAccessibleCache[$key] = is_callable(array($object, $setter)) && !(new \ReflectionMethod($object, $setter))->isStatic(); + self::$setterAccessibleCache[$key] = \is_callable(array($object, $setter)) && !(new \ReflectionMethod($object, $setter))->isStatic(); } if (self::$setterAccessibleCache[$key]) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index a92eb176d9c6f..614d6cd4a93a2 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -30,7 +30,7 @@ class ObjectNormalizer extends AbstractObjectNormalizer public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null) { - if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) { + if (!\class_exists(PropertyAccess::class)) { throw new RuntimeException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.'); } diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 3c73ec3b203ec..1a7087042567a 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -98,11 +98,11 @@ public function __construct(array $normalizers = array(), array $encoders = arra */ final public function serialize($data, $format, array $context = array()) { - if (!$this->supportsEncoding($format)) { + if (!$this->supportsEncoding($format, $context)) { throw new NotEncodableValueException(sprintf('Serialization for the format %s is not supported', $format)); } - if ($this->encoder->needsNormalization($format)) { + if ($this->encoder->needsNormalization($format, $context)) { $data = $this->normalize($data, $format, $context); } @@ -114,7 +114,7 @@ final public function serialize($data, $format, array $context = array()) */ final public function deserialize($data, $type, $format, array $context = array()) { - if (!$this->supportsDecoding($format)) { + if (!$this->supportsDecoding($format, $context)) { throw new NotEncodableValueException(sprintf('Deserialization for the format %s is not supported', $format)); } @@ -137,7 +137,7 @@ public function normalize($data, $format = null, array $context = array()) return $data; } - if (is_array($data) || $data instanceof \Traversable) { + if (\is_array($data) || $data instanceof \Traversable) { $normalized = array(); foreach ($data as $key => $val) { $normalized[$key] = $this->normalize($val, $format, $context); @@ -146,12 +146,12 @@ public function normalize($data, $format = null, array $context = array()) return $normalized; } - if (is_object($data)) { + if (\is_object($data)) { if (!$this->normalizers) { throw new LogicException('You must register at least one normalizer to be able to normalize objects.'); } - throw new NotNormalizableValueException(sprintf('Could not normalize object of type %s, no supporting normalizer found.', get_class($data))); + throw new NotNormalizableValueException(sprintf('Could not normalize object of type %s, no supporting normalizer found.', \get_class($data))); } throw new NotNormalizableValueException(sprintf('An unexpected value could not be normalized: %s', var_export($data, true))); diff --git a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php index 91704032cc2d5..fa12454fd094e 100644 --- a/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php +++ b/src/Symfony/Component/Serializer/Tests/DependencyInjection/SerializerPassTest.php @@ -23,48 +23,30 @@ */ class SerializerPassTest extends TestCase { + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage You must tag at least one service as "serializer.normalizer" to use the "serializer" service + */ public function testThrowExceptionWhenNoNormalizers() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds'))->getMock(); - - $container->expects($this->once()) - ->method('hasDefinition') - ->with('serializer') - ->will($this->returnValue(true)); - - $container->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('serializer.normalizer') - ->will($this->returnValue(array())); - - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(\RuntimeException::class); + $container = new ContainerBuilder(); + $container->register('serializer'); $serializerPass = new SerializerPass(); $serializerPass->process($container); } + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage You must tag at least one service as "serializer.encoder" to use the "serializer" service + */ public function testThrowExceptionWhenNoEncoders() { - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); - - $container->expects($this->once()) - ->method('hasDefinition') - ->with('serializer') - ->will($this->returnValue(true)); - - $container->expects($this->any()) - ->method('findTaggedServiceIds') - ->will($this->onConsecutiveCalls( - array('n' => array('serializer.normalizer')), - array() - )); - - $container->expects($this->any()) - ->method('getDefinition') - ->will($this->returnValue($definition)); - - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(\RuntimeException::class); + $container = new ContainerBuilder(); + $container->register('serializer') + ->addArgument(array()) + ->addArgument(array()); + $container->register('normalizer')->addTag('serializer.normalizer'); $serializerPass = new SerializerPass(); $serializerPass->process($container); diff --git a/src/Symfony/Component/Stopwatch/StopwatchEvent.php b/src/Symfony/Component/Stopwatch/StopwatchEvent.php index 410f935a269eb..de1259a9f17a1 100644 --- a/src/Symfony/Component/Stopwatch/StopwatchEvent.php +++ b/src/Symfony/Component/Stopwatch/StopwatchEvent.php @@ -150,7 +150,7 @@ public function getPeriods() /** * Gets the relative time of the start of the first period. * - * @return int The time (in milliseconds) + * @return int|float The time (in milliseconds) */ public function getStartTime() { @@ -160,7 +160,7 @@ public function getStartTime() /** * Gets the relative time of the end of the last period. * - * @return int The time (in milliseconds) + * @return int|float The time (in milliseconds) */ public function getEndTime() { @@ -172,7 +172,7 @@ public function getEndTime() /** * Gets the duration of the events (including all periods). * - * @return int The duration (in milliseconds) + * @return int|float The duration (in milliseconds) */ public function getDuration() { diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index d4eaedb3a9c55..b20d4909a3b32 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -123,36 +123,37 @@ private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, s $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); foreach ($xml->xpath('//xliff:unit') as $unit) { - $segment = $unit->segment; - $source = $segment->source; + foreach ($unit->segment as $segment) { + $source = $segment->source; - // If the xlf file has another encoding specified, try to convert it because - // simple_xml will always return utf-8 encoded values - $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding); + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding); - $catalogue->set((string) $source, $target, $domain); + $catalogue->set((string) $source, $target, $domain); - $metadata = array(); - if (isset($segment->target) && $segment->target->attributes()) { - $metadata['target-attributes'] = array(); - foreach ($segment->target->attributes() as $key => $value) { - $metadata['target-attributes'][$key] = (string) $value; + $metadata = array(); + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = array(); + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } } - } - if (isset($unit->notes)) { - $metadata['notes'] = array(); - foreach ($unit->notes->note as $noteNode) { - $note = array(); - foreach ($noteNode->attributes() as $key => $value) { - $note[$key] = (string) $value; + if (isset($unit->notes)) { + $metadata['notes'] = array(); + foreach ($unit->notes->note as $noteNode) { + $note = array(); + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; } - $note['content'] = (string) $noteNode; - $metadata['notes'][] = $note; } - } - $catalogue->setMetadata((string) $source, $metadata, $domain); + $catalogue->setMetadata((string) $source, $metadata, $domain); + } } } diff --git a/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationDumperPassTest.php b/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationDumperPassTest.php index 845200e71251a..3e02cb434edde 100644 --- a/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationDumperPassTest.php +++ b/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationDumperPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Translation\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass; @@ -19,48 +20,29 @@ class TranslationDumperPassTest extends TestCase { public function testProcess() { - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->disableOriginalConstructor()->getMock(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->disableOriginalConstructor()->getMock(); - - $container->expects($this->once()) - ->method('hasDefinition') - ->with('translation.writer') - ->will($this->returnValue(true)); - - $container->expects($this->once()) - ->method('getDefinition') - ->with('translation.writer') - ->will($this->returnValue($definition)); - - $valueTaggedServiceIdsFound = array( - 'foo.id' => array( - array('alias' => 'bar.alias'), - ), - ); - $container->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('translation.dumper', true) - ->will($this->returnValue($valueTaggedServiceIdsFound)); - - $definition->expects($this->once())->method('addMethodCall')->with('addDumper', array('bar.alias', new Reference('foo.id'))); + $container = new ContainerBuilder(); + $writerDefinition = $container->register('translation.writer'); + $container->register('foo.id') + ->addTag('translation.dumper', array('alias' => 'bar.alias')); $translationDumperPass = new TranslationDumperPass(); $translationDumperPass->process($container); + + $this->assertEquals(array(array('addDumper', array('bar.alias', new Reference('foo.id')))), $writerDefinition->getMethodCalls()); } public function testProcessNoDefinitionFound() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->disableOriginalConstructor()->getMock(); - - $container->expects($this->once()) - ->method('hasDefinition') - ->with('translation.writer') - ->will($this->returnValue(false)); + $container = new ContainerBuilder(); - $container->expects($this->never())->method('getDefinition'); - $container->expects($this->never())->method('findTaggedServiceIds'); + $definitionsBefore = count($container->getDefinitions()); + $aliasesBefore = count($container->getAliases()); $translationDumperPass = new TranslationDumperPass(); $translationDumperPass->process($container); + + // the container is untouched (i.e. no new definitions or aliases) + $this->assertCount($definitionsBefore, $container->getDefinitions()); + $this->assertCount($aliasesBefore, $container->getAliases()); } } diff --git a/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationExtractorPassTest.php b/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationExtractorPassTest.php index 76d2b999cfd83..b3313e63eeb42 100644 --- a/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationExtractorPassTest.php +++ b/src/Symfony/Component/Translation/Tests/DependencyInjection/TranslationExtractorPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Translation\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass; @@ -19,49 +20,30 @@ class TranslationExtractorPassTest extends TestCase { public function testProcess() { - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->disableOriginalConstructor()->getMock(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->disableOriginalConstructor()->getMock(); - - $container->expects($this->once()) - ->method('hasDefinition') - ->with('translation.extractor') - ->will($this->returnValue(true)); - - $container->expects($this->once()) - ->method('getDefinition') - ->with('translation.extractor') - ->will($this->returnValue($definition)); - - $valueTaggedServiceIdsFound = array( - 'foo.id' => array( - array('alias' => 'bar.alias'), - ), - ); - $container->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('translation.extractor', true) - ->will($this->returnValue($valueTaggedServiceIdsFound)); - - $definition->expects($this->once())->method('addMethodCall')->with('addExtractor', array('bar.alias', new Reference('foo.id'))); + $container = new ContainerBuilder(); + $extractorDefinition = $container->register('translation.extractor'); + $container->register('foo.id') + ->addTag('translation.extractor', array('alias' => 'bar.alias')); $translationDumperPass = new TranslationExtractorPass(); $translationDumperPass->process($container); + + $this->assertEquals(array(array('addExtractor', array('bar.alias', new Reference('foo.id')))), $extractorDefinition->getMethodCalls()); } public function testProcessNoDefinitionFound() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->disableOriginalConstructor()->getMock(); + $container = new ContainerBuilder(); - $container->expects($this->once()) - ->method('hasDefinition') - ->with('translation.extractor') - ->will($this->returnValue(false)); - - $container->expects($this->never())->method('getDefinition'); - $container->expects($this->never())->method('findTaggedServiceIds'); + $definitionsBefore = count($container->getDefinitions()); + $aliasesBefore = count($container->getAliases()); $translationDumperPass = new TranslationExtractorPass(); $translationDumperPass->process($container); + + // the container is untouched (i.e. no new definitions or aliases) + $this->assertCount($definitionsBefore, $container->getDefinitions()); + $this->assertCount($aliasesBefore, $container->getAliases()); } /** @@ -71,25 +53,10 @@ public function testProcessNoDefinitionFound() public function testProcessMissingAlias() { $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->disableOriginalConstructor()->getMock(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->disableOriginalConstructor()->getMock(); - - $container->expects($this->once()) - ->method('hasDefinition') - ->with('translation.extractor') - ->will($this->returnValue(true)); - - $container->expects($this->once()) - ->method('getDefinition') - ->with('translation.extractor') - ->will($this->returnValue($definition)); - - $valueTaggedServiceIdsFound = array( - 'foo.id' => array(), - ); - $container->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('translation.extractor', true) - ->will($this->returnValue($valueTaggedServiceIdsFound)); + $container = new ContainerBuilder(); + $container->register('translation.extractor'); + $container->register('foo.id') + ->addTag('translation.extractor', array()); $definition->expects($this->never())->method('addMethodCall'); diff --git a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php index a06b7c0990766..a8182895e07bf 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php @@ -228,4 +228,33 @@ public function testLoadVersion2WithNoteMeta() $this->assertEquals('quality', $metadata['notes'][1]['category']); $this->assertEquals('Fuzzy', $metadata['notes'][1]['content']); } + + public function testLoadVersion2WithMultiSegmentUnit() + { + $loader = new XliffFileLoader(); + $resource = __DIR__.'/../fixtures/resources-2.0-multi-segment-unit.xlf'; + $catalog = $loader->load($resource, 'en', 'domain1'); + + $this->assertSame('en', $catalog->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalog->getResources()); + $this->assertFalse(libxml_get_last_error()); + + // test for "foo" metadata + $this->assertTrue($catalog->defines('foo', 'domain1')); + $metadata = $catalog->getMetadata('foo', 'domain1'); + $this->assertNotEmpty($metadata); + $this->assertCount(1, $metadata['notes']); + + $this->assertSame('processed', $metadata['notes'][0]['category']); + $this->assertSame('true', $metadata['notes'][0]['content']); + + // test for "bar" metadata + $this->assertTrue($catalog->defines('bar', 'domain1')); + $metadata = $catalog->getMetadata('bar', 'domain1'); + $this->assertNotEmpty($metadata); + $this->assertCount(1, $metadata['notes']); + + $this->assertSame('processed', $metadata['notes'][0]['category']); + $this->assertSame('true', $metadata['notes'][0]['content']); + } } diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf b/src/Symfony/Component/Translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf new file mode 100644 index 0000000000000..d0dc2a8afe2f1 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/resources-2.0-multi-segment-unit.xlf @@ -0,0 +1,17 @@ + + + + + true + + + foo + foo (translated) + + + bar + bar (translated) + + + + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf index a33eb8239153b..dc6f95ff130fa 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Невалиден бизнес идентификационен код (BIC). + + Error + Грешка + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf index 24061e5b1a014..4b966698f7769 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Tato hodnota není platný identifikační kód podniku (BIC). + + Error + Chyba + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf index 14e479a59ad07..4f8556a3c17a0 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.da.xlf @@ -242,6 +242,10 @@ This value is not a valid ISSN. Værdien er ikke en gyldig ISSN. + + Error + Fejl + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf index 192b992f8f3ba..6e89e17ec3f1f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.de.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Dieser Wert ist kein gültiger BIC. + + Error + Fehler + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 2a5c97d8cf2d0..7e0e9614de50a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). This is not a valid Business Identifier Code (BIC). + + Error + Error + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index 1fa59dda6ad46..948594d03e91b 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). No es un Código de Identificación Bancaria (BIC) válido. + + Error + Error + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf index b2edefd30398b..d4125fdb0fbed 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.eu.xlf @@ -278,6 +278,10 @@ This value should not be identical to {{ compared_value_type }} {{ compared_value }}. Balio hau ez litzateke {{ compared_value_type }} {{ compared_value }}-(r)en berbera izan behar. + + Error + Errore + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf index 3f5a07e0921ae..e4390981dfda1 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fi.xlf @@ -222,6 +222,10 @@ Unsupported card type or invalid card number. Tätä korttityyppiä ei tueta tai korttinumero on virheellinen. + + Error + Virhe + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 30a883bbf239d..6c1b4030f3b0d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Ce n'est pas un code universel d'identification des banques (BIC) valide. + + Error + Erreur + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf index 8c62d899fdaec..126ef90332e30 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hr.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Ovo nije validan poslovni identifikacijski broj (BIC). + + Error + Greška + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf index a113a7241bc68..3a8a1db6eebd0 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.hu.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Érvénytelen nemzetközi bankazonosító kód (BIC/SWIFT). + + Error + Hiba + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf index 38e437d6cf978..bd71ece6aea49 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Questo valore non è un codice BIC valido. + + Error + Errore + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf index a556c45f0c42f..f2447500ecc3b 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.lt.xlf @@ -302,6 +302,10 @@ An empty file is not allowed. Failas negali būti tuščias. + + Error + Klaida + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf index e81141f0eed81..70ec678ec9a6f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf @@ -310,6 +310,10 @@ This is not a valid Business Identifier Code (BIC). Dit is geen geldige bedrijfsidentificatiecode (BIC/SWIFT). + + Error + Fout + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf index d364eba937e8d..f33274e6e6ce9 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Ta wartość nie jest poprawnym kodem BIC (Business Identifier Code). + + Error + Błąd + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf index d563c921cb90b..0561c048f512c 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf @@ -302,6 +302,10 @@ An empty file is not allowed. Ficheiro vazio não é permitido. + + Error + Erro + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf index 27346a9f17718..63af47042b199 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ro.xlf @@ -278,6 +278,10 @@ This value should not be identical to {{ compared_value_type }} {{ compared_value }}. Această valoare nu trebuie să fie identică cu {{ compared_value_type }} {{ compared_value }}. + + Error + Eroare + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf index d7a90c907ed08..de7edfe8dc5ab 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf @@ -310,6 +310,10 @@ This value does not match the expected {{ charset }} charset. Значение не совпадает с ожидаемой {{ charset }} кодировкой. + + Error + Ошибка + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf index 879c0a5c559d0..fa043ea23a309 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.sv.xlf @@ -314,6 +314,10 @@ This is not a valid Business Identifier Code (BIC). Detta är inte en giltig BIC-kod. + + Error + Fel + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf new file mode 100644 index 0000000000000..75dc329589730 --- /dev/null +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tl.xlf @@ -0,0 +1,319 @@ + + + + + + This value should be false. + Ang halaga nito ay dapat na huwad. + + + This value should be true. + Ang halaga nito ay dapat totoo. + + + This value should be of type {{ type }}. + Ang halaga nito ay dapat sa uri {{ type }}. + + + This value should be blank. + Ang halaga nito ay dapat walang laman. + + + The value you selected is not a valid choice. + Ang halaga ng iyong pinili ay hindi balidong pagpili. + + + You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices. + Kailangan mong pumili ng pinakamababang {{ limit }} ng pagpilian.|Kailangan mong pumili ng pinakamababang {{ limit }} ng mga pagpipilian. + + + You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices. + Kailangan mong pumili ng pinakamataas {{ limit }} ng pagpipilian.|Kailangan mong pumili ng pinakamataas {{ limit }} ng mga pagpipilian. + + + One or more of the given values is invalid. + Isa o higit pang mga halaga na binigay ay hindi balido. + + + This field was not expected. + Ang larangang ito ay hindi inaasahan. + + + This field is missing. + Ang patlang na ito ay nawawala. + + + This value is not a valid date. + Ang halagang ito ay hindi balidong petsa. + + + This value is not a valid datetime. + Ang halagang ito ay hindi wastong petsa/oras. + + + This value is not a valid email address. + Ang halagang ito ay hindi balidong address ng email. + + + The file could not be found. + Ang file na ito ay hindi makita. + + + The file is not readable. + Ang file na ito ay hindi mabasa. + + + The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}. + Ang file na ito ay masyadong malaki ({{ size }} {{ suffix }}). Ang pinakamalaking sukat {{ limit }} {{ suffix }}. + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}. + Ang uri ng file ng mime ay hindi balido ({{ type }}).Ang mga pinapayagang uri ng mime ay ang {{ types }}. + + + This value should be {{ limit }} or less. + Ang halaga nito ay dapat na {{ limit }} or maliit pa. + + + This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. + Ang halaga nito ay masyadong mahaba.Ito ay dapat na {{ limit }} karakter o maliit pa.|Ang halaga nito ay masyadong mahaba. Ito ay dapat na {{ limit }} mga karakter o maliit pa. + + + This value should be {{ limit }} or more. + Ang halaga nito ay dapat na {{ limit }} o mas marami pa. + + + This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. + Ang halaga nito ay masyadong maliit. Ito ay dapat na {{ limit }} karakter o marami pa.|Ang halaga nito ay masyadong maliit. Ito ay dapat na {{ limit }} mga karakter o marami pa. + + + This value should not be blank. + Ang halaga na ito ay dapat na may laman. + + + This value should not be null. + Meron dapt itong halaga. + + + This value should be null. + Wala dapat itong halaga. + + + This value is not valid. + Hindi balido ang halagang ito. + + + This value is not a valid time. + Ang halagang ito ay hindi wastong oras. + + + This value is not a valid URL. + Hindi ito isang balidong URL. + + + The two values should be equal. + Ang dalwang halaga ay dapat magkapareha. + + + The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}. + Ang file ay masyadong malaki. Ang pinapayagan halaga lamang ay {{ limit}} {{ suffix }}. + + + The file is too large. + Ang file na ito ay masyadong malaki. + + + The file could not be uploaded. + Ang file na ito ay hindi ma-upload. + + + This value should be a valid number. + Ang halaga nito ay dapat na wastong numero. + + + This file is not a valid image. + Ang file na ito ay hindi wastong imahe. + + + This is not a valid IP address. + Ito ay hindi wastong IP address. + + + This value is not a valid language. + Ang halaga na ito ay hindi balidong wika. + + + This value is not a valid locale. + Ito ay isang hindi wastong locale na halaga. + + + This value is not a valid country. + ng halaga na ito ay hindi wastong bansa. + + + This value is already used. + Ang halaga na ito ay ginamit na. + + + The size of the image could not be detected. + Ang sukat ng imahe ay hindi madetect. + + + The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px. + Ang lapad ng imahe ay masyadong malaki ({{ width }}px). Ang pinapayagang lapay ay {{ max_width }}px. + + + The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px. + Ang lapad ng imahe ay masyadong maliit ({{ width }}px). Ang pinakamaliit na pinapayagang lapad ay {{ min_width }}px. + + + The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px. + Ang haba ng imahe ay masyadong mataas ({{ height }}px). Ang pinakmataas na haba ay {{ max_height }}px. + + + The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px. + Ang haba ng imahe ay masyadong maliit ({{ height }}px). Ang inaasahang haba ay {{ min_height }}px. + + + This value should be the user's current password. + Ang halagang ito ay dapat na password ng kasalukuyang gumagamit. + + + This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. + Ang halagang ito ay dapat na eksakto sa {{ limit}} karakter.|Ang halagang ito ay dapat na eksakto sa {{ limit }} mga karakter. + + + The file was only partially uploaded. + Ang file na ito ay kahalating na upload lamang. + + + No file was uploaded. + Walang na upload na file. + + + No temporary folder was configured in php.ini. + Walang temporaryong folder ang naayos sa php.ini. + + + Cannot write temporary file to disk. + Temporaryong hindi makasulat ng file sa disk. + + + A PHP extension caused the upload to fail. + Ang dahilan ng pagkabigo ng pagupload ng files ay isang extension ng PHP. + + + This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more. + Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} elemento o marami pa.|Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} mga elemento o marami pa. + + + This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less. + Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} elemento o maliit pa.|Ang koleksyon na ito ay dapat magkaroon ng {{ limit }} mga elemento o maliit pa. + + + This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements. + Ang koleksyong ito ay magkaroon ng eksaktong {{ limit }} elemento.|Ang koleksyong ito ay magkaroon ng eksaktong {{ limit }} mga elemento. + + + Invalid card number. + Hindi wastong numero ng kard. + + + Unsupported card type or invalid card number. + Hindi supportadong uri ng kard o hindi wastong numero ng kard. + + + This is not a valid International Bank Account Number (IBAN). + Ito ay hindi isang balidong International Bank Account Number (IBAN). + + + This value is not a valid ISBN-10. + Ang halagang ito ay hindi balidong SBN-10. + + + This value is not a valid ISBN-13. + Ang halagang ito ay hindi balidong ISBN-13. + + + This value is neither a valid ISBN-10 nor a valid ISBN-13. + Ang halagang ito ay pwdeng isang balidong ISBN-10 o isang balidong ISBN-13. + + + This value is not a valid ISSN. + Ang halangang ito ay hindi isang balidong ISSN. + + + This value is not a valid currency. + Ang halagang ito ay hindi balidong pera. + + + This value should be equal to {{ compared_value }}. + Ito ay hindi dapat magkapareho sa {{ compared_value }}. + + + This value should be greater than {{ compared_value }}. + Ang halagang ito ay dapat tataas sa {{ compared_value }}. + + + This value should be greater than or equal to {{ compared_value }}. + Ang halagang ito ay dapat mas mataas o magkapareha sa {{ compared_value }}. + + + This value should be identical to {{ compared_value_type }} {{ compared_value }}. + Ang halagang ito ay dapat kapareha ng {{ compared_value_type }} {{ compared_value }}. + + + This value should be less than {{ compared_value }}. + Ang halagang ito ay dapat mas maliit sa {{ compared_value }}. + + + This value should be less than or equal to {{ compared_value }}. + Ang halagang ito ay dapat mas mmaliit o magkapareha sa {{ compared_value }}. + + + This value should not be equal to {{ compared_value }}. + Ang halagang ito ay hindi dapat magkapareha sa {{ compared_value }}. + + + This value should not be identical to {{ compared_value_type }} {{ compared_value }}. + Ang halagang ito ay hindi dapat magkapareha sa {{ compared_value_type }} {{ compared_value }}. + + + The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}. + Ang ratio ng imahe ay masyadong malaki ({{ ratio }}). Ang pinakamalaking ratio ay {{ max_ratio }}. + + + The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}. + ng ratio ng imahe ay masyadong maliit ({{ ratio }}). Ang pinamaliit na ratio ay {{ min_ratio }}. + + + The image is square ({{ width }}x{{ height }}px). Square images are not allowed. + Ang imahe ay kwadrado ({{ width }}x{{ height }}px). Ang mga kwadradong imahe ay hindi pwede. + + + The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. + Ang orientasyon ng imahe ay nakalandscape ({{ width }}x{{ height }}px). Ang mga imaheng nakalandscape ang orientasyon ay hindi pwede. + + + The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. + Ang orientasyon ng imahe ay nakaportrait ({{ width }}x{{ height }}px). PAng mga imaheng nakaportrait ang orientasyon ay hindi pwede. + + + An empty file is not allowed. + Ang file na walang laman ay hindi pwede. + + + The host could not be resolved. + Hindi maresolba ang host. + + + This value does not match the expected {{ charset }} charset. + Ang halaga ay hindi kapareha sa inaasahang {{ charset }} set ng karater. + + + This is not a valid Business Identifier Code (BIC). + Ito ay hindi isang balidong Business Identifier Code (BIC). + + + + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf index a7906eaae25af..5e19e3e5a3c66 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf @@ -222,6 +222,10 @@ Unsupported card type or invalid card number. Desteklenmeyen kart tipi veya geçersiz kart numarası. + + Error + Hata + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf index 02ecd5a687c93..a2235634bdc44 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.uk.xlf @@ -278,6 +278,10 @@ This value should not be identical to {{ compared_value_type }} {{ compared_value }}. Значення не повинно бути ідентичним {{ compared_value_type }} {{ compared_value }}. + + Error + Помилка + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf index 6c95ef1a20dda..d4ed03ded1916 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf @@ -310,6 +310,10 @@ This value does not match the expected {{ charset }} charset. 该值不符合 {{ charset }} 编码。 + + Error + 错误 + diff --git a/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php b/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php index 92091484e2905..3782fba46c3b1 100644 --- a/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php +++ b/src/Symfony/Component/Validator/Tests/ContainerConstraintValidatorFactoryTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Blank as BlankConstraint; use Symfony\Component\Validator\ConstraintValidator; @@ -23,16 +22,8 @@ class ContainerConstraintValidatorFactoryTest extends TestCase { public function testGetInstanceCreatesValidator() { - $class = get_class($this->getMockForAbstractClass(ConstraintValidator::class)); - - $constraint = $this->getMockBuilder(Constraint::class)->getMock(); - $constraint - ->expects($this->once()) - ->method('validatedBy') - ->will($this->returnValue($class)); - $factory = new ContainerConstraintValidatorFactory(new Container()); - $this->assertInstanceOf($class, $factory->getInstance($constraint)); + $this->assertInstanceOf(DummyConstraintValidator::class, $factory->getInstance(new DummyConstraint())); } public function testGetInstanceReturnsExistingValidator() @@ -45,30 +36,13 @@ public function testGetInstanceReturnsExistingValidator() public function testGetInstanceReturnsService() { - $service = 'validator_constraint_service'; - $validator = $this->getMockForAbstractClass(ConstraintValidator::class); - - // mock ContainerBuilder b/c it implements TaggedContainerInterface - $container = $this->getMockBuilder(ContainerBuilder::class)->setMethods(array('get', 'has'))->getMock(); - $container - ->expects($this->once()) - ->method('get') - ->with($service) - ->willReturn($validator); - $container - ->expects($this->once()) - ->method('has') - ->with($service) - ->willReturn(true); - - $constraint = $this->getMockBuilder(Constraint::class)->getMock(); - $constraint - ->expects($this->once()) - ->method('validatedBy') - ->will($this->returnValue($service)); + $validator = new DummyConstraintValidator(); + $container = new Container(); + $container->set(DummyConstraintValidator::class, $validator); $factory = new ContainerConstraintValidatorFactory($container); - $this->assertSame($validator, $factory->getInstance($constraint)); + + $this->assertSame($validator, $factory->getInstance(new DummyConstraint())); } /** @@ -86,3 +60,18 @@ public function testGetInstanceInvalidValidatorClass() $factory->getInstance($constraint); } } + +class DummyConstraint extends Constraint +{ + public function validatedBy() + { + return DummyConstraintValidator::class; + } +} + +class DummyConstraintValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint) + { + } +} diff --git a/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php b/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php index ba4ea36b2b8c3..05167e84ca17e 100644 --- a/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php +++ b/src/Symfony/Component/Validator/Tests/DependencyInjection/AddConstraintValidatorsPassTest.php @@ -50,7 +50,7 @@ public function testThatConstraintValidatorServicesAreProcessed() public function testAbstractConstraintValidator() { $container = new ContainerBuilder(); - $validatorFactory = $container->register('validator.validator_factory') + $container->register('validator.validator_factory') ->addArgument(array()); $container->register('my_abstract_constraint_validator') @@ -63,18 +63,16 @@ public function testAbstractConstraintValidator() public function testThatCompilerPassIsIgnoredIfThereIsNoConstraintValidatorFactoryDefinition() { - $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $container = new ContainerBuilder(); - $container->expects($this->never())->method('findTaggedServiceIds'); - $container->expects($this->never())->method('getDefinition'); - $container->expects($this->atLeastOnce()) - ->method('hasDefinition') - ->with('validator.validator_factory') - ->will($this->returnValue(false)); - $definition->expects($this->never())->method('replaceArgument'); + $definitionsBefore = count($container->getDefinitions()); + $aliasesBefore = count($container->getAliases()); $addConstraintValidatorsPass = new AddConstraintValidatorsPass(); $addConstraintValidatorsPass->process($container); + + // the container is untouched (i.e. no new definitions or aliases) + $this->assertCount($definitionsBefore, $container->getDefinitions()); + $this->assertCount($aliasesBefore, $container->getAliases()); } } diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 4e17d62693d5d..f325321234030 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -215,7 +215,7 @@ public function withRefHandles($useRefHandles) * * @param string|int $key The key to seek to * - * @return self|null A clone of $this of null if the key is not set + * @return self|null A clone of $this or null if the key is not set */ public function seek($key) { diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index 1ec4998553164..2faf54372f714 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -497,7 +497,8 @@ protected function supportsColors() if ('\\' === DIRECTORY_SEPARATOR) { static::$defaultColors = @( - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD + function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support($this->outputStream) + || '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM') diff --git a/src/Symfony/Component/Workflow/Tests/DependencyInjection/ValidateWorkflowsPassTest.php b/src/Symfony/Component/Workflow/Tests/DependencyInjection/ValidateWorkflowsPassTest.php index d728cc1195570..1a38588f27cdb 100644 --- a/src/Symfony/Component/Workflow/Tests/DependencyInjection/ValidateWorkflowsPassTest.php +++ b/src/Symfony/Component/Workflow/Tests/DependencyInjection/ValidateWorkflowsPassTest.php @@ -4,7 +4,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\Workflow\Definition; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\Workflow\Definition as WorkflowDefinition; use Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass; use Symfony\Component\Workflow\Transition; @@ -12,19 +13,20 @@ class ValidateWorkflowsPassTest extends TestCase { public function testProcess() { - $container = $this->getMockBuilder(ContainerBuilder::class)->getMock(); - $container - ->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('workflow.definition') - ->willReturn(array('definition1' => array('workflow.definition' => array('name' => 'wf1', 'type' => 'state_machine', 'marking_store' => 'foo')))); - - $container - ->expects($this->once()) - ->method('get') - ->with('definition1') - ->willReturn(new Definition(array('a', 'b', 'c'), array(new Transition('t1', 'a', 'b'), new Transition('t2', 'a', 'c')))); + $container = new ContainerBuilder(); + $container->register('definition1', WorkflowDefinition::class) + ->addArgument(array('a', 'b', 'c')) + ->addArgument(array( + new Definition(Transition::class, array('t1', 'a', 'b')), + new Definition(Transition::class, array('t2', 'a', 'c')), + )) + ->addTag('workflow.definition', array('name' => 'wf1', 'type' => 'state_machine', 'marking_store' => 'foo')); (new ValidateWorkflowsPass())->process($container); + + $workflowDefinition = $container->get('definition1'); + + $this->assertSame(array('a' => 'a', 'b' => 'b', 'c' => 'c'), $workflowDefinition->getPlaces()); + $this->assertEquals(array(new Transition('t1', 'a', 'b'), new Transition('t2', 'a', 'c')), $workflowDefinition->getTransitions()); } } diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index 254009733eac8..d47daa49c7b98 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class Dumper { @@ -63,7 +63,10 @@ public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): foreach ($input as $key => $value) { if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r\n")) { - $output .= sprintf("%s%s%s |\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', ''); + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator); foreach (preg_split('/\n|\r\n/', $value) as $row) { $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 195d3185e4d76..3bfd352cd50a5 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -19,7 +19,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class Parser { @@ -372,54 +372,39 @@ private function doParse(string $value, int $flags) // try to parse the value as a multi-line string as a last resort if (0 === $this->currentLineNb) { - $parseError = false; $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = false; $value = ''; foreach ($this->lines as $line) { - try { - if (isset($line[0]) && ('"' === $line[0] || "'" === $line[0])) { - $parsedLine = $line; - } else { - $parsedLine = Inline::parse($line, $flags, $this->refs); - } - - if (!is_string($parsedLine)) { - $parseError = true; - break; - } - - if ('' === trim($parsedLine)) { - $value .= "\n"; - } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { - $value .= ' '; - } + if ('' === trim($line)) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } - if ('' !== trim($parsedLine) && '\\' === substr($parsedLine, -1)) { - $value .= ltrim(substr($parsedLine, 0, -1)); - } elseif ('' !== trim($parsedLine)) { - $value .= trim($parsedLine); - } + if ('' !== trim($line) && '\\' === substr($line, -1)) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== trim($line)) { + $value .= trim($line); + } - if ('' === trim($parsedLine)) { - $previousLineWasNewline = true; - $previousLineWasTerminatedWithBackslash = false; - } elseif ('\\' === substr($parsedLine, -1)) { - $previousLineWasNewline = false; - $previousLineWasTerminatedWithBackslash = true; - } else { - $previousLineWasNewline = false; - $previousLineWasTerminatedWithBackslash = false; - } - } catch (ParseException $e) { - $parseError = true; - break; + if ('' === trim($line)) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === substr($line, -1)) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; } } - if (!$parseError) { + try { return Inline::parse(trim($value)); + } catch (ParseException $e) { + // fall-through to the ParseException thrown below } } diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index edcb7344409f8..0a8c023d017fe 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -388,6 +388,17 @@ public function testDumpMultiLineStringAsScalarBlock() $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); } + public function testDumpMultiLineStringAsScalarBlockWhenFirstLineHasLeadingSpace() + { + $data = array( + 'data' => array( + 'multi_line' => " the first line has leading spaces\nThe second line does not.", + ), + ); + + $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + } + public function testCarriageReturnIsMaintainedWhenDumpingAsMultiLineLiteralBlock() { $this->assertSame("- \"a\\r\\nb\\nc\"\n", $this->dumper->dump(array("a\r\nb\nc"), 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml new file mode 100644 index 0000000000000..3f2dedd10e682 --- /dev/null +++ b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml @@ -0,0 +1,4 @@ +data: + multi_line: |4 + the first line has leading spaces + The second line does not. diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index fe3f07986028b..cd9085a76e058 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -672,6 +672,42 @@ public function testSequenceFollowedByCommentEmbeddedInMapping() $this->assertSame($expected, $this->parser->parse($yaml)); } + public function testNonStringFollowedByCommentEmbeddedInMapping() + { + $yaml = <<<'EOT' +a: + b: + {} +# comment + d: + 1.1 +# another comment +EOT; + $expected = array( + 'a' => array( + 'b' => array(), + 'd' => 1.1, + ), + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + + public function testMultiLineStringLastResortParsing() + { + $yaml = <<<'EOT' +test: + You can have things that don't look like strings here + true + yes you can +EOT; + $expected = array( + 'test' => 'You can have things that don\'t look like strings here true yes you can', + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + /** * @expectedException \Symfony\Component\Yaml\Exception\ParseException */ diff --git a/src/Symfony/Component/Yaml/Yaml.php b/src/Symfony/Component/Yaml/Yaml.php index 0a97c2198a3fc..80de7e877723c 100644 --- a/src/Symfony/Component/Yaml/Yaml.php +++ b/src/Symfony/Component/Yaml/Yaml.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class Yaml {