diff --git a/CHANGELOG-3.3.md b/CHANGELOG-3.3.md index 310284444011e..eeb42846f5f5e 100644 --- a/CHANGELOG-3.3.md +++ b/CHANGELOG-3.3.md @@ -7,6 +7,41 @@ in 3.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.3.0...v3.3.1 +* 3.3.14 (2017-12-04) + + * bug #25304 [Bridge/PhpUnit] Prefer $_SERVER['argv'] over $argv (ricknox) + * bug #25308 [FrameworkBundle] Fix a bug where a color tag will be shown when passing an antislash (Simperfit) + * bug #25278 Fix for missing whitespace control modifier in form layout (kubawerlos) + * bug #25305 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25236 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25312 [DI] Fix deep-inlining of non-shared refs (nicolas-grekas) + * bug #25309 [Yaml] parse newlines in quoted multiline strings (xabbuh) + * bug #25241 [Yaml] do not eagerly filter comment lines (xabbuh) + * bug #25258 [link] Prevent warnings when running link with 2.7 (dunglas) + * bug #24750 [Validator] ExpressionValidator should use OBJECT_TO_STRING (Simperfit) + * bug #25232 [WebProfilerBundle] [TwigBundle] Fix Profiler breaking XHTML pages (tistre) + * bug #25209 [VarDumper] Dont use empty(), it chokes on eg GMP objects (nicolas-grekas) + * bug #25200 [HttpKernel] Arrays with scalar values passed to ESI fragment renderer throw deprecation notice (Simperfit) + * bug #25217 [Dotenv] Changed preg_match flags from null to 0 (deekthesqueak) + * bug #25203 [DI] Fix infinite loop in InlineServiceDefinitionsPass (nicolas-grekas) + * bug #25185 [Serializer] Do not cache attributes if `attributes` in context (sroze) + * bug #25182 [HttpFoundation] AutExpireFlashBag should not clear new flashes (Simperfit, sroze) + * bug #25179 [FrameworkBundle][Serializer] Remove YamlEncoder definition if Yaml component isn't installed (ogizanagi) + * bug #25163 [DI] Fix tracking of env vars in exceptions (nicolas-grekas) + * bug #25152 [Form] Don't rely on `Symfony\Component\HttpFoundation\File\File` if http-foundation isn't in FileType (issei-m) + * bug #24987 [Console] Fix global console flag when used in chain (Simperfit) + * bug #25146 [DI] Dont resolve envs in service ids (nicolas-grekas) + * bug #25113 [Routing] Fix "config-file-relative" annotation loader resources (nicolas-grekas, sroze) + * bug #25109 Make debug:container search command case-insensitive (jzawadzki) + * bug #25043 [Yaml] added ability for substitute aliases when mapping is on single line (Michał Strzelecki, xabbuh) + * bug #25102 [Form] Fixed ContextErrorException in FileType (chihiro-adachi) + * bug #25130 [DI] Fix handling of inlined definitions by ContainerBuilder (nicolas-grekas) + * bug #25094 [FrameworkBundle][DX] Display a nice error message if an enabled component is missing (derrabus) + * bug #25097 [Bridge\PhpUnit] Turn "preserveGlobalState" to false by default, revert "Blacklist" removal (nicolas-grekas) + * bug #25072 [Bridge/PhpUnit] Remove trailing "\n" from ClockMock::microtime(false) (joky) + * bug #25032 [Bridge\PhpUnit] Disable broken auto-require mechanism of phpunit (nicolas-grekas) + * bug #24956 Fix ambiguous pattern (weltling) + * 3.3.13 (2017-11-16) * security #24995 Validate redirect targets using the session cookie domain (nicolas-grekas) diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md index 7be69fd18959c..24620ba3a50fb 100644 --- a/CHANGELOG-3.4.md +++ b/CHANGELOG-3.4.md @@ -7,6 +7,40 @@ in 3.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1 +* 3.4.2 (2017-12-15) + + * bug #25489 [FrameworkBundle] remove esi/ssi renderers if inactive (dmaicher) + * bug #25502 Fixing wrong class_exists on interface (weaverryan) + * bug #25427 Preserve percent-encoding in URLs when performing redirects in the UrlMatcher (mpdude) + * bug #25480 [FrameworkBundle] add missing validation options to XSD file (xabbuh) + * bug #25487 [Console] Fix a bug when passing a letter that could be an alias (Simperfit) + * bug #25425 When available use AnnotationRegistry::registerUniqueLoader (jrjohnson) + * bug #25474 [DI] Optimize Container::get() for perf (nicolas-grekas) + * bug #24594 [Translation] Fix InvalidArgumentException when using untranslated plural forms from .po files (BjornTwachtmann) + * bug #25233 [TwigBridge][Form] Fix hidden currency element with Bootstrap 3 theme (julienfalque) + * bug #25413 [HttpKernel] detect deprecations thrown by container initialization during tests (nicolas-grekas) + * bug #25408 [Debug] Fix catching fatal errors in case of nested error handlers (nicolas-grekas) + * bug #25330 [HttpFoundation] Support 0 bit netmask in IPv6 (`::/0`) (stephank) + * bug #25378 [VarDumper] Fixed file links leave blank pages when ide is configured (antalaron) + * bug #25410 [HttpKernel] Fix logging of post-terminate errors/exceptions (nicolas-grekas) + * bug #25417 [Process] Dont rely on putenv(), it fails on ZTS PHP (nicolas-grekas) + * bug #25333 [DI] Impossible to set an environment variable and then an array as container parameter (Phantas0s) + * bug #25447 [Process] remove false-positive BC breaking exception on Windows (nicolas-grekas) + * bug #25381 [DI] Add context to service-not-found exceptions thrown by service locators (nicolas-grekas) + * bug #25438 [Yaml] empty lines don't count for indent detection (xabbuh) + * bug #25412 Extend Argon2i support check to account for sodium_compat (mbabker) + * bug #25389 [Yaml] fix some edge cases with indented blocks (xabbuh) + * bug #25396 [Form] Fix debug:form command definition (yceruto) + * bug #25398 [HttpFoundation] don't prefix cookies with "Set-Cookie:" (pableu) + * bug #25354 [DI] Fix non-string class handling in PhpDumper (nicolas-grekas, sroze) + * bug #25340 [Serializer] Unset attributes when creating child context (dunglas) + * bug #25325 [Yaml] do not evaluate PHP constant names (xabbuh) + * bug #25380 [FrameworkBundle][Cache] register system cache clearer only if it's used (xabbuh) + * bug #25323 [ExpressionLanguage] throw an SyntaxError instead of an undefined index notice (Simperfit) + * bug #25363 [HttpKernel] Disable inlining on PHP 5 (nicolas-grekas) + * bug #25364 [DependencyInjection] Prevent a loop in aliases within the `findDefinition` method (sroze) + * bug #25337 Remove Exclusive Lock That Breaks NFS Caching (brianfreytag) + * 3.4.1 (2017-12-04) * bug #25304 [Bridge/PhpUnit] Prefer $_SERVER['argv'] over $argv (ricknox) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0f3e9c22adf2d..30d8ae3f828ed 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -14,8 +14,8 @@ Symfony is the result of the work of many people who made the code better - Victor Berchet (victor) - Johannes S (johannes) - Jakub Zalas (jakubzalas) - - Kris Wallsmith (kriswallsmith) - Kévin Dunglas (dunglas) + - Kris Wallsmith (kriswallsmith) - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) - Maxime Steinhausser (ogizanagi) @@ -29,8 +29,8 @@ Symfony is the result of the work of many people who made the code better - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) - - Martin Hasoň (hason) - Roland Franssen (ro0) + - Martin Hasoň (hason) - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) @@ -52,24 +52,24 @@ Symfony is the result of the work of many people who made the code better - Peter Rehm (rpet) - Saša Stamenković (umpirsky) - Henrik Bjørnskov (henrikbjorn) + - Yonel Ceruto (yonelceruto) - Miha Vrhovnik - Matthias Pigulla (mpdude) - Diego Saint Esteben (dii3g0) - Konstantin Kudryashov (everzet) - Bilal Amarni (bamarni) - - Yonel Ceruto (yonelceruto) - - Dany Maillard (maidmaid) - Kevin Bond (kbond) + - Dany Maillard (maidmaid) + - Pierre du Plessis (pierredup) - Florin Patan (florinpatan) - Jérémy DERUSSÉ (jderusse) - - Pierre du Plessis (pierredup) - Gábor Egyed (1ed) - Michel Weimerskirch (mweimerskirch) - Andrej Hudec (pulzarraider) + - Alexander M. Turek (derrabus) - Eric Clemmons (ericclemmons) - Jáchym Toušek (enumag) - Charles Sarrazin (csarrazi) - - Alexander M. Turek (derrabus) - Konstantin Myakshin (koc) - Christian Raue - Arnout Boks (aboks) @@ -79,6 +79,7 @@ Symfony is the result of the work of many people who made the code better - Titouan Galopin (tgalopin) - Douglas Greenshields (shieldo) - Tobias Nyholm (tobias) + - Issei Murasawa (issei_m) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) @@ -91,13 +92,14 @@ Symfony is the result of the work of many people who made the code better - John Wards (johnwards) - Dariusz Ruminski - Fran Moreno (franmomu) - - Issei Murasawa (issei_m) - Antoine Hérault (herzult) - Paráda József (paradajozsef) - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER - Michal Piotrowski (eventhorizon) + - Samuel ROZE (sroze) - Tim Nagel (merk) + - Amrouche Hamza (simperfit) - Brice BERNARD (brikou) - Baptiste Clavié (talus) - Vladimir Reznichenko (kalessil) @@ -126,6 +128,7 @@ Symfony is the result of the work of many people who made the code better - Sebastiaan Stok (sstok) - Stefano Sala (stefano.sala) - Evgeniy (ewgraf) + - Grégoire Paris (greg0ire) - Vincent AUBERT (vincent) - Juti Noppornpitak (shiroyuki) - Tigran Azatyan (tigranazatyan) @@ -135,7 +138,6 @@ Symfony is the result of the work of many people who made the code better - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) - - Grégoire Paris (greg0ire) - Andréia Bohner (andreia) - Rafael Dohms (rdohms) - Arnaud Kleinpeter (nanocom) @@ -149,6 +151,7 @@ Symfony is the result of the work of many people who made the code better - Vyacheslav Pavlov - Richard van Laak (rvanlaak) - Javier Spagnoletti (phansys) + - Julien Falque (julienfalque) - Richard Shank (iampersistent) - Thomas Rabaix (rande) - Rouven Weßling (realityking) @@ -160,7 +163,6 @@ Symfony is the result of the work of many people who made the code better - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) - Dawid Nowak - - Julien Falque (julienfalque) - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) - Artur Kotyrba @@ -171,8 +173,6 @@ Symfony is the result of the work of many people who made the code better - Warnar Boekkooi (boekkooi) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) - - Amrouche Hamza - - Samuel ROZE (sroze) - Daniel Espendiller - Possum - Dorian Villet (gnutix) @@ -190,6 +190,7 @@ Symfony is the result of the work of many people who made the code better - Stepan Anchugov (kix) - bronze1man - sun (sun) + - Valentin Udaltsov (vudaltsov) - Larry Garfield (crell) - Martin Schuhfuß (usefulthink) - apetitpa @@ -248,7 +249,6 @@ Symfony is the result of the work of many people who made the code better - Pierre-Yves LEBECQ (pylebecq) - Jordan Samouh (jordansamouh) - Jakub Kucharovic (jkucharovic) - - Valentin Udaltsov (vudaltsov) - Uwe Jäger (uwej711) - Eugene Leonovich (rybakit) - Filippo Tessarotto @@ -261,6 +261,7 @@ Symfony is the result of the work of many people who made the code better - Nikolay Labinskiy (e-moe) - Leo Feyer - Chekote + - gadelat (gadelat) - Thomas Adam - Albert Casademont (acasademont) - Jhonny Lidfors (jhonne) @@ -299,8 +300,8 @@ Symfony is the result of the work of many people who made the code better - Thomas Tourlourat (armetiz) - Andrey Esaulov (andremaha) - Grégoire Passault (gregwar) + - Jerzy Zawadzki (jzawadzki) - Ismael Ambrosi (iambrosi) - - gadelat (gadelat) - Baptiste Lafontaine - Aurelijus Valeiša (aurelijus) - Victor Bocharsky (bocharsky_bw) @@ -345,6 +346,7 @@ Symfony is the result of the work of many people who made the code better - Yaroslav Kiliba - Terje Bråten - Robbert Klarenbeek (robbertkl) + - Edi Modrić (emodric) - Thomas Calvet (fancyweb) - Niels Keurentjes (curry684) - JhonnyL @@ -364,7 +366,6 @@ Symfony is the result of the work of many people who made the code better - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) - Vyacheslav Salakhutdinov (megazoll) - - Jerzy Zawadzki (jzawadzki) - Hassan Amouhzi - Tamas Szijarto - Pavel Volokitin (pvolok) @@ -375,6 +376,7 @@ Symfony is the result of the work of many people who made the code better - Tobias Naumann (tna) - Daniel Beyer - Shein Alexey + - Alex Rock Ancelet (pierstoval) - Romain Gautier (mykiwi) - Joe Lencioni - Daniel Tschinder @@ -434,7 +436,6 @@ Symfony is the result of the work of many people who made the code better - cedric lombardot (cedriclombardot) - Jonas Flodén (flojon) - Thomas Perez (scullwm) - - Edi Modrić (emodric) - Marcin Sikoń (marphi) - Dominik Zogg (dominik.zogg) - Marek Pietrzak @@ -446,6 +447,7 @@ Symfony is the result of the work of many people who made the code better - Rob Bast - Zander Baldwin - Adam Harvey + - Anton Bakai - Maxime Veber (nek-) - Alex Bakhturin - Yanick Witschi (toflar) @@ -495,7 +497,6 @@ Symfony is the result of the work of many people who made the code better - Arjen van der Meijden - Michele Locati - Dariusz Ruminski - - Alex Rock Ancelet (pierstoval) - Erik Trapman (eriktrapman) - De Cock Xavier (xdecock) - Almog Baku (almogbaku) @@ -619,7 +620,6 @@ Symfony is the result of the work of many people who made the code better - Catalin Dan - Stephan Vock - Benjamin Zikarsky (bzikarsky) - - Anton Bakai - Simon Schick (simonsimcity) - redstar504 - Tristan Roussel @@ -711,8 +711,10 @@ Symfony is the result of the work of many people who made the code better - Nikita Nefedov (nikita2206) - cgonzalez - Ben + - Mathieu Lechat - Vincent Composieux (eko) - Jayson Xu (superjavason) + - Christopher Hertel (chertel) - Hubert Lenoir (hubert_lenoir) - Jaik Dean (jaikdean) - fago @@ -733,6 +735,7 @@ Symfony is the result of the work of many people who made the code better - Pierre Vanliefland (pvanliefland) - Sofiane HADDAG (sofhad) - frost-nzcr4 + - Bozhidar Hristov - Abhoryo - Fabian Vogler (fabian) - Korvin Szanto @@ -763,6 +766,7 @@ Symfony is the result of the work of many people who made the code better - Fabien LUCAS (flucas2) - Jörn Lang (j.lang) - Omar Yepez (oyepez003) + - Gawain Lynch (gawain) - mwsaz - Jelle Kapitein - Benoît Bourgeois @@ -925,6 +929,7 @@ Symfony is the result of the work of many people who made the code better - Christian - Denis Golubovskiy (bukashk0zzz) - Sergii Smertin (nfx) + - Michał Strzelecki - hugofonseca (fonsecas72) - Martynas Narbutas - Bailey Parker @@ -1002,6 +1007,7 @@ Symfony is the result of the work of many people who made the code better - Brooks Boyd - Roger Webb - Dmitriy Simushev + - pkowalczyk - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) @@ -1127,6 +1133,7 @@ Symfony is the result of the work of many people who made the code better - Max Summe - WedgeSama - Felds Liscia + - Chihiro Adachi (chihiro-adachi) - Sullivan SENECHAL - Tadcka - Beth Binkovitz @@ -1283,6 +1290,7 @@ Symfony is the result of the work of many people who made the code better - nuncanada - flack - František Bereň + - Jeremiah VALERIE - Mike Francis - Christoph Nissle (derstoffel) - Ionel Scutelnicu (ionelscutelnicu) @@ -1305,6 +1313,7 @@ Symfony is the result of the work of many people who made the code better - Andrew Zhilin (zhil) - Oleksii Zhurbytskyi - Andy Stanberry + - Felix Marezki - Luiz “Felds” Liscia - Thomas Rothe - nietonfir @@ -1352,7 +1361,6 @@ Symfony is the result of the work of many people who made the code better - Nicole Cordes - Martin Kirilov - Bram Van der Sype (brammm) - - Christopher Hertel (chertel) - Guile (guile) - Julien Moulin (lizjulien) - Mauro Foti (skler) @@ -1386,7 +1394,6 @@ Symfony is the result of the work of many people who made the code better - Bertalan Attila - Yannick Bensacq (cibou) - Freek Van der Herten (freekmurze) - - Gawain Lynch (gawain) - Luca Genuzio (genuzio) - Hans Nilsson (hansnilsson) - Andrew Marcinkevičius (ifdattic) @@ -1444,6 +1451,7 @@ Symfony is the result of the work of many people who made the code better - David Windell - Gabriel Birke - skafandri + - Derek Bonner - Alan Chen - Maerlyn - Even André Fiskvik @@ -1487,6 +1495,7 @@ Symfony is the result of the work of many people who made the code better - Ian Jenkins (jenkoian) - Jorge Martin (jorgemartind) - Joeri Verdeyen (jverdeyen) + - Dmitrii Poddubnyi (karser) - Kevin Verschaeve (keversc) - Kevin Herrera (kherge) - Luis Ramón López López (lrlopez) @@ -1500,6 +1509,7 @@ Symfony is the result of the work of many people who made the code better - Jimmy Leger (redpanda) - Marcin Szepczynski (szepczynski) - Cyrille Jouineau (tuxosaurus) + - Vladimir Chernyshev (volch) - Yorkie Chadwick (yorkie76) - GuillaumeVerdon - Ondrej Mirtes @@ -1582,9 +1592,11 @@ Symfony is the result of the work of many people who made the code better - Michael Schneider - Cédric Bertolini - n-aleha + - Anatol Belski - Şəhriyar İmanov - Kaipi Yann - Sam Williams + - Guillaume Aveline - Adrian Philipp - James Michael DuPont - Kasperki @@ -1744,6 +1756,7 @@ Symfony is the result of the work of many people who made the code better - Schuyler Jager (sjager) - Pascal Luna (skalpa) - Volker (skydiablo) + - Serkan Yildiz (srknyldz) - Julien Sanchez (sumbobyboys) - Guillermo Gisinger (t3chn0r) - Markus Tacker (tacker) diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md index 77d0a143f84d3..b6b1fb9b55bb9 100644 --- a/UPGRADE-3.4.md +++ b/UPGRADE-3.4.md @@ -281,7 +281,7 @@ HttpKernel # ... # explicit commands registration - AppBundle\Command: + AppBundle\Command\: resource: '../../src/AppBundle/Command/*' tags: ['console.command'] ``` diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 96c652705aa81..0cee61b0cfcb1 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -1,6 +1,40 @@ UPGRADE FROM 3.x to 4.0 ======================= +Symfony Framework +----------------- + +The first step to upgrade a Symfony 3.x application to 4.x is to update the +file and directory structure of your application: + +| Symfony 3.x | Symfony 4.x +| ----------------------------------- | -------------------------------- +| `app/config/` | `config/` +| `app/config/*.yml` | `config/*.yaml` and `config/packages/*.yaml` +| `app/config/parameters.yml.dist` | `config/services.yaml` and `.env.dist` +| `app/config/parameters.yml` | `config/services.yaml` and `.env` +| `app/Resources//views/` | `templates/bundles//` +| `app/Resources/` | `src/Resources/` +| `app/Resources/assets/` | `assets/` +| `app/Resources/translations/` | `translations/` +| `app/Resources/views/` | `templates/` +| `src/AppBundle/` | `src/` +| `var/logs/` | `var/log/` +| `web/` | `public/` +| `web/app.php` | `public/index.php` +| `web/app_dev.php` | `public/index.php` + +Then, upgrade the contents of your console script and your front controller: + +* `bin/console`: https://github.com/symfony/recipes/blob/master/symfony/console/3.3/bin/console +* `public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/public/index.php + +Lastly, read the following article to add Symfony Flex to your application and +upgrade the configuration files: https://symfony.com/doc/current/setup/flex.html + +If you use Symfony components instead of the whole framework, you can find below +the upgrading instructions for each individual bundle and component. + ClassLoader ----------- diff --git a/appveyor.yml b/appveyor.yml index a1ba60ac1b02d..6cee17f6feedf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -51,7 +51,7 @@ install: - copy /Y .composer\* %APPDATA%\Composer\ - php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - - php composer.phar update --no-progress --no-suggest --ansi + - php -dmemory_limit=-1 composer.phar update --no-progress --no-suggest --ansi - php phpunit install test_script: diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index f918d0d211c94..e7015fd1e4119 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -112,7 +112,7 @@ private function groupByConnection(array $services, $isListener = false) } $instance['event'] = array($instance['event']); - if ($lazy = !empty($instance['lazy'])) { + if (!empty($instance['lazy'])) { $this->container->getDefinition($id)->setPublic(true); } } diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 9fcb974602b87..1deb3ca9d3164 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -32,7 +32,8 @@ "suggest": { "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", - "symfony/event-dispatcher": "Needed when using log messages in console commands." + "symfony/event-dispatcher": "Needed when using log messages in console commands.", + "symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler." }, "autoload": { "psr-4": { "Symfony\\Bridge\\Monolog\\": "" }, diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php index 655687575d241..d2ddddf3f23bb 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php @@ -120,7 +120,11 @@ public function startTestSuite($suite) $this->state = 0; if (!class_exists('Doctrine\Common\Annotations\AnnotationRegistry', false) && class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { - AnnotationRegistry::registerLoader('class_exists'); + if (method_exists('Doctrine\Common\Annotations\AnnotationRegistry', 'registerUniqueLoader')) { + AnnotationRegistry::registerUniqueLoader('class_exists'); + } else { + AnnotationRegistry::registerLoader('class_exists'); + } } if ($this->skippedFile = getenv('SYMFONY_PHPUNIT_SKIPPED_TESTS')) { diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index a265a129e6fdc..5de946789155d 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -28,7 +28,11 @@ setlocale(LC_ALL, 'C'); if (!class_exists('Doctrine\Common\Annotations\AnnotationRegistry', false) && class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { - AnnotationRegistry::registerLoader('class_exists'); + if (method_exists('Doctrine\Common\Annotations\AnnotationRegistry', 'registerUniqueLoader')) { + AnnotationRegistry::registerUniqueLoader('class_exists'); + } else { + AnnotationRegistry::registerLoader('class_exists'); + } } if ('disabled' !== getenv('SYMFONY_DEPRECATIONS_HELPER')) { 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 6793064520eb3..d57978220f330 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 @@ -8,16 +8,21 @@ {%- endblock textarea_widget %} {% block money_widget -%} -
- {%- set append = money_pattern starts with '{{' -%} - {%- if not append -%} - {{ money_pattern|replace({ '{{ widget }}':''}) }} - {%- endif -%} + {% 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') -}} - {%- if append -%} - {{ money_pattern|replace({ '{{ widget }}':''}) }} - {%- endif -%} -
+ {% endif %} {%- endblock money_widget %} {% block percent_widget -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php index 60934c1c2df84..2adf12d99ea88 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php @@ -25,10 +25,6 @@ class WorkflowExtensionTest extends TestCase protected function setUp() { - if (!class_exists(Workflow::class)) { - $this->markTestSkipped('The Workflow component is needed to run tests for this extension.'); - } - $places = array('ordered', 'waiting_for_payment', 'processed'); $transitions = array( new Transition('t1', 'ordered', 'waiting_for_payment'), diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 33ac7d5849c7c..aea0d10a67791 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -20,7 +20,6 @@ "twig/twig": "^1.35|^2.4.4" }, "require-dev": { - "fig/link-util": "^1.0", "symfony/asset": "~2.8|~3.0|~4.0", "symfony/dependency-injection": "~2.8|~3.0|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", @@ -38,7 +37,8 @@ "symfony/console": "~3.4|~4.0", "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0", "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/web-link": "~3.3|~4.0" + "symfony/web-link": "~3.3|~4.0", + "symfony/workflow": "~3.3|~4.0" }, "conflict": { "symfony/form": "<3.4", diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index b86df408fd2c3..ccc6b38d92e65 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -246,7 +246,7 @@ public function filterToServiceTypes($serviceId) } try { - $r = new \ReflectionClass($serviceId); + new \ReflectionClass($serviceId); return true; } catch (\ReflectionException $e) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index 51b383bc3810b..ae8415cb4efbd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -112,6 +112,10 @@ public function process(ContainerBuilder $container) $clearer->setArgument(0, $pools); } $clearer->addTag('cache.pool.clearer'); + + if ('cache.system_clearer' === $id) { + $clearer->addTag('kernel.cache_clearer'); + } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index c9dbd6af77a2a..d5030fc9fe7cf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -21,7 +21,6 @@ use Symfony\Component\Form\Form; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Validator\Validation; @@ -143,7 +142,7 @@ private function addCsrfSection(ArrayNodeDefinition $rootNode) $rootNode ->children() ->arrayNode('csrf_protection') - ->{!class_exists(FullStack::class) && class_exists(CsrfTokenManagerInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->canBeEnabled() ->end() ->end() ; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 6638860e9eff3..6f972ceaf4663 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Doctrine\Common\Annotations\Reader; +use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Bridge\Monolog\Processor\DebugProcessor; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -1344,6 +1345,11 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde $container->getAlias('annotation_reader')->setPrivate(true); + if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { + $container->getDefinition('annotations.dummy_registry') + ->setMethodCalls(array(array('registerLoader', array('class_exists')))); + } + if ('none' !== $config['cache']) { if (!class_exists('Doctrine\Common\Cache\CacheProvider')) { throw new LogicException('Annotations cannot be enabled as the Doctrine Cache library is not installed.'); @@ -1415,7 +1421,7 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild } if (!class_exists('Symfony\Component\Security\Csrf\CsrfToken')) { - throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed.'); + throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require security-csrf".'); } if (!$this->sessionConfigEnabled) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml index cefae5570fb11..6e8cc4f9e6639 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/annotations.xml @@ -10,14 +10,14 @@ required - - - - - class_exists - - - + + + + + + + + class_exists diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index 9466d992c0fa4..f388501ba4d32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -104,9 +104,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 abdf9955791a5..0798dce16e1f5 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 @@ -199,6 +199,8 @@ + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_strict_email.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_strict_email.php new file mode 100644 index 0000000000000..64a47a232204e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_strict_email.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'validation' => array( + 'strict_email' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_translation_domain.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_translation_domain.php new file mode 100644 index 0000000000000..40a81d4936ce1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_translation_domain.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'validation' => array( + 'translation_domain' => 'messages', + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_strict_email.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_strict_email.xml new file mode 100644 index 0000000000000..5b4aba1b70dd6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_strict_email.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_translation_domain.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_translation_domain.xml new file mode 100644 index 0000000000000..733d5fa683ebe --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_translation_domain.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_strict_email.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_strict_email.yml new file mode 100644 index 0000000000000..1c805f9b923d2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_strict_email.yml @@ -0,0 +1,3 @@ +framework: + validation: + strict_email: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_translation_domain.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_translation_domain.yml new file mode 100644 index 0000000000000..167b5fcce85b1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_translation_domain.yml @@ -0,0 +1,3 @@ +framework: + validation: + translation_domain: messages diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 974a069443cb3..52d94b45dbf67 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -143,6 +143,14 @@ public function testEsiDisabled() $this->assertFalse($container->hasDefinition('fragment.renderer.esi'), 'The ESI fragment renderer is not registered'); } + public function testEsiInactive() + { + $container = $this->createContainerFromFile('default_config'); + + $this->assertFalse($container->hasDefinition('fragment.renderer.esi')); + $this->assertFalse($container->hasDefinition('esi')); + } + public function testSsi() { $container = $this->createContainerFromFile('full'); @@ -167,6 +175,14 @@ public function testEsiAndSsiWithoutFragments() $this->assertTrue($container->hasDefinition('fragment.renderer.ssi'), 'The SSI fragment renderer is registered'); } + public function testSsiInactive() + { + $container = $this->createContainerFromFile('default_config'); + + $this->assertFalse($container->hasDefinition('fragment.renderer.ssi')); + $this->assertFalse($container->hasDefinition('ssi')); + } + public function testEnabledProfiler() { $container = $this->createContainerFromFile('profiler'); @@ -702,6 +718,20 @@ public function testValidationNoStaticMethod() // no cache, no annotations, no static methods } + public function testValidationTranslationDomain() + { + $container = $this->createContainerFromFile('validation_translation_domain'); + + $this->assertSame('messages', $container->getParameter('validator.translation_domain')); + } + + public function testValidationStrictEmail() + { + $container = $this->createContainerFromFile('validation_strict_email'); + + $this->assertTrue($container->getDefinition('validator.email')->getArgument(0)); + } + public function testValidationMapping() { $container = $this->createContainerFromFile('validation_mapping'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php index 5238eb3f842c3..382bdebe018fa 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php @@ -121,7 +121,7 @@ private function createContainer($sessionStorageOptions) ); $ext = new FrameworkExtension(); - $ext->load(array(), $container); + $ext->load(array('framework' => array('csrf_protection' => false)), $container); $ext = new SecurityExtension(); $ext->load($config, $container); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 9918dc7e48414..e9c12485dfe3f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -378,7 +378,7 @@ public function testAccess() foreach ($rules as list($matcherId, $attributes, $channel)) { $requestMatcher = $container->getDefinition($matcherId); - $this->assertFalse(isset($matcherIds[$matcherId])); + $this->assertArrayNotHasKey($matcherId, $matcherIds); $matcherIds[$matcherId] = true; $i = count($matcherIds); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index 5a00ac329895d..6077b0b4a7870 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -28,6 +28,7 @@ security: security: false default: + logout_on_user_change: true form_login: check_path: /login_check default_target_path: /profile diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml index 955c41192e26d..fc032ab5ef0e0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/FirewallEntryPoint/config.yml @@ -16,10 +16,12 @@ services: security: firewalls: secure: + logout_on_user_change: true pattern: ^/secure/ http_basic: { realm: "Secure Gateway API" } entry_point: firewall_entry_point.entry_point.stub default: + logout_on_user_change: true anonymous: ~ access_control: - { path: ^/secure/, roles: ROLE_SECURE } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml index d6ed10e896ff9..9cf5e8bf9a3a0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml @@ -13,6 +13,7 @@ security: firewalls: main: + logout_on_user_change: true pattern: ^/ anonymous: true json_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml index e15e203c626cc..c49f7312e00d5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml @@ -13,6 +13,7 @@ security: firewalls: main: + logout_on_user_change: true pattern: ^/ anonymous: true json_login: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml index d7b8ac97d9775..d486d63065903 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/SecurityHelper/config.yml @@ -15,4 +15,5 @@ security: firewalls: default: + logout_on_user_change: true anonymous: ~ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml index 19b9d8952ec5e..2276b14dd920c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml @@ -20,6 +20,7 @@ security: security: false default: + logout_on_user_change: true form_login: check_path: /login_check default_target_path: /profile @@ -28,6 +29,7 @@ security: # This firewall is here just to check its the logout functionality second_area: + logout_on_user_change: true http_basic: ~ anonymous: ~ logout: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml index e01ed369b1f56..e9c266af126dd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_form_failure_handler.yml @@ -13,6 +13,7 @@ security: firewalls: default: + logout_on_user_change: true form_login: login_path: localized_login_path check_path: localized_check_path diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml index 5251fd1d93de1..df54ac83f4aab 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/localized_routes.yml @@ -13,6 +13,7 @@ security: firewalls: default: + logout_on_user_change: true form_login: login_path: localized_login_path check_path: localized_check_path diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index 075a39f71ff76..e3a0d8454f5c8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -41,6 +41,7 @@ box-sizing: content-box; vertical-align: baseline; letter-spacing: normal; + width: auto; } .sf-toolbarreset { diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php index 2a92d62eeea8d..8b38748c3e1c8 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\WebServerBundle\Command; +use Monolog\Formatter\FormatterInterface; use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use Symfony\Bridge\Monolog\Handler\ConsoleHandler; use Symfony\Component\Console\Command\Command; @@ -37,6 +38,11 @@ public function isEnabled() return false; } + // based on a symfony/symfony package, it crashes due a missing FormatterInterface from monolog/monolog + if (!class_exists(FormatterInterface::class)) { + return false; + } + return parent::isEnabled(); } diff --git a/src/Symfony/Bundle/WebServerBundle/composer.json b/src/Symfony/Bundle/WebServerBundle/composer.json index 659b4adafc8f6..f51be62350033 100644 --- a/src/Symfony/Bundle/WebServerBundle/composer.json +++ b/src/Symfony/Bundle/WebServerBundle/composer.json @@ -29,6 +29,10 @@ "/Tests/" ] }, + "suggest": { + "symfony/monolog-bridge": "For using the log server.", + "symfony/expression-language": "For using the filter option of the log server." + }, "minimum-stability": "dev", "extra": { "branch-alias": { diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 23f7ccbff1f9c..63d4be77cff58 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -346,6 +346,7 @@ protected function doRequestInProcess($request) { $deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec'); putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile); + $_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile; $process = new PhpProcess($this->getScript($request), null, null); $process->run(); diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 9dd6068351c08..72bbc143cedff 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -32,7 +32,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R private $setCacheItemTags; private $getTagsByKey; private $invalidateTags; - private $tagsPool; + private $tags; public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null) { diff --git a/src/Symfony/Component/Cache/Traits/PdoTrait.php b/src/Symfony/Component/Cache/Traits/PdoTrait.php index dd8c97d8ab1b2..a88099ecb183e 100644 --- a/src/Symfony/Component/Cache/Traits/PdoTrait.php +++ b/src/Symfony/Component/Cache/Traits/PdoTrait.php @@ -101,7 +101,7 @@ public function createTable() $table->addColumn($this->idCol, $types[$this->driver], array('length' => 255)); $table->addColumn($this->dataCol, 'blob', array('length' => 16777215)); $table->addColumn($this->lifetimeCol, 'integer', array('unsigned' => true, 'notnull' => false)); - $table->addColumn($this->timeCol, 'integer', array('unsigned' => true, 'foo' => 'bar')); + $table->addColumn($this->timeCol, 'integer', array('unsigned' => true)); $table->setPrimaryKey(array($this->idCol)); foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) { diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php index dd6a6317e6ffc..0fd4af976784a 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php @@ -32,7 +32,7 @@ public function testAppendingSomeNode() ->append($child); $this->assertCount(3, $this->getField($parent, 'children')); - $this->assertTrue(in_array($child, $this->getField($parent, 'children'))); + $this->assertContains($child, $this->getField($parent, 'children')); } /** diff --git a/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php index 99c75f1047576..c37c3e2fc7a3b 100644 --- a/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php @@ -178,6 +178,6 @@ public function testResourcesWithDifferentPatternsAreDifferent() $resourceA = new DirectoryResource($this->directory, '/.xml$/'); $resourceB = new DirectoryResource($this->directory, '/.yaml$/'); - $this->assertEquals(2, count(array_unique(array($resourceA, $resourceB)))); + $this->assertCount(2, array_unique(array($resourceA, $resourceB))); } } diff --git a/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php b/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php index 49edb723d212d..0d05b9dad5bd0 100644 --- a/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php +++ b/src/Symfony/Component/Console/Event/ConsoleErrorEvent.php @@ -78,6 +78,6 @@ public function setExitCode($exitCode) */ public function getExitCode() { - return null !== $this->exitCode ? $this->exitCode : ($this->error->getCode() ?: 1); + return null !== $this->exitCode ? $this->exitCode : (is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1); } } diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php index b4db5bdb4f0b0..905f7b87085f7 100644 --- a/src/Symfony/Component/Console/Helper/Table.php +++ b/src/Symfony/Component/Console/Helper/Table.php @@ -263,6 +263,7 @@ public function setRow($column, array $row) * Renders table to output. * * Example: + * * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ @@ -270,6 +271,7 @@ public function setRow($column, array $row) * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ + * */ public function render() { @@ -303,7 +305,7 @@ public function render() /** * Renders horizontal header separator. * - * Example: +-----+-----------+-------+ + * Example: +-----+-----------+-------+ */ private function renderRowSeparator() { @@ -334,7 +336,7 @@ private function renderColumnSeparator() /** * Renders table row. * - * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * * @param array $row * @param string $cellFormat diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index eef360aad3e26..b576cf42a903a 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -287,6 +287,8 @@ public function hasParameterOption($values, $onlyParams = false) } if (0 === strpos($token, '-') && 0 !== strpos($token, '--')) { + $noValue = explode('=', $token); + $token = $noValue[0]; $searchableToken = str_replace('-', '', $token); $searchableValue = str_replace('-', '', $value); if ('' !== $searchableToken && '' !== $searchableValue && false !== strpos($searchableToken, $searchableValue)) { diff --git a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php index b9b42b9af4c3f..01d6e4b96a0c2 100644 --- a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php +++ b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php @@ -317,6 +317,9 @@ public function testHasParameterOption() $input = new ArgvInput(array('cli.php', '-fh')); $this->assertTrue($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '-e=test')); + $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'); diff --git a/src/Symfony/Component/CssSelector/Parser/TokenStream.php b/src/Symfony/Component/CssSelector/Parser/TokenStream.php index 7ebc0cf194250..24e8634ad6b86 100644 --- a/src/Symfony/Component/CssSelector/Parser/TokenStream.php +++ b/src/Symfony/Component/CssSelector/Parser/TokenStream.php @@ -31,11 +31,6 @@ class TokenStream */ private $tokens = array(); - /** - * @var bool - */ - private $frozen = false; - /** * @var Token[] */ @@ -49,7 +44,7 @@ class TokenStream /** * @var Token|null */ - private $peeked = null; + private $peeked; /** * @var bool @@ -75,8 +70,6 @@ public function push(Token $token) */ public function freeze() { - $this->frozen = true; - return $this; } diff --git a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php index 79e2da14bb249..218a3b43c1a78 100644 --- a/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php +++ b/src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php @@ -37,7 +37,7 @@ public function testXmlLang($css, array $elementsId) $translator = new Translator(); $document = new \SimpleXMLElement(file_get_contents(__DIR__.'/Fixtures/lang.xml')); $elements = $document->xpath($translator->cssToXPath($css)); - $this->assertEquals(count($elementsId), count($elements)); + $this->assertCount(count($elementsId), $elements); foreach ($elements as $element) { $this->assertTrue(in_array($element->attributes()->id, $elementsId)); } diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index 61a25e1123344..aebb740addede 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -530,6 +530,7 @@ public function handleException($exception, array $error = null) $exception = new FatalThrowableError($exception); } $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; + $handlerException = null; if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { if ($exception instanceof FatalErrorException) { @@ -564,18 +565,20 @@ public function handleException($exception, array $error = null) } } } - if (empty($this->exceptionHandler)) { - throw $exception; // Give back $exception to the native handler - } try { - call_user_func($this->exceptionHandler, $exception); + if (null !== $this->exceptionHandler) { + return \call_user_func($this->exceptionHandler, $exception); + } + $handlerException = $handlerException ?: $exception; } catch (\Exception $handlerException) { } catch (\Throwable $handlerException) { } - if (isset($handlerException)) { - $this->exceptionHandler = null; - $this->handleException($handlerException); + $this->exceptionHandler = null; + if ($exception === $handlerException) { + self::$reservedMemory = null; // Disable the fatal error handler + throw $exception; // Give back $exception to the native handler } + $this->handleException($handlerException); } /** @@ -591,15 +594,30 @@ public static function handleFatalError(array $error = null) return; } - self::$reservedMemory = null; + $handler = self::$reservedMemory = null; + $handlers = array(); - $handler = set_error_handler('var_dump'); - $handler = is_array($handler) ? $handler[0] : null; - restore_error_handler(); + while (!is_array($handler) || !$handler[0] instanceof self) { + $handler = set_exception_handler('var_dump'); + restore_exception_handler(); - if (!$handler instanceof self) { + if (!$handler) { + break; + } + restore_exception_handler(); + array_unshift($handlers, $handler); + } + foreach ($handlers as $h) { + set_exception_handler($h); + } + if (!$handler) { return; } + if ($handler !== $h) { + $handler[0]->setExceptionHandler($h); + } + $handler = $handler[0]; + $handlers = array(); if ($exit = null === $error) { $error = error_get_last(); diff --git a/src/Symfony/Component/Debug/Tests/phpt/exception_rethrown.phpt b/src/Symfony/Component/Debug/Tests/phpt/exception_rethrown.phpt new file mode 100644 index 0000000000000..877e208f406a7 --- /dev/null +++ b/src/Symfony/Component/Debug/Tests/phpt/exception_rethrown.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test rethrowing in custom exception handler +--FILE-- +setDefaultLogger(new TestLogger()); +ini_set('display_errors', 1); + +throw new \Exception('foo'); + +?> +--EXPECTF-- +Uncaught Exception: foo +123 +Fatal error: Uncaught %s:25 +Stack trace: +%a diff --git a/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt b/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt new file mode 100644 index 0000000000000..bd7b644dc3aa7 --- /dev/null +++ b/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test catching fatal errors when handlers are nested +--FILE-- +setExceptionHandler('print_r'); + +if (true) { + class Broken implements \Serializable {}; +} + +?> +--EXPECTF-- +array(1) { + [0]=> + string(37) "Error and exception handlers do match" +} +object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (%d) { + ["message":protected]=> + string(199) "Error: Class Symfony\Component\Debug\Broken contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize)" +%a +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 889d81448a94c..23a5abc9f58ea 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -33,7 +33,6 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe { private $graph; private $currentDefinition; - private $repeatedPass; private $onlyConstructorArguments; private $lazy; private $expressionLanguage; @@ -51,7 +50,7 @@ public function __construct($onlyConstructorArguments = false) */ public function setRepeatedPass(RepeatedPass $repeatedPass) { - $this->repeatedPass = $repeatedPass; + // no-op for BC } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 0b7305d5a8ada..1320ef1f2a503 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\DependencyInjection\Config\AutowireServiceResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -442,7 +443,17 @@ private function createAutowiredDefinition($type) private function createTypeNotFoundMessage(TypedReference $reference, $label) { if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { - $message = sprintf('has type "%s" but this class cannot be loaded.', $type); + // either $type does not exist or a parent class does not exist + try { + $resource = new ClassExistenceResource($type, false); + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + $parentMsg = false; + } catch (\ReflectionException $e) { + $parentMsg = $e->getMessage(); + } + + $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); } else { $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $this->createTypeAlternatives($reference)); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index a015b2e2e94e5..c704891856f9e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -23,7 +23,6 @@ */ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface { - private $repeatedPass; private $cloningIds = array(); private $inlinedServiceIds = array(); @@ -32,7 +31,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe */ public function setRepeatedPass(RepeatedPass $repeatedPass) { - $this->repeatedPass = $repeatedPass; + // no-op for BC } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index f8dba86a0b547..4ed990ac08d2b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -94,7 +94,7 @@ protected function processValue($value, $isRoot = false) throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId)); } - $value->addTag('container.service_subscriber.locator', array('id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap))); + $value->addTag('container.service_subscriber.locator', array('id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId))); return parent::processValue($value); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php index 2c4a09f8476f9..fb32d501ac29e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php @@ -72,10 +72,11 @@ protected function processValue($value, $isRoot = false) /** * @param ContainerBuilder $container * @param Reference[] $refMap + * @param string|null $callerId * * @return Reference */ - public static function register(ContainerBuilder $container, array $refMap) + public static function register(ContainerBuilder $container, array $refMap, $callerId = null) { foreach ($refMap as $id => $ref) { if (!$ref instanceof Reference) { @@ -94,6 +95,18 @@ public static function register(ContainerBuilder $container, array $refMap) $container->setDefinition($id, $locator); } + if (null !== $callerId) { + $locatorId = $id; + // Locators are shared when they hold the exact same list of factories; + // to have them specialized per consumer service, we use a cloning factory + // to derivate customized instances from the prototype one. + $container->register($id .= '.'.$callerId, ServiceLocator::class) + ->setPublic(false) + ->setFactory(array(new Reference($locatorId), 'withContext')) + ->addArgument($callerId) + ->addArgument(new Reference('service_container')); + } + return new Reference($id); } } diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 14af37ef78ac4..a7b9a94e4f5d4 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -264,7 +264,7 @@ public function has($id) * * @see Reference */ - public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) + public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1) { // Attempt to retrieve the service by checking first aliases then // available services. Service IDs are case insensitive, however since @@ -294,9 +294,9 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE try { if (isset($this->fileMap[$id])) { - return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->load($this->fileMap[$id]); + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); } elseif (isset($this->methodMap[$id])) { - return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); } elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) { unset($this->loading[$id]); $id = $normalizedId; @@ -306,7 +306,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) @trigger_error('Generating a dumped container without populating the method map is deprecated since 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', E_USER_DEPRECATED); - return self::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior ? null : $this->{$method}(); + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$method}(); } break; @@ -319,7 +319,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE } } - if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { + if (/* self::EXCEPTION_ON_INVALID_REFERENCE */ 1 === $invalidBehavior) { if (!$id) { throw new ServiceNotFoundException($id); } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 086bfdafe2c18..7f0d6071d63e4 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1036,8 +1036,19 @@ public function findDefinition($id) { $id = $this->normalizeId($id); + $seen = array(); while (isset($this->aliasDefinitions[$id])) { $id = (string) $this->aliasDefinitions[$id]; + + if (isset($seen[$id])) { + $seen = array_values($seen); + $seen = array_slice($seen, array_search($id, $seen)); + $seen[] = $id; + + throw new ServiceCircularReferenceException($id, $seen); + } + + $seen[$id] = $id; } return $this->getDefinition($id); @@ -1351,6 +1362,11 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs $format = '%%env(%s)%%'; } + $bag = $this->getParameterBag(); + if (true === $format) { + $value = $bag->resolveValue($value); + } + if (is_array($value)) { $result = array(); foreach ($value as $k => $v) { @@ -1363,11 +1379,6 @@ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs if (!is_string($value)) { return $value; } - - $bag = $this->getParameterBag(); - if (true === $format) { - $value = $bag->resolveValue($value); - } $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; foreach ($envPlaceholders as $env => $placeholders) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 0f5e33afb6da9..5e402556af6d2 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -413,7 +413,7 @@ private function addServiceInclude($cId, Definition $definition, \SplObjectStora if ($this->inlineRequires && !$this->isHotPath($definition)) { $lineage = $calls = $behavior = array(); foreach ($inlinedDefinitions as $def) { - if (!$def->isDeprecated() && $class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) { + if (!$def->isDeprecated() && is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass())) { $this->collectLineage($class, $lineage); } $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); @@ -425,7 +425,7 @@ private function addServiceInclude($cId, Definition $definition, \SplObjectStora && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] && $this->container->has($id) && $this->isTrivialInstance($def = $this->container->findDefinition($id)) - && $class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass() + && is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) ) { $this->collectLineage($class, $lineage); } @@ -695,7 +695,7 @@ private function addServiceConfigurator(Definition $definition, $variableName = return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } - return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + return sprintf(" \\call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } return sprintf(" %s(\$%s);\n", $callable, $variableName); @@ -895,7 +895,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation, return $return.sprintf("(%s)->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); } - return $return.sprintf("call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); + return $return.sprintf("\\call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); } return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); @@ -948,11 +948,11 @@ public function __construct() EOF; if (null !== $this->targetDirRegex) { - $dir = $this->asFiles ? '$this->targetDirs[0] = dirname(__DIR__)' : '__DIR__'; + $dir = $this->asFiles ? '$this->targetDirs[0] = \\dirname(__DIR__)' : '__DIR__'; $code .= <<targetDirMaxMatches}; ++\$i) { - \$this->targetDirs[\$i] = \$dir = dirname(\$dir); + \$this->targetDirs[\$i] = \$dir = \\dirname(\$dir); } EOF; @@ -1226,7 +1226,7 @@ private function addInlineRequires() $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition)); foreach ($inlinedDefinitions as $def) { - if ($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) { + if (is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass())) { $this->collectLineage($class, $lineage); } } @@ -1722,7 +1722,7 @@ private function dumpValue($value, $interpolate = true) } if ($factory[0] instanceof Definition) { - return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($factory[0]), $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + return sprintf("\\call_user_func(array(%s, '%s')%s)", $this->dumpValue($factory[0]), $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); } if ($factory[0] instanceof Reference) { @@ -1854,7 +1854,7 @@ private function getServiceCall($id, Reference $reference = null) } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { return 'null'; } elseif (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { - $code = sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id); + $code = sprintf('$this->get(\'%s\', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $id, ContainerInterface::NULL_ON_INVALID_REFERENCE); } else { $code = sprintf('$this->get(\'%s\')', $id); } diff --git a/src/Symfony/Component/DependencyInjection/ServiceLocator.php b/src/Symfony/Component/DependencyInjection/ServiceLocator.php index 270de6b935630..324cccab98913 100644 --- a/src/Symfony/Component/DependencyInjection/ServiceLocator.php +++ b/src/Symfony/Component/DependencyInjection/ServiceLocator.php @@ -22,6 +22,9 @@ class ServiceLocator implements PsrContainerInterface { private $factories; + private $loading = array(); + private $externalId; + private $container; /** * @param callable[] $factories @@ -45,18 +48,22 @@ public function has($id) public function get($id) { if (!isset($this->factories[$id])) { - throw new ServiceNotFoundException($id, null, null, array_keys($this->factories)); + throw new ServiceNotFoundException($id, end($this->loading) ?: null, null, array(), $this->createServiceNotFoundMessage($id)); } - if (true === $factory = $this->factories[$id]) { - throw new ServiceCircularReferenceException($id, array($id, $id)); + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw new ServiceCircularReferenceException($id, $ids); } - $this->factories[$id] = true; + $this->loading[$id] = $id; try { - return $factory(); + return $this->factories[$id](); } finally { - $this->factories[$id] = $factory; + unset($this->loading[$id]); } } @@ -64,4 +71,75 @@ public function __invoke($id) { return isset($this->factories[$id]) ? $this->get($id) : null; } + + /** + * @internal + */ + public function withContext($externalId, Container $container) + { + $locator = clone $this; + $locator->externalId = $externalId; + $locator->container = $container; + + return $locator; + } + + private function createServiceNotFoundMessage($id) + { + if ($this->loading) { + return sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives()); + } + + $class = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $class = isset($class[2]['object']) ? get_class($class[2]['object']) : null; + $externalId = $this->externalId ?: $class; + + $msg = sprintf('Service "%s" not found: ', $id); + + if (!$this->container) { + $class = null; + } elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) { + $msg .= 'even though it exists in the app\'s container, '; + } else { + try { + $this->container->get($id); + $class = null; + } catch (ServiceNotFoundException $e) { + if ($e->getAlternatives()) { + $msg .= sprintf(' did you mean %s? Anyway, ', $this->formatAlternatives($e->getAlternatives(), 'or')); + } else { + $class = null; + } + } + } + if ($externalId) { + $msg .= sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives()); + } else { + $msg .= sprintf('the current service locator %s', $this->formatAlternatives()); + } + + if (!$class) { + // no-op + } elseif (is_subclass_of($class, ServiceSubscriberInterface::class)) { + $msg .= sprintf(' Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class)); + } else { + $msg .= 'Try using dependency injection instead.'; + } + + return $msg; + } + + private function formatAlternatives(array $alternatives = null, $separator = 'and') + { + $format = '"%s"%s'; + if (null === $alternatives) { + if (!$alternatives = array_keys($this->factories)) { + return 'is empty...'; + } + $format = sprintf('only knows about the %s service%s.', $format, 1 < count($alternatives) ? 's' : ''); + } + $last = array_pop($alternatives); + + return sprintf($format, $alternatives ? implode('", "', $alternatives) : $last, $alternatives ? sprintf(' %s "%s"', $separator, $last) : ''); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index a4a1cdb9b2511..e550bb2147776 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -372,7 +372,7 @@ public function testDontTriggerAutowiring() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException - * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded. + * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found. */ public function testClassNotFoundThrowsException() { @@ -389,7 +389,7 @@ public function testClassNotFoundThrowsException() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException - * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class cannot be loaded. + * @expectedExceptionMessage Cannot autowire service "a": argument "$r" of method "Symfony\Component\DependencyInjection\Tests\Compiler\BadParentTypeHintedArgument::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\OptionalServiceClass" but this class is missing a parent class (Class Symfony\Bug\NotExistClass not found). */ public function testParentClassNotFoundThrowsException() { @@ -789,7 +789,7 @@ public function testNotWireableCalls($method, $expectedMsg) public function provideNotWireableCalls() { return array( - array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'), + array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found.'), array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'), array(null, 'Invalid service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'), ); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php index 957a04c6c48c7..42f994e821d97 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php @@ -85,7 +85,7 @@ public function testNoAttributes() 'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, TestServiceSubscriber::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), ); - $this->assertEquals($expected, $locator->getArgument(0)); + $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); } public function testWithAttributes() @@ -115,7 +115,7 @@ public function testWithAttributes() 'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, TestServiceSubscriber::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), ); - $this->assertEquals($expected, $locator->getArgument(0)); + $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0)); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index cf0a0a5627a27..100296291ad80 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -73,7 +73,7 @@ public function testDefinitions() $builder->setDefinition('foobar', $foo = new Definition('FooBarClass')); $this->assertEquals($foo, $builder->getDefinition('foobar'), '->getDefinition() returns a service definition if defined'); - $this->assertTrue($builder->setDefinition('foobar', $foo = new Definition('FooBarClass')) === $foo, '->setDefinition() implements a fluid interface by returning the service reference'); + $this->assertSame($builder->setDefinition('foobar', $foo = new Definition('FooBarClass')), $foo, '->setDefinition() implements a fluid interface by returning the service reference'); $builder->addDefinitions($defs = array('foobar' => new Definition('FooBarClass'))); $this->assertEquals(array_merge($definitions, $defs), $builder->getDefinitions(), '->addDefinitions() adds the service definitions'); @@ -242,7 +242,7 @@ public function testAliases() $this->assertFalse($builder->hasAlias('foobar'), '->hasAlias() returns false if the alias does not exist'); $this->assertEquals('foo', (string) $builder->getAlias('bar'), '->getAlias() returns the aliased service'); $this->assertTrue($builder->has('bar'), '->setAlias() defines a new service'); - $this->assertTrue($builder->get('bar') === $builder->get('foo'), '->setAlias() creates a service that is an alias to another one'); + $this->assertSame($builder->get('bar'), $builder->get('foo'), '->setAlias() creates a service that is an alias to another one'); try { $builder->setAlias('foobar', 'foobar'); @@ -287,8 +287,8 @@ public function testSetAliases() $builder->setAliases(array('bar' => 'foo', 'foobar' => 'foo')); $aliases = $builder->getAliases(); - $this->assertTrue(isset($aliases['bar'])); - $this->assertTrue(isset($aliases['foobar'])); + $this->assertArrayHasKey('bar', $aliases); + $this->assertArrayHasKey('foobar', $aliases); } public function testAddAliases() @@ -298,8 +298,8 @@ public function testAddAliases() $builder->addAliases(array('foobar' => 'foo')); $aliases = $builder->getAliases(); - $this->assertTrue(isset($aliases['bar'])); - $this->assertTrue(isset($aliases['foobar'])); + $this->assertArrayHasKey('bar', $aliases); + $this->assertArrayHasKey('foobar', $aliases); } public function testSetReplacesAlias() @@ -562,7 +562,7 @@ public function testMerge() $this->assertEquals(array('service_container', 'foo', 'bar', 'baz'), array_keys($container->getDefinitions()), '->merge() merges definitions already defined ones'); $aliases = $container->getAliases(); - $this->assertTrue(isset($aliases['alias_for_foo'])); + $this->assertArrayHasKey('alias_for_foo', $aliases); $this->assertEquals('foo', (string) $aliases['alias_for_foo']); $container = new ContainerBuilder(); @@ -616,6 +616,28 @@ public function testResolveEnvValues() unset($_ENV['DUMMY_ENV_VAR'], $_SERVER['DUMMY_SERVER_VAR'], $_SERVER['HTTP_DUMMY_VAR']); } + public function testResolveEnvValuesWithArray() + { + $_ENV['ANOTHER_DUMMY_ENV_VAR'] = 'dummy'; + + $dummyArray = array('1' => 'one', '2' => 'two'); + + $container = new ContainerBuilder(); + $container->setParameter('dummy', '%env(ANOTHER_DUMMY_ENV_VAR)%'); + $container->setParameter('dummy2', $dummyArray); + + $container->resolveEnvPlaceholders('%dummy%', true); + $container->resolveEnvPlaceholders('%dummy2%', true); + + $this->assertInternalType('array', $container->resolveEnvPlaceholders('%dummy2%', true)); + + foreach ($dummyArray as $key => $value) { + $this->assertArrayHasKey($key, $container->resolveEnvPlaceholders('%dummy2%', true)); + } + + unset($_ENV['ANOTHER_DUMMY_ENV_VAR']); + } + public function testCompileWithResolveEnv() { putenv('DUMMY_ENV_VAR=du%%y'); @@ -947,7 +969,7 @@ public function testExtension() $container->setResourceTracking(false); $container->registerExtension($extension = new \ProjectExtension()); - $this->assertTrue($container->getExtension('project') === $extension, '->registerExtension() registers an extension'); + $this->assertSame($container->getExtension('project'), $extension, '->registerExtension() registers an extension'); $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('LogicException'); $container->getExtension('no_registered'); @@ -1123,6 +1145,23 @@ public function testInlinedDefinitions() $this->assertNotSame($bar->foo, $barUser->foo); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + * @expectedExceptionMessage Circular reference detected for service "app.test_class", path: "app.test_class -> App\TestClass -> app.test_class". + */ + public function testThrowsCircularExceptionForCircularAliases() + { + $builder = new ContainerBuilder(); + + $builder->setAliases(array( + 'foo' => new Alias('app.test_class'), + 'app.test_class' => new Alias('App\\TestClass'), + 'App\\TestClass' => new Alias('app.test_class'), + )); + + $builder->findDefinition('foo'); + } + public function testInitializePropertiesBeforeMethodCalls() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 7455dd6cde681..d9b3c5c864d25 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; +use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator; @@ -822,6 +823,31 @@ public function testDumpHandlesLiteralClassWithRootNamespace() $this->assertInstanceOf('stdClass', $container->get('foo')); } + public function testDumpHandlesObjectClassNames() + { + $container = new ContainerBuilder(new ParameterBag(array( + 'class' => 'stdClass', + ))); + + $container->setDefinition('foo', new Definition(new Parameter('class'))); + $container->setDefinition('bar', new Definition('stdClass', array( + new Reference('foo'), + )))->setPublic(true); + + $container->setParameter('inline_requires', true); + $container->compile(); + + $dumper = new PhpDumper($container); + eval('?>'.$dumper->dump(array( + 'class' => 'Symfony_DI_PhpDumper_Test_Object_Class_Name', + 'inline_class_loader_parameter' => 'inline_requires', + ))); + + $container = new \Symfony_DI_PhpDumper_Test_Object_Class_Name(); + + $this->assertInstanceOf('stdClass', $container->get('bar')); + } + /** * @group legacy * @expectedDeprecation The "private" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php index c5c434ec7d7c1..48750c480f0ec 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php @@ -23,7 +23,7 @@ public function __construct() { $dir = __DIR__; for ($i = 1; $i <= 5; ++$i) { - $this->targetDirs[$i] = $dir = dirname($dir); + $this->targetDirs[$i] = $dir = \dirname($dir); } $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php index 257a145c6f5ff..5c8773c380349 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php @@ -23,7 +23,7 @@ public function __construct() { $dir = __DIR__; for ($i = 1; $i <= 5; ++$i) { - $this->targetDirs[$i] = $dir = dirname($dir); + $this->targetDirs[$i] = $dir = \dirname($dir); } $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index 467733f467d6c..ad316b23c6ee9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -223,9 +223,9 @@ protected function getFooService() */ protected function getFoo_BazService() { - $this->services['foo.baz'] = $instance = call_user_func(array($this->getParameter('baz_class'), 'getInstance')); + $this->services['foo.baz'] = $instance = \call_user_func(array($this->getParameter('baz_class'), 'getInstance')); - call_user_func(array($this->getParameter('baz_class'), 'configureStatic1'), $instance); + \call_user_func(array($this->getParameter('baz_class'), 'configureStatic1'), $instance); return $instance; } @@ -281,7 +281,7 @@ protected function getLazyContextIgnoreInvalidRefService() return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () { yield 0 => ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->getFoo_BazService()) && false ?: '_'}; if ($this->has('invalid')) { - yield 1 => ${($_ = isset($this->services['invalid']) ? $this->services['invalid'] : $this->get('invalid', ContainerInterface::NULL_ON_INVALID_REFERENCE)) && false ?: '_'}; + yield 1 => ${($_ = isset($this->services['invalid']) ? $this->services['invalid'] : $this->get('invalid', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ 2)) && false ?: '_'}; } }, function () { return 1 + (int) ($this->has('invalid')); @@ -302,12 +302,12 @@ protected function getMethodCall1Service() $this->services['method_call1'] = $instance = new \Bar\FooClass(); $instance->setBar(${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->getFooService()) && false ?: '_'}); - $instance->setBar(${($_ = isset($this->services['foo2']) ? $this->services['foo2'] : $this->get('foo2', ContainerInterface::NULL_ON_INVALID_REFERENCE)) && false ?: '_'}); + $instance->setBar(${($_ = isset($this->services['foo2']) ? $this->services['foo2'] : $this->get('foo2', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ 2)) && false ?: '_'}); if ($this->has('foo3')) { - $instance->setBar(${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : $this->get('foo3', ContainerInterface::NULL_ON_INVALID_REFERENCE)) && false ?: '_'}); + $instance->setBar(${($_ = isset($this->services['foo3']) ? $this->services['foo3'] : $this->get('foo3', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ 2)) && false ?: '_'}); } if ($this->has('foobaz')) { - $instance->setBar(${($_ = isset($this->services['foobaz']) ? $this->services['foobaz'] : $this->get('foobaz', ContainerInterface::NULL_ON_INVALID_REFERENCE)) && false ?: '_'}); + $instance->setBar(${($_ = isset($this->services['foobaz']) ? $this->services['foobaz'] : $this->get('foobaz', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ 2)) && false ?: '_'}); } $instance->setBar((${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->getFooService()) && false ?: '_'}->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default")))); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 59ace1ddf3a38..0ee660df4dbf5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -281,9 +281,9 @@ class ProjectServiceContainer extends Container public function __construct() { - $dir = $this->targetDirs[0] = dirname(__DIR__); + $dir = $this->targetDirs[0] = \dirname(__DIR__); for ($i = 1; $i <= 5; ++$i) { - $this->targetDirs[$i] = $dir = dirname($dir); + $this->targetDirs[$i] = $dir = \dirname($dir); } $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php index 65e6b61e34e57..659aef172f810 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_array_params.php @@ -23,7 +23,7 @@ public function __construct() { $dir = __DIR__; for ($i = 1; $i <= 5; ++$i) { - $this->targetDirs[$i] = $dir = dirname($dir); + $this->targetDirs[$i] = $dir = \dirname($dir); } $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php index 3b45218bd9b9a..2012c952c9971 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php @@ -23,7 +23,7 @@ public function __construct() { $dir = __DIR__; for ($i = 1; $i <= 5; ++$i) { - $this->targetDirs[$i] = $dir = dirname($dir); + $this->targetDirs[$i] = $dir = \dirname($dir); } $this->parameters = $this->getDefaultParameters(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php index 03947c425df79..1a9c87286d6e9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_legacy_privates.php @@ -23,7 +23,7 @@ public function __construct() { $dir = __DIR__; for ($i = 1; $i <= 5; ++$i) { - $this->targetDirs[$i] = $dir = dirname($dir); + $this->targetDirs[$i] = $dir = \dirname($dir); } $this->services = array(); $this->methodMap = array( diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php index 967d8012680bc..f682e9752ca6e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php @@ -45,6 +45,7 @@ public function getRemovedIds() 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => true, 'service_locator.jmktfsv' => true, + 'service_locator.jmktfsv.foo_service' => true, ); } @@ -82,7 +83,7 @@ protected function getTestServiceSubscriberService() */ protected function getFooServiceService() { - return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber(new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function () { + return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber(\call_user_func(array(new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function () { $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()) && false ?: '_'}); }, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => function () { $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()) && false ?: '_'}); @@ -90,7 +91,7 @@ protected function getFooServiceService() $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()) && false ?: '_'}); }, 'baz' => function () { $f = function (\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition $v = null) { return $v; }; return $f(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()) && false ?: '_'}); - }))); + })), 'withContext'), 'foo_service', $this)); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 8dc0f786d6060..1c21045f42571 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -101,7 +101,7 @@ public function testLoadWithExternalEntitiesDisabled() libxml_disable_entity_loader($disableEntities); - $this->assertTrue(count($containerBuilder->getParameterBag()->all()) > 0, 'Parameters can be read from the config file.'); + $this->assertGreaterThan(0, $containerBuilder->getParameterBag()->all(), 'Parameters can be read from the config file.'); } public function testLoadParameters() @@ -191,7 +191,7 @@ public function testLoadAnonymousServices() $args = $services['foo']->getArguments(); $this->assertCount(1, $args, '->load() references anonymous services as "normal" ones'); $this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Reference', $args[0], '->load() converts anonymous services to references to "normal" services'); - $this->assertTrue(isset($services[(string) $args[0]]), '->load() makes a reference to the created ones'); + $this->assertArrayHasKey((string) $args[0], $services, '->load() makes a reference to the created ones'); $inner = $services[(string) $args[0]]; $this->assertEquals('BarClass', $inner->getClass(), '->load() uses the same configuration as for the anonymous ones'); $this->assertFalse($inner->isPublic()); @@ -200,7 +200,7 @@ public function testLoadAnonymousServices() $args = $inner->getArguments(); $this->assertCount(1, $args, '->load() references anonymous services as "normal" ones'); $this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Reference', $args[0], '->load() converts anonymous services to references to "normal" services'); - $this->assertTrue(isset($services[(string) $args[0]]), '->load() makes a reference to the created ones'); + $this->assertArrayHasKey((string) $args[0], $services, '->load() makes a reference to the created ones'); $inner = $services[(string) $args[0]]; $this->assertEquals('BazClass', $inner->getClass(), '->load() uses the same configuration as for the anonymous ones'); $this->assertFalse($inner->isPublic()); @@ -209,7 +209,7 @@ public function testLoadAnonymousServices() $properties = $services['foo']->getProperties(); $property = $properties['p']; $this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Reference', $property, '->load() converts anonymous services to references to "normal" services'); - $this->assertTrue(isset($services[(string) $property]), '->load() makes a reference to the created ones'); + $this->assertArrayHasKey((string) $property, $services, '->load() makes a reference to the created ones'); $inner = $services[(string) $property]; $this->assertEquals('BuzClass', $inner->getClass(), '->load() uses the same configuration as for the anonymous ones'); $this->assertFalse($inner->isPublic()); @@ -260,7 +260,7 @@ public function testLoadServices() $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); $loader->load('services6.xml'); $services = $container->getDefinitions(); - $this->assertTrue(isset($services['foo']), '->load() parses elements'); + $this->assertArrayHasKey('foo', $services, '->load() parses elements'); $this->assertFalse($services['not_shared']->isShared(), '->load() parses shared flag'); $this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Definition', $services['foo'], '->load() converts element to Definition instances'); $this->assertEquals('FooClass', $services['foo']->getClass(), '->load() parses the class attribute'); @@ -277,10 +277,10 @@ public function testLoadServices() $this->assertSame(array(null, 'getInstance'), $services['new_factory4']->getFactory(), '->load() accepts factory tag without class'); $aliases = $container->getAliases(); - $this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses elements'); + $this->assertArrayHasKey('alias_for_foo', $aliases, '->load() parses elements'); $this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases'); $this->assertTrue($aliases['alias_for_foo']->isPublic()); - $this->assertTrue(isset($aliases['another_alias_for_foo'])); + $this->assertArrayHasKey('another_alias_for_foo', $aliases); $this->assertEquals('foo', (string) $aliases['another_alias_for_foo']); $this->assertFalse($aliases['another_alias_for_foo']->isPublic()); @@ -403,8 +403,8 @@ public function testExtensions() $services = $container->getDefinitions(); $parameters = $container->getParameterBag()->all(); - $this->assertTrue(isset($services['project.service.bar']), '->load() parses extension elements'); - $this->assertTrue(isset($parameters['project.parameter.bar']), '->load() parses extension elements'); + $this->assertArrayHasKey('project.service.bar', $services, '->load() parses extension elements'); + $this->assertArrayHasKey('project.parameter.bar', $parameters, '->load() parses extension elements'); $this->assertEquals('BAR', $services['project.service.foo']->getClass(), '->load() parses extension elements'); $this->assertEquals('BAR', $parameters['project.parameter.foo'], '->load() parses extension elements'); @@ -419,8 +419,8 @@ public function testExtensions() $services = $container->getDefinitions(); $parameters = $container->getParameterBag()->all(); - $this->assertTrue(isset($services['project.service.bar']), '->load() parses extension elements'); - $this->assertTrue(isset($parameters['project.parameter.bar']), '->load() parses extension elements'); + $this->assertArrayHasKey('project.service.bar', $services, '->load() parses extension elements'); + $this->assertArrayHasKey('project.parameter.bar', $parameters, '->load() parses extension elements'); $this->assertEquals('BAR', $services['project.service.foo']->getClass(), '->load() parses extension elements'); $this->assertEquals('BAR', $parameters['project.parameter.foo'], '->load() parses extension elements'); @@ -542,8 +542,8 @@ public function testXmlNamespaces() $loader->load('namespaces.xml'); $services = $container->getDefinitions(); - $this->assertTrue(isset($services['foo']), '->load() parses elements'); - $this->assertEquals(1, count($services['foo']->getTag('foo.tag')), '->load parses elements'); + $this->assertArrayHasKey('foo', $services, '->load() parses elements'); + $this->assertCount(1, $services['foo']->getTag('foo.tag'), '->load parses elements'); $this->assertEquals(array(array('setBar', array('foo'))), $services['foo']->getMethodCalls(), '->load() parses the tag'); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 45106f90e2b37..b36cc31161029 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -141,7 +141,7 @@ public function testLoadServices() $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); $loader->load('services6.yml'); $services = $container->getDefinitions(); - $this->assertTrue(isset($services['foo']), '->load() parses service elements'); + $this->assertArrayHasKey('foo', $services, '->load() parses service elements'); $this->assertFalse($services['not_shared']->isShared(), '->load() parses the shared flag'); $this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Definition', $services['foo'], '->load() converts service element to Definition instances'); $this->assertEquals('FooClass', $services['foo']->getClass(), '->load() parses the class attribute'); @@ -159,10 +159,10 @@ public function testLoadServices() $this->assertEquals(array('foo', new Reference('baz')), $services['Acme\WithShortCutArgs']->getArguments(), '->load() parses short service definition'); $aliases = $container->getAliases(); - $this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses aliases'); + $this->assertArrayHasKey('alias_for_foo', $aliases, '->load() parses aliases'); $this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases'); $this->assertTrue($aliases['alias_for_foo']->isPublic()); - $this->assertTrue(isset($aliases['another_alias_for_foo'])); + $this->assertArrayHasKey('another_alias_for_foo', $aliases); $this->assertEquals('foo', (string) $aliases['another_alias_for_foo']); $this->assertFalse($aliases['another_alias_for_foo']->isPublic()); $this->assertTrue(isset($aliases['another_third_alias_for_foo'])); @@ -206,8 +206,8 @@ public function testExtensions() $services = $container->getDefinitions(); $parameters = $container->getParameterBag()->all(); - $this->assertTrue(isset($services['project.service.bar']), '->load() parses extension elements'); - $this->assertTrue(isset($parameters['project.parameter.bar']), '->load() parses extension elements'); + $this->assertArrayHasKey('project.service.bar', $services, '->load() parses extension elements'); + $this->assertArrayHasKey('project.parameter.bar', $parameters, '->load() parses extension elements'); $this->assertEquals('BAR', $services['project.service.foo']->getClass(), '->load() parses extension elements'); $this->assertEquals('BAR', $parameters['project.parameter.foo'], '->load() parses extension elements'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php b/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php index a1e2fff50fdbe..56fac643eb40b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ServiceLocatorTest.php @@ -12,8 +12,9 @@ namespace Symfony\Component\DependencyInjection\Tests; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; class ServiceLocatorTest extends TestCase { @@ -59,7 +60,7 @@ public function testGetDoesNotMemoize() /** * @expectedException \Psr\Container\NotFoundExceptionInterface - * @expectedExceptionMessage You have requested a non-existent service "dummy". Did you mean one of these: "foo", "bar"? + * @expectedExceptionMessage Service "dummy" not found: the container inside "Symfony\Component\DependencyInjection\Tests\ServiceLocatorTest" is a smaller service locator that only knows about the "foo" and "bar" services. */ public function testGetThrowsOnUndefinedService() { @@ -68,13 +69,50 @@ public function testGetThrowsOnUndefinedService() 'bar' => function () { return 'baz'; }, )); - try { - $locator->get('dummy'); - } catch (ServiceNotFoundException $e) { - $this->assertSame(array('foo', 'bar'), $e->getAlternatives()); + $locator->get('dummy'); + } + + /** + * @expectedException \Psr\Container\NotFoundExceptionInterface + * @expectedExceptionMessage The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service. + */ + public function testThrowsOnUndefinedInternalService() + { + $locator = new ServiceLocator(array( + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + )); + + $locator->get('foo'); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + * @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> baz -> bar". + */ + public function testThrowsOnCircularReference() + { + $locator = new ServiceLocator(array( + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + )); - throw $e; - } + $locator->get('foo'); + } + + /** + * @expectedException \Psr\Container\NotFoundExceptionInterface + * @expectedExceptionMessage Service "foo" not found: even though it exists in the app's container, the container inside "caller" is a smaller service locator that only knows about the "bar" service. Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "SomeServiceSubscriber::getSubscribedServices()". + */ + public function testThrowsInServiceSubscriber() + { + $container = new Container(); + $container->set('foo', new \stdClass()); + $subscriber = new SomeServiceSubscriber(); + $subscriber->container = new ServiceLocator(array('bar' => function () {})); + $subscriber->container = $subscriber->container->withContext('caller', $container); + + $subscriber->getFoo(); } public function testInvoke() @@ -89,3 +127,18 @@ public function testInvoke() $this->assertNull($locator('dummy'), '->__invoke() should return null on invalid service'); } } + +class SomeServiceSubscriber implements ServiceSubscriberinterface +{ + public $container; + + public function getFoo() + { + return $this->container->get('foo'); + } + + public static function getSubscribedServices() + { + return array('bar' => 'stdClass'); + } +} diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 2ca4cd048581c..6eaa3fd089cff 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -32,7 +32,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { - "symfony/config": "<3.3.1", + "symfony/config": "<3.3.7", "symfony/finder": "<3.3", "symfony/proxy-manager-bridge": "<3.4", "symfony/yaml": "<3.4" diff --git a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php index c787869ca7940..ec8512e4ef7fd 100644 --- a/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -204,7 +204,7 @@ public function testAddXmlContentWithErrors() EOF , 'UTF-8'); - $this->assertTrue(count(libxml_get_errors()) > 1); + $this->assertGreaterThan(1, libxml_get_errors()); libxml_clear_errors(); libxml_use_internal_errors($internalErrors); diff --git a/src/Symfony/Component/DomCrawler/Tests/FormTest.php b/src/Symfony/Component/DomCrawler/Tests/FormTest.php index 6164b1ca7c9fa..90e87d4c00a2d 100644 --- a/src/Symfony/Component/DomCrawler/Tests/FormTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/FormTest.php @@ -376,15 +376,15 @@ public function testOffsetUnset() { $form = $this->createForm('
'); unset($form['foo']); - $this->assertFalse(isset($form['foo']), '->offsetUnset() removes a field'); + $this->assertArrayNotHasKey('foo', $form, '->offsetUnset() removes a field'); } public function testOffsetExists() { $form = $this->createForm('
'); - $this->assertTrue(isset($form['foo']), '->offsetExists() return true if the field exists'); - $this->assertFalse(isset($form['bar']), '->offsetExists() return false if the field does not exist'); + $this->assertArrayHasKey('foo', $form, '->offsetExists() return true if the field exists'); + $this->assertArrayNotHasKey('bar', $form, '->offsetExists() return false if the field does not exist'); } public function testGetValues() diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index dde92f46af5b6..3ae735c7db78b 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -112,7 +112,7 @@ public function parse($data, $path = '.env') $this->end = strlen($this->data); $this->state = self::STATE_VARNAME; $this->values = array(); - $name = $value = ''; + $name = ''; $this->skipEmptyLines(); diff --git a/src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php b/src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php index 313db4a4a904a..7a2b68be22881 100644 --- a/src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php +++ b/src/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php @@ -78,7 +78,7 @@ public function removeListener($eventName, $listener) $this->lazyLoad($eventName); if (isset($this->listenerIds[$eventName])) { - foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { + foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method)) { $key = $serviceId.'.'.$method; if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { unset($this->listeners[$eventName][$key]); diff --git a/src/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php index 180556149268c..9d5eecc547bd4 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -143,7 +143,7 @@ public function testGetListenersOnLazyLoad() $listeners = $dispatcher->getListeners(); - $this->assertTrue(isset($listeners['onEvent'])); + $this->assertArrayHasKey('onEvent', $listeners); $this->assertCount(1, $dispatcher->getListeners('onEvent')); } diff --git a/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php b/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php index c84d3ac24c3b1..9cf68c987f0da 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php @@ -114,8 +114,8 @@ public function testOffsetUnset() public function testOffsetIsset() { - $this->assertTrue(isset($this->event['name'])); - $this->assertFalse(isset($this->event['nameNotExist'])); + $this->assertArrayHasKey('name', $this->event); + $this->assertArrayNotHasKey('nameNotExist', $this->event); } public function testHasArgument() diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 52343f119bca0..70328c8e59c22 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -146,6 +146,16 @@ public function testShortCircuitOperatorsCompile($expression, array $names, $exp $this->assertSame($expected, $result); } + /** + * @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError + * @expectedExceptionMessage Unexpected end of expression around position 6 for expression `node.`. + */ + public function testParseThrowsInsteadOfNotice() + { + $expressionLanguage = new ExpressionLanguage(); + $expressionLanguage->parse('node.', array('node')); + } + public function shortCircuitProviderEvaluate() { $object = $this->getMockBuilder('stdClass')->setMethods(array('foo'))->getMock(); diff --git a/src/Symfony/Component/ExpressionLanguage/TokenStream.php b/src/Symfony/Component/ExpressionLanguage/TokenStream.php index 9426bfeb7e287..9096b183ff378 100644 --- a/src/Symfony/Component/ExpressionLanguage/TokenStream.php +++ b/src/Symfony/Component/ExpressionLanguage/TokenStream.php @@ -50,12 +50,12 @@ public function __toString() */ public function next() { + ++$this->position; + if (!isset($this->tokens[$this->position])) { throw new SyntaxError('Unexpected end of expression', $this->current->cursor, $this->expression); } - ++$this->position; - $this->current = $this->tokens[$this->position]; } diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index ea4f82d80b646..1d3009295665a 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -26,7 +26,7 @@ public function testCopyCreatesNewFile() $this->filesystem->copy($sourceFilePath, $targetFilePath); $this->assertFileExists($targetFilePath); - $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE'); } /** @@ -73,7 +73,7 @@ public function testCopyOverridesExistingFileIfModified() $this->filesystem->copy($sourceFilePath, $targetFilePath); $this->assertFileExists($targetFilePath); - $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE'); } public function testCopyDoesNotOverrideExistingFileByDefault() @@ -92,7 +92,7 @@ public function testCopyDoesNotOverrideExistingFileByDefault() $this->filesystem->copy($sourceFilePath, $targetFilePath); $this->assertFileExists($targetFilePath); - $this->assertEquals('TARGET FILE', file_get_contents($targetFilePath)); + $this->assertStringEqualsFile($targetFilePath, 'TARGET FILE'); } public function testCopyOverridesExistingFileIfForced() @@ -111,7 +111,7 @@ public function testCopyOverridesExistingFileIfForced() $this->filesystem->copy($sourceFilePath, $targetFilePath, true); $this->assertFileExists($targetFilePath); - $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE'); } /** @@ -153,7 +153,7 @@ public function testCopyCreatesTargetDirectoryIfItDoesNotExist() $this->assertTrue(is_dir($targetFileDirectory)); $this->assertFileExists($targetFilePath); - $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + $this->assertStringEqualsFile($targetFilePath, 'SOURCE FILE'); } /** @@ -840,9 +840,9 @@ public function testRemoveSymlink() $this->filesystem->remove($link); - $this->assertTrue(!is_link($link)); - $this->assertTrue(!is_file($link)); - $this->assertTrue(!is_dir($link)); + $this->assertFalse(is_link($link)); + $this->assertFalse(is_file($link)); + $this->assertFalse(is_dir($link)); } public function testSymlinkIsOverwrittenIfPointsToDifferentTarget() @@ -1474,7 +1474,7 @@ public function testDumpFile() $this->filesystem->dumpFile($filename, 'bar'); $this->assertFileExists($filename); - $this->assertSame('bar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'bar'); // skip mode check on Windows if ('\\' !== DIRECTORY_SEPARATOR) { @@ -1491,7 +1491,7 @@ public function testDumpFileOverwritesAnExistingFile() $this->filesystem->dumpFile($filename, 'bar'); $this->assertFileExists($filename); - $this->assertSame('bar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'bar'); } public function testDumpFileWithFileScheme() @@ -1506,7 +1506,7 @@ public function testDumpFileWithFileScheme() $this->filesystem->dumpFile($filename, 'bar'); $this->assertFileExists($filename); - $this->assertSame('bar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'bar'); } public function testDumpFileWithZlibScheme() @@ -1518,7 +1518,7 @@ public function testDumpFileWithZlibScheme() // Zlib stat uses file:// wrapper so remove scheme $this->assertFileExists(str_replace($scheme, '', $filename)); - $this->assertSame('bar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'bar'); } public function testAppendToFile() @@ -1535,7 +1535,7 @@ public function testAppendToFile() $this->filesystem->appendToFile($filename, 'bar'); $this->assertFileExists($filename); - $this->assertSame('foobar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'foobar'); // skip mode check on Windows if ('\\' !== DIRECTORY_SEPARATOR) { @@ -1557,7 +1557,7 @@ public function testAppendToFileWithScheme() $this->filesystem->appendToFile($filename, 'bar'); $this->assertFileExists($filename); - $this->assertSame('foobar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'foobar'); } public function testAppendToFileWithZlibScheme() @@ -1567,12 +1567,12 @@ public function testAppendToFileWithZlibScheme() $this->filesystem->dumpFile($filename, 'foo'); // Zlib stat uses file:// wrapper so remove it - $this->assertSame('foo', file_get_contents(str_replace($scheme, '', $filename))); + $this->assertStringEqualsFile(str_replace($scheme, '', $filename), 'foo'); $this->filesystem->appendToFile($filename, 'bar'); $this->assertFileExists($filename); - $this->assertSame('foobar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'foobar'); } public function testAppendToFileCreateTheFileIfNotExists() @@ -1593,7 +1593,7 @@ public function testAppendToFileCreateTheFileIfNotExists() } $this->assertFileExists($filename); - $this->assertSame('bar', file_get_contents($filename)); + $this->assertStringEqualsFile($filename, 'bar'); } public function testDumpKeepsExistingPermissionsWhenOverwritingAnExistingFile() diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php index bed74532d1571..df297da277f93 100644 --- a/src/Symfony/Component/Form/DependencyInjection/FormPass.php +++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php @@ -18,7 +18,6 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\Form\Command\DebugCommand; /** * Adds all services with the tags "form.type", "form.type_extension" and @@ -36,7 +35,7 @@ class FormPass implements CompilerPassInterface private $formTypeGuesserTag; private $formDebugCommandService; - public function __construct($formExtensionService = 'form.extension', $formTypeTag = 'form.type', $formTypeExtensionTag = 'form.type_extension', $formTypeGuesserTag = 'form.type_guesser', $formDebugCommandService = DebugCommand::class) + public function __construct($formExtensionService = 'form.extension', $formTypeTag = 'form.type', $formTypeExtensionTag = 'form.type_extension', $formTypeGuesserTag = 'form.type_guesser', $formDebugCommandService = 'console.command.form_debug') { $this->formExtensionService = $formExtensionService; $this->formTypeTag = $formTypeTag; diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php index dffe146623772..4624e585e7e0d 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateIntervalToStringTransformer.php @@ -23,20 +23,17 @@ class DateIntervalToStringTransformer implements DataTransformerInterface { private $format; - private $parseSigned; /** * Transforms a \DateInterval instance to a string. * * @see \DateInterval::format() for supported formats * - * @param string $format The date format - * @param bool $parseSigned Whether to parse as a signed interval + * @param string $format The date format */ - public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS', $parseSigned = false) + public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS') { $this->format = $format; - $this->parseSigned = $parseSigned; } /** diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php index 6415d7f4a9096..4764117964b61 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -1914,6 +1914,25 @@ public function testMoney() ); } + public function testMoneyWithoutCurrency() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\MoneyType', 1234.56, array( + 'currency' => false, + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('id' => 'my&id', 'attr' => array('class' => 'my&class')), +'/input + [@id="my&id"] + [@type="text"] + [@name="name"] + [@class="my&class form-control"] + [@value="1234.56"] + [not(preceding-sibling::*)] + [not(following-sibling::*)] +' + ); + } + public function testNumber() { $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56); diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index acdd004d425a0..bcf1b8c4f9aa7 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -308,12 +308,12 @@ public function testArrayAccess() $this->form[] = $child; - $this->assertTrue(isset($this->form['foo'])); + $this->assertArrayHasKey('foo', $this->form); $this->assertSame($child, $this->form['foo']); unset($this->form['foo']); - $this->assertFalse(isset($this->form['foo'])); + $this->assertArrayNotHasKey('foo', $this->form); } public function testCountable() diff --git a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php index f045e3f9f5d50..59e2262efc418 100644 --- a/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php +++ b/src/Symfony/Component/Form/Tests/DependencyInjection/FormPassTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; -use Symfony\Component\Form\Command\DebugCommand; use Symfony\Component\Form\DependencyInjection\FormPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -43,7 +42,7 @@ public function testDoNothingIfDebugCommandNotLoaded() $container->compile(); - $this->assertFalse($container->hasDefinition(DebugCommand::class)); + $this->assertFalse($container->hasDefinition('console.command.form_debug')); } public function testAddTaggedTypes() @@ -72,13 +71,13 @@ public function testAddTaggedTypesToDebugCommand() $container = $this->createContainerBuilder(); $container->setDefinition('form.extension', $this->createExtensionDefinition()); - $container->setDefinition(DebugCommand::class, $this->createDebugCommandDefinition()); + $container->setDefinition('console.command.form_debug', $this->createDebugCommandDefinition()); $container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type')->setPublic(true); $container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type')->setPublic(true); $container->compile(); - $cmdDefinition = $container->getDefinition(DebugCommand::class); + $cmdDefinition = $container->getDefinition('console.command.form_debug'); $this->assertEquals( array( 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 15e08ae96f91e..192d38f1a9225 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -196,7 +196,7 @@ public function testPlaceholderPresentOnNonRequiredExpandedSingleChoice() 'choices' => $this->choices, )); - $this->assertTrue(isset($form['placeholder'])); + $this->assertArrayHasKey('placeholder', $form); $this->assertCount(count($this->choices) + 1, $form, 'Each choice should become a new field'); } @@ -209,7 +209,7 @@ public function testPlaceholderNotPresentIfRequired() 'choices' => $this->choices, )); - $this->assertFalse(isset($form['placeholder'])); + $this->assertArrayNotHasKey('placeholder', $form); $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); } @@ -222,7 +222,7 @@ public function testPlaceholderNotPresentIfMultiple() 'choices' => $this->choices, )); - $this->assertFalse(isset($form['placeholder'])); + $this->assertArrayNotHasKey('placeholder', $form); $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); } @@ -238,7 +238,7 @@ public function testPlaceholderNotPresentIfEmptyChoice() ), )); - $this->assertFalse(isset($form['placeholder'])); + $this->assertArrayNotHasKey('placeholder', $form); $this->assertCount(2, $form, 'Each choice should become a new field'); } @@ -295,7 +295,7 @@ public function testPlaceholderWithExpandedBooleanChoices() 'placeholder' => 'Select an option', )); - $this->assertTrue(isset($form['placeholder']), 'Placeholder should be set'); + $this->assertArrayHasKey('placeholder', $form, 'Placeholder should be set'); $this->assertCount(3, $form, 'Each choice should become a new field, placeholder included'); $view = $form->createView(); @@ -319,7 +319,7 @@ public function testPlaceholderWithExpandedBooleanChoicesAndWithFalseAsPreSetDat 'placeholder' => 'Select an option', )); - $this->assertTrue(isset($form['placeholder']), 'Placeholder should be set'); + $this->assertArrayHasKey('placeholder', $form, 'Placeholder should be set'); $this->assertCount(3, $form, 'Each choice should become a new field, placeholder included'); $view = $form->createView(); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php index 977cb8cf43948..ffeace3038b0d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php @@ -49,7 +49,7 @@ public function testSetDataAdjustsSize() $form->setData(array('foo@baz.com')); $this->assertInstanceOf('Symfony\Component\Form\Form', $form[0]); - $this->assertFalse(isset($form[1])); + $this->assertArrayNotHasKey(1, $form); $this->assertCount(1, $form); $this->assertEquals('foo@baz.com', $form[0]->getData()); $formAttrs0 = $form[0]->getConfig()->getOption('attr'); @@ -271,7 +271,7 @@ public function testGetDataDoesNotContainsPrototypeNameBeforeDataAreSet() )); $data = $form->getData(); - $this->assertFalse(isset($data['__name__'])); + $this->assertArrayNotHasKey('__name__', $data); } public function testGetDataDoesNotContainsPrototypeNameAfterDataAreSet() @@ -284,7 +284,7 @@ public function testGetDataDoesNotContainsPrototypeNameAfterDataAreSet() $form->setData(array('foobar.png')); $data = $form->getData(); - $this->assertFalse(isset($data['__name__'])); + $this->assertArrayNotHasKey('__name__', $data); } public function testPrototypeNameOption() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index cf55d7d1dbccf..d80e85021b796 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -419,7 +419,7 @@ public function testDontPassHtml5TypeIfHtml5NotAllowed() )) ->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function testDontPassHtml5TypeIfNotHtml5Format() @@ -430,7 +430,7 @@ public function testDontPassHtml5TypeIfNotHtml5Format() )) ->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function testDontPassHtml5TypeIfNotSingleText() @@ -440,7 +440,7 @@ public function testDontPassHtml5TypeIfNotSingleText() )) ->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function testDateTypeChoiceErrorsBubbleUp() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index 326c34c8c7430..02ef678511181 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -678,7 +678,7 @@ public function testDontPassDatePatternIfText() )) ->createView(); - $this->assertFalse(isset($view->vars['date_pattern'])); + $this->assertArrayNotHasKey('date_pattern', $view->vars); } public function testDatePatternFormatWithQuotedStrings() @@ -826,7 +826,7 @@ public function testDontPassHtml5TypeIfHtml5NotAllowed() )) ->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function testDontPassHtml5TypeIfNotHtml5Format() @@ -837,7 +837,7 @@ public function testDontPassHtml5TypeIfNotHtml5Format() )) ->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function testDontPassHtml5TypeIfNotSingleText() @@ -847,7 +847,7 @@ public function testDontPassHtml5TypeIfNotSingleText() )) ->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function provideCompoundWidgets() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php index 26098f3ddc569..752b754d5319b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/MoneyTypeTest.php @@ -43,7 +43,7 @@ public function testMoneyPatternWorksForYen() $view = $this->factory->create(static::TESTED_TYPE, null, array('currency' => 'JPY')) ->createView(); - $this->assertTrue((bool) strstr($view->vars['money_pattern'], '¥')); + $this->assertSame('¥ {{ widget }}', $view->vars['money_pattern']); } // https://github.com/symfony/symfony/issues/5458 @@ -62,4 +62,12 @@ public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); } + + public function testMoneyPatternWithoutCurrency() + { + $view = $this->factory->create(static::TESTED_TYPE, null, array('currency' => false)) + ->createView(); + + $this->assertSame('{{ widget }}', $view->vars['money_pattern']); + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index 369ebc0f6cb2c..95e43d8c0f5ff 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -536,7 +536,7 @@ public function testDontPassHtml5TypeIfHtml5NotAllowed() )); $view = $form->createView(); - $this->assertFalse(isset($view->vars['type'])); + $this->assertArrayNotHasKey('type', $view->vars); } public function testPassDefaultPlaceholderToViewIfNotRequired() diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index 0e7261cc2e3c2..6432ac6889967 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -72,7 +72,7 @@ public function testCsrfProtectionByDefaultIfRootAndCompound() )) ->createView(); - $this->assertTrue(isset($view['csrf'])); + $this->assertArrayHasKey('csrf', $view); } public function testNoCsrfProtectionByDefaultIfCompoundButNotRoot() @@ -89,7 +89,7 @@ public function testNoCsrfProtectionByDefaultIfCompoundButNotRoot() ->get('form') ->createView(); - $this->assertFalse(isset($view['csrf'])); + $this->assertArrayNotHasKey('csrf', $view); } public function testNoCsrfProtectionByDefaultIfRootButNotCompound() @@ -101,7 +101,7 @@ public function testNoCsrfProtectionByDefaultIfRootButNotCompound() )) ->createView(); - $this->assertFalse(isset($view['csrf'])); + $this->assertArrayNotHasKey('csrf', $view); } public function testCsrfProtectionCanBeDisabled() @@ -114,7 +114,7 @@ public function testCsrfProtectionCanBeDisabled() )) ->createView(); - $this->assertFalse(isset($view['csrf'])); + $this->assertArrayNotHasKey('csrf', $view); } public function testGenerateCsrfToken() @@ -357,7 +357,7 @@ public function testNoCsrfProtectionOnPrototype() ->createView() ->vars['prototype']; - $this->assertFalse(isset($prototypeView['csrf'])); + $this->assertArrayNotHasKey('csrf', $prototypeView); $this->assertCount(1, $prototypeView); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php index 31377dec3e58c..88d5169cd7a4b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationPathTest.php @@ -96,7 +96,7 @@ public function testCreatePath($string, $entries, $slicedPath = null) $path = new ViolationPath($string); $this->assertSame($slicedPath, $path->__toString()); - $this->assertSame(count($entries), count($path->getElements())); + $this->assertCount(count($entries), $path->getElements()); $this->assertSame(count($entries), $path->getLength()); foreach ($entries as $index => $entry) { diff --git a/src/Symfony/Component/Form/Tests/Util/OrderedHashMapTest.php b/src/Symfony/Component/Form/Tests/Util/OrderedHashMapTest.php index fe922e9239faa..8ce3f8fc82b82 100644 --- a/src/Symfony/Component/Form/Tests/Util/OrderedHashMapTest.php +++ b/src/Symfony/Component/Form/Tests/Util/OrderedHashMapTest.php @@ -91,14 +91,14 @@ public function testIsset() $map = new OrderedHashMap(); $map['first'] = 1; - $this->assertTrue(isset($map['first'])); + $this->assertArrayHasKey('first', $map); } public function testIssetReturnsFalseForNonExisting() { $map = new OrderedHashMap(); - $this->assertFalse(isset($map['first'])); + $this->assertArrayNotHasKey('first', $map); } public function testIssetReturnsFalseForNull() @@ -106,7 +106,7 @@ public function testIssetReturnsFalseForNull() $map = new OrderedHashMap(); $map['first'] = null; - $this->assertFalse(isset($map['first'])); + $this->assertArrayNotHasKey('first', $map); } public function testUnset() diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 3bb33140f5055..86d135b2d3afd 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -123,6 +123,10 @@ public static function checkIp6($requestIp, $ip) if (false !== strpos($ip, '/')) { list($address, $netmask) = explode('/', $ip, 2); + if ('0' === $netmask) { + return (bool) unpack('n*', @inet_pton($address)); + } + if ($netmask < 1 || $netmask > 128) { return self::$checkedIps[$cacheKey] = false; } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index d45d54091ef97..98f414783f470 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1886,7 +1886,7 @@ protected function prepareBaseUrl() // Does the baseUrl have anything in common with the request_uri? $requestUri = $this->getRequestUri(); - if ($requestUri !== '' && $requestUri[0] !== '/') { + if ('' !== $requestUri && '/' !== $requestUri[0]) { $requestUri = '/'.$requestUri; } @@ -1962,7 +1962,7 @@ protected function preparePathInfo() if (false !== $pos = strpos($requestUri, '?')) { $requestUri = substr($requestUri, 0, $pos); } - if ($requestUri !== '' && $requestUri[0] !== '/') { + if ('' !== $requestUri && '/' !== $requestUri[0]) { $requestUri = '/'.$requestUri; } diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index e079ae2e95d55..cf1325afe0627 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -952,7 +952,7 @@ public function setEtag($etag = null, $weak = false) /** * Sets the response's cache headers (validation and/or expiration). * - * Available options are: etag, last_modified, max_age, s_maxage, private, and public. + * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable. * * @param array $options An array of cache options * diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php index 7c6c476aac453..6ae1355816fdb 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -156,7 +156,7 @@ public function destroy($sessionId) if ($sessionCookieFound) { header_remove('Set-Cookie'); foreach ($otherCookies as $h) { - header('Set-Cookie:'.$h, false); + header($h, false); } } else { setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly')); diff --git a/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php index 1acf593086772..6d19ceb009f23 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php @@ -200,6 +200,6 @@ public function testCount() $headers = array('foo' => 'bar', 'HELLO' => 'WORLD'); $headerBag = new HeaderBag($headers); - $this->assertEquals(count($headers), count($headerBag)); + $this->assertCount(count($headers), $headerBag); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index 54cbb5c20672d..7a93f9947262d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -62,6 +62,8 @@ public function getIpv6Data() array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'), array(true, '0:0:0:0:0:0:0:1', '::1'), array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'), + array(true, '0:0:603:0:396e:4789:8e99:0001', '::/0'), + array(true, '0:0:603:0:396e:4789:8e99:0001', '2a01:198:603:0::/0'), array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '2a01:198:603:0::/65')), array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('2a01:198:603:0::/65', '::1')), array(false, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '1a01:198:603:0::/65')), diff --git a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php index 5311a0d8036c8..ab908d8d37de7 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -179,7 +179,7 @@ public function testCount() $parameters = array('foo' => 'bar', 'hello' => 'world'); $bag = new ParameterBag($parameters); - $this->assertEquals(count($parameters), count($bag)); + $this->assertCount(count($parameters), $bag); } public function testGetBoolean() diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index 7ed6ccc070c2e..ce8553590dcdb 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -175,10 +175,10 @@ public function testCookiesWithSameNames() $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); - $this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo'])); - $this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo'])); - $this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo'])); - $this->assertTrue(isset($cookies['']['/']['foo'])); + $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/foo']); + $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/bar']); + $this->assertArrayHasKey('foo', $cookies['bar.foo']['/path/bar']); + $this->assertArrayHasKey('foo', $cookies['']['/']); } public function testRemoveCookie() @@ -191,19 +191,19 @@ public function testRemoveCookie() $this->assertTrue($bag->has('set-cookie')); $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); - $this->assertTrue(isset($cookies['foo.bar']['/path/foo'])); + $this->assertArrayHasKey('/path/foo', $cookies['foo.bar']); $bag->removeCookie('foo', '/path/foo', 'foo.bar'); $this->assertTrue($bag->has('set-cookie')); $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); - $this->assertFalse(isset($cookies['foo.bar']['/path/foo'])); + $this->assertArrayNotHasKey('/path/foo', $cookies['foo.bar']); $bag->removeCookie('bar', '/path/bar', 'foo.bar'); $this->assertFalse($bag->has('set-cookie')); $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); - $this->assertFalse(isset($cookies['foo.bar'])); + $this->assertArrayNotHasKey('foo.bar', $cookies); } public function testRemoveCookieWithNullRemove() @@ -213,11 +213,11 @@ public function testRemoveCookieWithNullRemove() $bag->setCookie(new Cookie('bar', 'foo', 0)); $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); - $this->assertTrue(isset($cookies['']['/'])); + $this->assertArrayHasKey('/', $cookies['']); $bag->removeCookie('foo', null); $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); - $this->assertFalse(isset($cookies['']['/']['foo'])); + $this->assertArrayNotHasKey('foo', $cookies['']['/']); $bag->removeCookie('bar', null); $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); diff --git a/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php index c1d9d12a654ba..f8becec5a982d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php @@ -74,8 +74,8 @@ public function testHttpBasicAuthWithPhpCgiBogus() // Username and passwords should not be set as the header is bogus $headers = $bag->getHeaders(); - $this->assertFalse(isset($headers['PHP_AUTH_USER'])); - $this->assertFalse(isset($headers['PHP_AUTH_PW'])); + $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers); + $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers); } public function testHttpBasicAuthWithPhpCgiRedirect() @@ -118,8 +118,8 @@ public function testHttpDigestAuthWithPhpCgiBogus() // Username and passwords should not be set as the header is bogus $headers = $bag->getHeaders(); - $this->assertFalse(isset($headers['PHP_AUTH_USER'])); - $this->assertFalse(isset($headers['PHP_AUTH_PW'])); + $this->assertArrayNotHasKey('PHP_AUTH_USER', $headers); + $this->assertArrayNotHasKey('PHP_AUTH_PW', $headers); } public function testHttpDigestAuthWithPhpCgiRedirect() diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php index 655c26a9c24ce..724a0b9844700 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php @@ -181,6 +181,6 @@ public function testGetIterator() public function testCount() { - $this->assertEquals(count($this->array), count($this->bag)); + $this->assertCount(count($this->array), $this->bag); } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected new file mode 100644 index 0000000000000..5de2d9e3904ed --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected @@ -0,0 +1,24 @@ +open +validateId +read +doRead: abc|i:123; +read +updateTimestamp +close +open +validateId +read +doRead: abc|i:123; +read + +write +destroy +doDestroy +close +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: max-age=10800, private, must-revalidate + [2] => Set-Cookie: abc=def +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php new file mode 100644 index 0000000000000..ec5119323b757 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php @@ -0,0 +1,13 @@ +assertFalse($storage->isStarted()); $key = $storage->getMetadataBag()->getStorageKey(); - $this->assertFalse(isset($_SESSION[$key])); + $this->assertArrayNotHasKey($key, $_SESSION); $storage->start(); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php index b8b98386c3367..958dc0bc08984 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -75,9 +75,9 @@ public function testPhpSession() $this->assertFalse($storage->isStarted()); $key = $storage->getMetadataBag()->getStorageKey(); - $this->assertFalse(isset($_SESSION[$key])); + $this->assertArrayNotHasKey($key, $_SESSION); $storage->start(); - $this->assertTrue(isset($_SESSION[$key])); + $this->assertArrayHasKey($key, $_SESSION); } public function testClear() diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php index 75355084d2608..186583d7be966 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php @@ -63,19 +63,28 @@ protected function createController($controller) return parent::createController($controller); } + $method = null; if (1 == substr_count($controller, ':')) { // controller in the "service:method" notation - list($service, $method) = explode(':', $controller, 2); + list($controller, $method) = explode(':', $controller, 2); + } + + if (!$this->container->has($controller)) { + $this->throwExceptionIfControllerWasRemoved($controller); + + throw new \LogicException(sprintf('Controller not found: service "%s" does not exist.', $controller)); + } - return array($this->container->get($service), $method); + $service = $this->container->get($controller); + if (null !== $method) { + return array($service, $method); } - if ($this->container->has($controller) && method_exists($service = $this->container->get($controller), '__invoke')) { - // invokable controller in the "service" notation - return $service; + if (!method_exists($service, '__invoke')) { + throw new \LogicException(sprintf('Controller "%s" cannot be called without a method name. Did you forget an "__invoke" method?', $controller)); } - throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller)); + return $service; } /** @@ -94,10 +103,19 @@ protected function instantiateController($class) } catch (\TypeError $e) { } - if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$class])) { - throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $class), 0, $e); - } + $this->throwExceptionIfControllerWasRemoved($class, $e); throw $e; } + + /** + * @param string $controller + * @param \Exception|\Throwable|null $previous + */ + private function throwExceptionIfControllerWasRemoved($controller, $previous = null) + { + if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) { + throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); + } + } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index 440b0d0025e65..ab50cec3531bf 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -50,7 +50,7 @@ public function process(ContainerBuilder $container) $action = substr(strrchr($controller, ':'), 1); $id = substr($controller, 0, -1 - strlen($action)); $controllerDef = $container->getDefinition($id); - foreach ($controllerDef->getMethodCalls() as list($method, $args)) { + foreach ($controllerDef->getMethodCalls() as list($method)) { if (0 === strcasecmp($action, $method)) { $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); break; diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 1a1a695af4262..5cb2c20f3a807 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -37,6 +37,7 @@ class DebugHandlersListener implements EventSubscriberInterface private $fileLinkFormat; private $scope; private $firstCall = true; + private $hasTerminatedWithException; /** * @param callable|null $exceptionHandler A handler that will be called on Exception @@ -63,14 +64,16 @@ public function __construct(callable $exceptionHandler = null, LoggerInterface $ */ public function configure(Event $event = null) { - if (!$this->firstCall) { + if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMasterRequest()) { return; } - $this->firstCall = false; + $this->firstCall = $this->hasTerminatedWithException = false; + + $handler = set_exception_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + if ($this->logger || null !== $this->throwAt) { - $handler = set_error_handler('var_dump'); - $handler = is_array($handler) ? $handler[0] : null; - restore_error_handler(); if ($handler instanceof ErrorHandler) { if ($this->logger) { $handler->setDefaultLogger($this->logger, $this->levels); @@ -99,8 +102,16 @@ public function configure(Event $event = null) } if (!$this->exceptionHandler) { if ($event instanceof KernelEvent) { - if (method_exists($event->getKernel(), 'terminateWithException')) { - $this->exceptionHandler = array($event->getKernel(), 'terminateWithException'); + if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { + $request = $event->getRequest(); + $hasRun = &$this->hasTerminatedWithException; + $this->exceptionHandler = function (\Exception $e) use ($kernel, $request, &$hasRun) { + if ($hasRun) { + throw $e; + } + $hasRun = true; + $kernel->terminateWithException($e, $request); + }; } } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { $output = $event->getOutput(); @@ -113,9 +124,6 @@ public function configure(Event $event = null) } } if ($this->exceptionHandler) { - $handler = set_exception_handler('var_dump'); - $handler = is_array($handler) ? $handler[0] : null; - restore_exception_handler(); if ($handler instanceof ErrorHandler) { $h = $handler->setExceptionHandler('var_dump') ?: $this->exceptionHandler; $handler->setExceptionHandler($h); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index 8eeb93192cc8f..fa0d4a86712b6 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -151,7 +151,7 @@ public function lookup(Request $request) return; } - list($req, $headers) = $match; + $headers = $match[1]; if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { return $this->restoreResponse($headers, $body); } diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 8d55ccde1c648..a8d35af1fe697 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -89,14 +89,12 @@ public function terminate(Request $request, Response $response) } /** - * @throws \LogicException If the request stack is empty - * * @internal */ - public function terminateWithException(\Exception $exception) + public function terminateWithException(\Exception $exception, Request $request = null) { - if (!$request = $this->requestStack->getMasterRequest()) { - throw new \LogicException('Request stack is empty', 0, $exception); + if (!$request = $request ?: $this->requestStack->getMasterRequest()) { + throw $exception; } $response = $this->handleException($exception, $request, self::MASTER_REQUEST); @@ -150,7 +148,7 @@ private function handleRaw(Request $request, $type = self::MASTER_REQUEST) $arguments = $event->getArguments(); // call controller - $response = call_user_func_array($controller, $arguments); + $response = \call_user_func_array($controller, $arguments); // view if (!$response instanceof Response) { diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 743140dab0ed3..94686a389ad77 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -67,11 +67,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.1'; - const VERSION_ID = 30401; + const VERSION = '3.4.2'; + const VERSION_ID = 30402; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; - const RELEASE_VERSION = 1; + const RELEASE_VERSION = 2; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2020'; @@ -423,7 +423,7 @@ public function setClassCache(array $classes) */ public function setAnnotatedClassCache(array $annotatedClasses) { - file_put_contents(($this->warmupDir ?: $this->getCacheDir()).'/annotations.map', sprintf('warmupDir ?: $this->getCacheDir()).'/annotations.map', sprintf('debug) { $collectedLogs = array(); - $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + $previousHandler = defined('PHPUNIT_COMPOSER_INSTALL'); + $previousHandler = $previousHandler ?: set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { return $previousHandler ? $previousHandler($type & ~E_WARNING, $message, $file, $line) : E_WARNING === $type; } @@ -634,13 +635,13 @@ protected function initializeContainer() $oldContainer = file_exists($cache->getPath()) && is_object($oldContainer = include $cache->getPath()) ? new \ReflectionClass($oldContainer) : false; } finally { - if ($this->debug) { + if (!$this->debug) { + error_reporting($errorLevel); + } elseif (true !== $previousHandler) { restore_error_handler(); file_put_contents($cacheDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); file_put_contents($cacheDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); - } else { - error_reporting($errorLevel); } } @@ -839,7 +840,7 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, - 'inline_class_loader_parameter' => !$this->loadClassCache && !class_exists(ClassCollectionLoader::class, false) ? 'container.dumper.inline_class_loader' : null, + 'inline_class_loader_parameter' => \PHP_VERSION_ID >= 70000 && !$this->loadClassCache && !class_exists(ClassCollectionLoader::class, false) ? 'container.dumper.inline_class_loader' : null, )); $rootCode = array_pop($content); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php index 83217aef558fc..0019123b6819c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php @@ -23,6 +23,10 @@ class ContainerControllerResolverTest extends ControllerResolverTest public function testGetControllerService() { $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with('foo') + ->will($this->returnValue(true)); $container->expects($this->once()) ->method('get') ->with('foo') @@ -175,6 +179,57 @@ public function testNonInstantiableControllerWithCorrespondingService() $this->assertSame(array($service, 'action'), $controller); } + /** + * @expectedException \LogicException + * @expectedExceptionMessage Controller "app.my_controller" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"? + */ + public function testExceptionWhenUsingRemovedControllerService() + { + $container = $this->getMockBuilder(Container::class)->getMock(); + $container->expects($this->at(0)) + ->method('has') + ->with('app.my_controller') + ->will($this->returnValue(false)) + ; + + $container->expects($this->atLeastOnce()) + ->method('getRemovedIds') + ->with() + ->will($this->returnValue(array('app.my_controller' => true))) + ; + + $resolver = $this->createControllerResolver(null, $container); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'app.my_controller'); + $resolver->getController($request); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Controller "app.my_controller" cannot be called without a method name. Did you forget an "__invoke" method? + */ + public function testExceptionWhenUsingControllerWithoutAnInvokeMethod() + { + $container = $this->getMockBuilder(Container::class)->getMock(); + $container->expects($this->once()) + ->method('has') + ->with('app.my_controller') + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with('app.my_controller') + ->will($this->returnValue(new ImpossibleConstructController('toto', 'controller'))) + ; + + $resolver = $this->createControllerResolver(null, $container); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'app.my_controller'); + $resolver->getController($request); + } + /** * @dataProvider getUndefinedControllers */ @@ -197,9 +252,9 @@ public function testGetControllerOnNonUndefinedFunction($controller, $exceptionN public function getUndefinedControllers() { return array( - array('foo', \LogicException::class, '/Unable to parse the controller name "foo"\./'), + array('foo', \LogicException::class, '/Controller not found: service "foo" does not exist\./'), array('oof::bar', \InvalidArgumentException::class, '/Class "oof" does not exist\./'), - array('stdClass', \LogicException::class, '/Unable to parse the controller name "stdClass"\./'), + array('stdClass', \LogicException::class, '/Controller not found: service "stdClass" does not exist\./'), array( 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar', \InvalidArgumentException::class, diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php index e642e3c33715f..fc4b92b5334dc 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -35,7 +35,7 @@ public function testDump() $this->assertSame(1, $collector->getDumpsCount()); $dump = $collector->getDumps('html'); - $this->assertTrue(isset($dump[0]['data'])); + $this->assertArrayHasKey('data', $dump[0]); $dump[0]['data'] = preg_replace('/^.*?
request('GET', '/');
         $this->assertHttpKernelIsNotCalled();
         $this->assertEquals(200, $this->response->getStatusCode());
-        $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2);
-        $this->assertTrue($this->response->headers->get('Age') > 0);
+        $this->assertLessThan(2, strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')));
+        $this->assertGreaterThan(0, $this->response->headers->get('Age'));
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
         $this->assertTraceContains('fresh');
         $this->assertTraceNotContains('store');
@@ -554,8 +554,8 @@ public function testHitsCachedResponseWithMaxAgeDirective()
         $this->request('GET', '/');
         $this->assertHttpKernelIsNotCalled();
         $this->assertEquals(200, $this->response->getStatusCode());
-        $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2);
-        $this->assertTrue($this->response->headers->get('Age') > 0);
+        $this->assertLessThan(2, strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')));
+        $this->assertGreaterThan(0, $this->response->headers->get('Age'));
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
         $this->assertTraceContains('fresh');
         $this->assertTraceNotContains('store');
@@ -618,8 +618,8 @@ public function testHitsCachedResponseWithSMaxAgeDirective()
         $this->request('GET', '/');
         $this->assertHttpKernelIsNotCalled();
         $this->assertEquals(200, $this->response->getStatusCode());
-        $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2);
-        $this->assertTrue($this->response->headers->get('Age') > 0);
+        $this->assertLessThan(2, strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')));
+        $this->assertGreaterThan(0, $this->response->headers->get('Age'));
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
         $this->assertTraceContains('fresh');
         $this->assertTraceNotContains('store');
@@ -793,7 +793,7 @@ public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent()
         $this->request('GET', '/');
         $this->assertHttpKernelIsCalled();
         $this->assertEquals(200, $this->response->getStatusCode());
-        $this->assertTrue($this->response->headers->get('Age') <= 1);
+        $this->assertLessThanOrEqual(1, $this->response->headers->get('Age'));
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
         $this->assertTraceContains('stale');
         $this->assertTraceNotContains('fresh');
@@ -831,7 +831,7 @@ public function testValidatesCachedResponsesWithLastModifiedAndNoFreshnessInform
         $this->assertEquals(200, $this->response->getStatusCode());
         $this->assertNotNull($this->response->headers->get('Last-Modified'));
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
-        $this->assertTrue($this->response->headers->get('Age') <= 1);
+        $this->assertLessThanOrEqual(1, $this->response->headers->get('Age'));
         $this->assertEquals('Hello World', $this->response->getContent());
         $this->assertTraceContains('stale');
         $this->assertTraceContains('valid');
@@ -881,7 +881,7 @@ public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation()
         $this->assertEquals(200, $this->response->getStatusCode());
         $this->assertNotNull($this->response->headers->get('ETag'));
         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
-        $this->assertTrue($this->response->headers->get('Age') <= 1);
+        $this->assertLessThanOrEqual(1, $this->response->headers->get('Age'));
         $this->assertEquals('Hello World', $this->response->getContent());
         $this->assertTraceContains('stale');
         $this->assertTraceContains('valid');
diff --git a/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php b/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php
index 253a4c91bd231..84ec19f70db21 100644
--- a/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/UriSignerTest.php
@@ -36,7 +36,7 @@ public function testCheck()
         $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar')));
         $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer')));
 
-        $this->assertTrue($signer->sign('http://example.com/foo?foo=bar&bar=foo') === $signer->sign('http://example.com/foo?bar=foo&foo=bar'));
+        $this->assertSame($signer->sign('http://example.com/foo?foo=bar&bar=foo'), $signer->sign('http://example.com/foo?bar=foo&foo=bar'));
     }
 
     public function testCheckWithDifferentArgSeparator()
diff --git a/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/IntlBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/IntlBundleReaderTest.php
index cfffd2b7cc0d8..3e992db42a1f8 100644
--- a/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/IntlBundleReaderTest.php
+++ b/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/IntlBundleReaderTest.php
@@ -36,7 +36,7 @@ public function testReadReturnsArrayAccess()
 
         $this->assertInstanceOf('\ArrayAccess', $data);
         $this->assertSame('Bar', $data['Foo']);
-        $this->assertFalse(isset($data['ExistsNot']));
+        $this->assertArrayNotHasKey('ExistsNot', $data);
     }
 
     public function testReadFollowsAlias()
@@ -46,7 +46,7 @@ public function testReadFollowsAlias()
 
         $this->assertInstanceOf('\ArrayAccess', $data);
         $this->assertSame('Bar', $data['Foo']);
-        $this->assertFalse(isset($data['ExistsNot']));
+        $this->assertArrayNotHasKey('ExistsNot', $data);
     }
 
     public function testReadDoesNotFollowFallback()
@@ -60,9 +60,9 @@ public function testReadDoesNotFollowFallback()
 
         $this->assertInstanceOf('\ArrayAccess', $data);
         $this->assertSame('Bam', $data['Baz']);
-        $this->assertFalse(isset($data['Foo']));
+        $this->assertArrayNotHasKey('Foo', $data);
         $this->assertNull($data['Foo']);
-        $this->assertFalse(isset($data['ExistsNot']));
+        $this->assertArrayNotHasKey('ExistsNot', $data);
     }
 
     public function testReadDoesNotFollowFallbackAlias()
@@ -76,9 +76,9 @@ public function testReadDoesNotFollowFallbackAlias()
 
         $this->assertInstanceOf('\ArrayAccess', $data);
         $this->assertSame('Bam', $data['Baz'], 'data from the aliased locale can be accessed');
-        $this->assertFalse(isset($data['Foo']));
+        $this->assertArrayNotHasKey('Foo', $data);
         $this->assertNull($data['Foo']);
-        $this->assertFalse(isset($data['ExistsNot']));
+        $this->assertArrayNotHasKey('ExistsNot', $data);
     }
 
     /**
diff --git a/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/JsonBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/JsonBundleReaderTest.php
index 2b6e6c4169e7f..dd0cf9cd872cd 100644
--- a/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/JsonBundleReaderTest.php
+++ b/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/JsonBundleReaderTest.php
@@ -35,7 +35,7 @@ public function testReadReturnsArray()
 
         $this->assertInternalType('array', $data);
         $this->assertSame('Bar', $data['Foo']);
-        $this->assertFalse(isset($data['ExistsNot']));
+        $this->assertArrayNotHasKey('ExistsNot', $data);
     }
 
     /**
diff --git a/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/PhpBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/PhpBundleReaderTest.php
index 954e2f04237c8..f6adae9b7de00 100644
--- a/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/PhpBundleReaderTest.php
+++ b/src/Symfony/Component/Intl/Tests/Data/Bundle/Reader/PhpBundleReaderTest.php
@@ -35,7 +35,7 @@ public function testReadReturnsArray()
 
         $this->assertInternalType('array', $data);
         $this->assertSame('Bar', $data['Foo']);
-        $this->assertFalse(isset($data['ExistsNot']));
+        $this->assertArrayNotHasKey('ExistsNot', $data);
     }
 
     /**
diff --git a/src/Symfony/Component/Intl/Tests/Data/Util/RingBufferTest.php b/src/Symfony/Component/Intl/Tests/Data/Util/RingBufferTest.php
index bbaecfcbd668f..f13bf36c96d19 100644
--- a/src/Symfony/Component/Intl/Tests/Data/Util/RingBufferTest.php
+++ b/src/Symfony/Component/Intl/Tests/Data/Util/RingBufferTest.php
@@ -34,8 +34,8 @@ public function testWriteWithinBuffer()
         $this->buffer[0] = 'foo';
         $this->buffer['bar'] = 'baz';
 
-        $this->assertTrue(isset($this->buffer[0]));
-        $this->assertTrue(isset($this->buffer['bar']));
+        $this->assertArrayHasKey(0, $this->buffer);
+        $this->assertArrayHasKey('bar', $this->buffer);
         $this->assertSame('foo', $this->buffer[0]);
         $this->assertSame('baz', $this->buffer['bar']);
     }
@@ -46,8 +46,8 @@ public function testWritePastBuffer()
         $this->buffer['bar'] = 'baz';
         $this->buffer[2] = 'bam';
 
-        $this->assertTrue(isset($this->buffer['bar']));
-        $this->assertTrue(isset($this->buffer[2]));
+        $this->assertArrayHasKey('bar', $this->buffer);
+        $this->assertArrayHasKey(2, $this->buffer);
         $this->assertSame('baz', $this->buffer['bar']);
         $this->assertSame('bam', $this->buffer[2]);
     }
@@ -62,14 +62,14 @@ public function testReadNonExistingFails()
 
     public function testQueryNonExisting()
     {
-        $this->assertFalse(isset($this->buffer['foo']));
+        $this->assertArrayNotHasKey('foo', $this->buffer);
     }
 
     public function testUnsetNonExistingSucceeds()
     {
         unset($this->buffer['foo']);
 
-        $this->assertFalse(isset($this->buffer['foo']));
+        $this->assertArrayNotHasKey('foo', $this->buffer);
     }
 
     /**
@@ -86,7 +86,7 @@ public function testReadOverwrittenFails()
 
     public function testQueryOverwritten()
     {
-        $this->assertFalse(isset($this->buffer[0]));
+        $this->assertArrayNotHasKey(0, $this->buffer);
     }
 
     public function testUnsetOverwrittenSucceeds()
@@ -97,6 +97,6 @@ public function testUnsetOverwrittenSucceeds()
 
         unset($this->buffer[0]);
 
-        $this->assertFalse(isset($this->buffer[0]));
+        $this->assertArrayNotHasKey(0, $this->buffer);
     }
 }
diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php
index d9b2dd4f5d4ea..581cf153ed652 100644
--- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php
+++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php
@@ -48,7 +48,7 @@ public function count()
             return $count;
         }
 
-        throw new LdapException(sprintf('Error while retrieving entry count: %s', ldap_error($this->connection->getResource())));
+        throw new LdapException(sprintf('Error while retrieving entry count: %s.', ldap_error($this->connection->getResource())));
     }
 
     public function getIterator()
@@ -62,7 +62,7 @@ public function getIterator()
         }
 
         if (false === $current) {
-            throw new LdapException(sprintf('Could not rewind entries array: %s', ldap_error($con)));
+            throw new LdapException(sprintf('Could not rewind entries array: %s.', ldap_error($con)));
         }
 
         yield $this->getSingleEntry($con, $current);
@@ -105,7 +105,7 @@ private function getSingleEntry($con, $current)
         $attributes = ldap_get_attributes($con, $current);
 
         if (false === $attributes) {
-            throw new LdapException(sprintf('Could not fetch attributes: %s', ldap_error($con)));
+            throw new LdapException(sprintf('Could not fetch attributes: %s.', ldap_error($con)));
         }
 
         $attributes = $this->cleanupAttributes($attributes);
@@ -113,7 +113,7 @@ private function getSingleEntry($con, $current)
         $dn = ldap_get_dn($con, $current);
 
         if (false === $dn) {
-            throw new LdapException(sprintf('Could not fetch DN: %s', ldap_error($con)));
+            throw new LdapException(sprintf('Could not fetch DN: %s.', ldap_error($con)));
         }
 
         return new Entry($dn, $attributes);
diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
index d705b3bce9d13..1a6ea28ac6505 100644
--- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
+++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php
@@ -134,11 +134,11 @@ private function connect()
         }
 
         if (false === $this->connection) {
-            throw new LdapException(sprintf('Could not connect to Ldap server: %s', ldap_error($this->connection)));
+            throw new LdapException(sprintf('Could not connect to Ldap server: %s.', ldap_error($this->connection)));
         }
 
         if ('tls' === $this->config['encryption'] && false === ldap_start_tls($this->connection)) {
-            throw new LdapException(sprintf('Could not initiate TLS connection: %s', ldap_error($this->connection)));
+            throw new LdapException(sprintf('Could not initiate TLS connection: %s.', ldap_error($this->connection)));
         }
     }
 
diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php
index 95d65e020a63f..6d878aa699061 100644
--- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php
+++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php
@@ -65,7 +65,7 @@ public static function getOption($name)
         $constantName = self::getOptionName($name);
 
         if (!defined($constantName)) {
-            throw new LdapException(sprintf('Unknown option "%s"', $name));
+            throw new LdapException(sprintf('Unknown option "%s".', $name));
         }
 
         return constant($constantName);
diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php
index 225414884bea2..871c4b049562d 100644
--- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php
+++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php
@@ -38,7 +38,7 @@ public function add(Entry $entry)
         $con = $this->getConnectionResource();
 
         if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) {
-            throw new LdapException(sprintf('Could not add entry "%s": %s', $entry->getDn(), ldap_error($con)));
+            throw new LdapException(sprintf('Could not add entry "%s": %s.', $entry->getDn(), ldap_error($con)));
         }
 
         return $this;
@@ -52,7 +52,7 @@ public function update(Entry $entry)
         $con = $this->getConnectionResource();
 
         if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) {
-            throw new LdapException(sprintf('Could not update entry "%s": %s', $entry->getDn(), ldap_error($con)));
+            throw new LdapException(sprintf('Could not update entry "%s": %s.', $entry->getDn(), ldap_error($con)));
         }
     }
 
@@ -64,7 +64,7 @@ public function remove(Entry $entry)
         $con = $this->getConnectionResource();
 
         if (!@ldap_delete($con, $entry->getDn())) {
-            throw new LdapException(sprintf('Could not remove entry "%s": %s', $entry->getDn(), ldap_error($con)));
+            throw new LdapException(sprintf('Could not remove entry "%s": %s.', $entry->getDn(), ldap_error($con)));
         }
     }
 
@@ -76,7 +76,7 @@ public function rename(Entry $entry, $newRdn, $removeOldRdn = true)
         $con = $this->getConnectionResource();
 
         if (!@ldap_rename($con, $entry->getDn(), $newRdn, null, $removeOldRdn)) {
-            throw new LdapException(sprintf('Could not rename entry "%s" to "%s": %s', $entry->getDn(), $newRdn, ldap_error($con)));
+            throw new LdapException(sprintf('Could not rename entry "%s" to "%s": %s.', $entry->getDn(), $newRdn, ldap_error($con)));
         }
     }
 
diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php
index 472ccf631b282..09cb42c7d38ee 100644
--- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php
+++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php
@@ -45,7 +45,7 @@ public function __destruct()
         $this->search = null;
 
         if (!$success) {
-            throw new LdapException(sprintf('Could not free results: %s', ldap_error($con)));
+            throw new LdapException(sprintf('Could not free results: %s.', ldap_error($con)));
         }
     }
 
@@ -73,7 +73,7 @@ public function execute()
                     $func = 'ldap_search';
                     break;
                 default:
-                    throw new LdapException(sprintf('Could not search in scope %s', $this->options['scopen']));
+                    throw new LdapException(sprintf('Could not search in scope "%s".', $this->options['scope']));
             }
 
             $this->search = @$func(
@@ -89,7 +89,7 @@ public function execute()
         }
 
         if (false === $this->search) {
-            throw new LdapException(sprintf('Could not complete search with dn "%s", query "%s" and filters "%s"', $this->dn, $this->query, implode(',', $this->options['filter'])));
+            throw new LdapException(sprintf('Could not complete search with dn "%s", query "%s" and filters "%s".', $this->dn, $this->query, implode(',', $this->options['filter'])));
         }
 
         return new Collection($this->connection, $this);
diff --git a/src/Symfony/Component/Lock/Store/MemcachedStore.php b/src/Symfony/Component/Lock/Store/MemcachedStore.php
index beaad69962084..8e9db10cd036f 100644
--- a/src/Symfony/Component/Lock/Store/MemcachedStore.php
+++ b/src/Symfony/Component/Lock/Store/MemcachedStore.php
@@ -24,12 +24,6 @@
  */
 class MemcachedStore implements StoreInterface
 {
-    private static $defaultClientOptions = array(
-        'persistent_id' => null,
-        'username' => null,
-        'password' => null,
-    );
-
     private $memcached;
     private $initialTtl;
     /** @var bool */
diff --git a/src/Symfony/Component/Lock/StoreInterface.php b/src/Symfony/Component/Lock/StoreInterface.php
index 428786b4c8bf6..985c4476d7da6 100644
--- a/src/Symfony/Component/Lock/StoreInterface.php
+++ b/src/Symfony/Component/Lock/StoreInterface.php
@@ -24,19 +24,15 @@ interface StoreInterface
     /**
      * Stores the resource if it's not locked by someone else.
      *
-     * @param Key $key key to lock
-     *
      * @throws LockConflictedException
      */
     public function save(Key $key);
 
     /**
-     * Waits a key becomes free, then stores the resource.
+     * Waits until a key becomes free, then stores the resource.
      *
      * If the store does not support this feature it should throw a NotSupportedException.
      *
-     * @param Key $key key to lock
-     *
      * @throws LockConflictedException
      * @throws NotSupportedException
      */
@@ -47,7 +43,6 @@ public function waitAndSave(Key $key);
      *
      * If the store does not support this feature it should throw a NotSupportedException.
      *
-     * @param Key   $key key to lock
      * @param float $ttl amount of second to keep the lock in the store
      *
      * @throws LockConflictedException
@@ -57,16 +52,12 @@ public function putOffExpiration(Key $key, $ttl);
 
     /**
      * Removes a resource from the storage.
-     *
-     * @param Key $key key to remove
      */
     public function delete(Key $key);
 
     /**
      * Returns whether or not the resource exists in the storage.
      *
-     * @param Key $key key to remove
-     *
      * @return bool
      */
     public function exists(Key $key);
diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php
index b95e039dc32cf..fb15b1843b809 100644
--- a/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php
+++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php
@@ -1504,12 +1504,12 @@ public function testArrayAccess()
         });
 
         $this->resolver->setDefault('lazy2', function (Options $options) {
-            Assert::assertTrue(isset($options['default1']));
-            Assert::assertTrue(isset($options['default2']));
-            Assert::assertTrue(isset($options['required']));
-            Assert::assertTrue(isset($options['lazy1']));
-            Assert::assertTrue(isset($options['lazy2']));
-            Assert::assertFalse(isset($options['defined']));
+            Assert::assertArrayHasKey('default1', $options);
+            Assert::assertArrayHasKey('default2', $options);
+            Assert::assertArrayHasKey('required', $options);
+            Assert::assertArrayHasKey('lazy1', $options);
+            Assert::assertArrayHasKey('lazy2', $options);
+            Assert::assertArrayNotHasKey('defined', $options);
 
             Assert::assertSame(0, $options['default1']);
             Assert::assertSame(42, $options['default2']);
diff --git a/src/Symfony/Component/Process/Pipes/AbstractPipes.php b/src/Symfony/Component/Process/Pipes/AbstractPipes.php
index 5fdd41d050e6c..ba79c32702df8 100644
--- a/src/Symfony/Component/Process/Pipes/AbstractPipes.php
+++ b/src/Symfony/Component/Process/Pipes/AbstractPipes.php
@@ -119,7 +119,7 @@ protected function write()
         $w = array($this->pipes[0]);
 
         // let's have a look if something changed in streams
-        if (false === $n = @stream_select($r, $w, $e, 0, 0)) {
+        if (false === @stream_select($r, $w, $e, 0, 0)) {
             return;
         }
 
diff --git a/src/Symfony/Component/Process/Pipes/UnixPipes.php b/src/Symfony/Component/Process/Pipes/UnixPipes.php
index 4e0e452b4a044..78ffee7b031c1 100644
--- a/src/Symfony/Component/Process/Pipes/UnixPipes.php
+++ b/src/Symfony/Component/Process/Pipes/UnixPipes.php
@@ -99,7 +99,7 @@ public function readAndWrite($blocking, $close = false)
         unset($r[0]);
 
         // let's have a look if something changed in streams
-        if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+        if (($r || $w) && false === @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
             // if a system call has been interrupted, forget about it, let's try again
             // otherwise, an error occurred, let's reset pipes
             if (!$this->hasSystemCallBeenInterrupted()) {
diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php
index 7f490785e0783..22af36e7c4864 100644
--- a/src/Symfony/Component/Process/Process.php
+++ b/src/Symfony/Component/Process/Process.php
@@ -304,19 +304,16 @@ public function start(callable $callback = null/*, array $env = array()*/)
             $inheritEnv = true;
         }
 
-        $envBackup = array();
         if (null !== $env && $inheritEnv) {
-            foreach ($env as $k => $v) {
-                $envBackup[$k] = getenv($k);
-                putenv(false === $v || null === $v ? $k : "$k=$v");
-            }
-            $env = null;
+            $env += $this->getDefaultEnv();
         } elseif (null !== $env) {
             @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED);
+        } else {
+            $env = $this->getDefaultEnv();
         }
         if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
             $this->options['bypass_shell'] = true;
-            $commandline = $this->prepareWindowsCommandLine($commandline, $envBackup, $env);
+            $commandline = $this->prepareWindowsCommandLine($commandline, $env);
         } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
             // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
             $descriptors[3] = array('pipe', 'w');
@@ -331,19 +328,11 @@ public function start(callable $callback = null/*, array $env = array()*/)
         }
 
         if (!is_dir($this->cwd)) {
-            if ('\\' === DIRECTORY_SEPARATOR) {
-                throw new RuntimeException('The provided cwd does not exist.');
-            }
-
             @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since version 3.4 and will be removed in 4.0.', E_USER_DEPRECATED);
         }
 
         $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $env, $this->options);
 
-        foreach ($envBackup as $k => $v) {
-            putenv(false === $v ? $k : "$k=$v");
-        }
-
         if (!is_resource($this->process)) {
             throw new RuntimeException('Unable to launch a new process.');
         }
@@ -1631,7 +1620,7 @@ private function doSignal($signal, $throwException)
         return true;
     }
 
-    private function prepareWindowsCommandLine($cmd, array &$envBackup, array &$env = null)
+    private function prepareWindowsCommandLine($cmd, array &$env)
     {
         $uid = uniqid('', true);
         $varCount = 0;
@@ -1644,7 +1633,7 @@ private function prepareWindowsCommandLine($cmd, array &$envBackup, array &$env
                     [^"%!^]*+
                 )++
             ) | [^"]*+ )"/x',
-            function ($m) use (&$envBackup, &$env, &$varCache, &$varCount, $uid) {
+            function ($m) use (&$env, &$varCache, &$varCount, $uid) {
                 if (!isset($m[1])) {
                     return $m[0];
                 }
@@ -1662,13 +1651,7 @@ function ($m) use (&$envBackup, &$env, &$varCache, &$varCount, $uid) {
                 $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
                 $var = $uid.++$varCount;
 
-                if (null === $env) {
-                    putenv("$var=$value");
-                } else {
-                    $env[$var] = $value;
-                }
-
-                $envBackup[$var] = false;
+                $env[$var] = $value;
 
                 return $varCache[$m[0]] = '!'.$var.'!';
             },
@@ -1736,4 +1719,27 @@ private function escapeArgument($argument)
 
         return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"';
     }
+
+    private function getDefaultEnv()
+    {
+        if (\PHP_VERSION_ID >= 70100) {
+            $env = getenv();
+        } else {
+            $env = array();
+
+            foreach ($_SERVER as $k => $v) {
+                if (is_string($v) && false !== $v = getenv($k)) {
+                    $env[$k] = $v;
+                }
+            }
+        }
+
+        foreach ($_ENV as $k => $v) {
+            if (is_string($v)) {
+                $env[$k] = $v;
+            }
+        }
+
+        return $env;
+    }
 }
diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php
index 6af470b58acd9..a38287bcd9781 100644
--- a/src/Symfony/Component/Process/Tests/ProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/ProcessTest.php
@@ -54,10 +54,6 @@ protected function tearDown()
      */
     public function testInvalidCwd()
     {
-        if ('\\' === DIRECTORY_SEPARATOR) {
-            $this->markTestSkipped('Windows handles this automatically.');
-        }
-
         // Check that it works fine if the CWD exists
         $cmd = new Process('echo test', __DIR__);
         $cmd->run();
@@ -66,28 +62,6 @@ public function testInvalidCwd()
         $cmd->run();
     }
 
-    /**
-     * @expectedException \Symfony\Component\Process\Exception\RuntimeException
-     * @expectedExceptionMessage The provided cwd does not exist.
-     */
-    public function testInvalidCwdOnWindows()
-    {
-        if ('\\' !== DIRECTORY_SEPARATOR) {
-            $this->markTestSkipped('Unix handles this automatically.');
-        }
-
-        try {
-            // Check that it works fine if the CWD exists
-            $cmd = new Process('echo test', __DIR__);
-            $cmd->run();
-        } catch (\Exception $e) {
-            $this->fail($e);
-        }
-
-        $cmd = new Process('echo test', __DIR__.'/notfound/');
-        $cmd->run();
-    }
-
     public function testThatProcessDoesNotThrowWarningDuringRun()
     {
         if ('\\' === DIRECTORY_SEPARATOR) {
@@ -597,7 +571,7 @@ public function testUpdateStatus()
     {
         $process = $this->getProcess('echo foo');
         $process->run();
-        $this->assertTrue(strlen($process->getOutput()) > 0);
+        $this->assertGreaterThan(0, strlen($process->getOutput()));
     }
 
     public function testGetExitCodeIsNullOnStart()
@@ -1446,6 +1420,7 @@ public function testSetBadEnv()
     public function testEnvBackupDoesNotDeleteExistingVars()
     {
         putenv('existing_var=foo');
+        $_ENV['existing_var'] = 'foo';
         $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
         $process->setEnv(array('existing_var' => 'bar', 'new_test_var' => 'foo'));
         $process->inheritEnvironmentVariables();
@@ -1455,6 +1430,9 @@ public function testEnvBackupDoesNotDeleteExistingVars()
         $this->assertSame('foo', $process->getOutput());
         $this->assertSame('foo', getenv('existing_var'));
         $this->assertFalse(getenv('new_test_var'));
+
+        putenv('existing_var');
+        unset($_ENV['existing_var']);
     }
 
     public function testEnvIsInherited()
@@ -1462,6 +1440,7 @@ public function testEnvIsInherited()
         $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
 
         putenv('FOO=BAR');
+        $_ENV['FOO'] = 'BAR';
 
         $process->run();
 
@@ -1469,6 +1448,9 @@ public function testEnvIsInherited()
         $env = array_intersect_key(unserialize($process->getOutput()), $expected);
 
         $this->assertEquals($expected, $env);
+
+        putenv('FOO');
+        unset($_ENV['FOO']);
     }
 
     /**
@@ -1479,6 +1461,7 @@ public function testInheritEnvDisabled()
         $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
 
         putenv('FOO=BAR');
+        $_ENV['FOO'] = 'BAR';
 
         $this->assertSame($process, $process->inheritEnvironmentVariables(false));
         $this->assertFalse($process->areEnvironmentVariablesInherited());
@@ -1490,6 +1473,9 @@ public function testInheritEnvDisabled()
         unset($expected['FOO']);
 
         $this->assertSame($expected, $env);
+
+        putenv('FOO');
+        unset($_ENV['FOO']);
     }
 
     public function testGetCommandLine()
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
index aaa352d22c069..acb9eddb34de8 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
@@ -96,10 +96,10 @@ private function generateMatchMethod($supportsRedirections)
         $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n");
 
         return <<context;
         \$request = \$this->request;
@@ -362,7 +362,7 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
         if ($hasTrailingSlash) {
             $code .= <<redirect(\$pathinfo.'/', '$name'));
+                return array_replace(\$ret, \$this->redirect(\$rawPathinfo.'/', '$name'));
             }
 
 
@@ -377,7 +377,7 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
             $code .= <<redirect(\$pathinfo, '$name', key(\$requiredSchemes)));
+                return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)));
             }
 
 
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 0fa8ea45e0a15..839c7d137255b 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher0.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
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 51fd29e8629f1..5d7dca8e55d07 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
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 fa1a1a8db4718..49ce082a81733 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
@@ -84,7 +84,7 @@ public function match($pathinfo)
                 if ('/test/baz3' === $trimmedPathinfo) {
                     $ret = array('_route' => 'baz3');
                     if (substr($pathinfo, -1) !== '/') {
-                        return array_replace($ret, $this->redirect($pathinfo.'/', 'baz3'));
+                        return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz3'));
                     }
 
                     return $ret;
@@ -96,7 +96,7 @@ public function match($pathinfo)
             if (preg_match('#^/test/(?P[^/]++)/?$#s', $pathinfo, $matches)) {
                 $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ());
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'baz4'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'baz4'));
                 }
 
                 return $ret;
@@ -180,7 +180,7 @@ public function match($pathinfo)
             if ('/multi/hey' === $trimmedPathinfo) {
                 $ret = array('_route' => 'hey');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'hey'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'hey'));
                 }
 
                 return $ret;
@@ -327,7 +327,7 @@ public function match($pathinfo)
             $ret = array('_route' => 'secure');
             $requiredSchemes = array (  'https' => 0,);
             if (!isset($requiredSchemes[$scheme])) {
-                return array_replace($ret, $this->redirect($pathinfo, 'secure', key($requiredSchemes)));
+                return array_replace($ret, $this->redirect($rawPathinfo, 'secure', key($requiredSchemes)));
             }
 
             return $ret;
@@ -338,7 +338,7 @@ public function match($pathinfo)
             $ret = array('_route' => 'nonsecure');
             $requiredSchemes = array (  'http' => 0,);
             if (!isset($requiredSchemes[$scheme])) {
-                return array_replace($ret, $this->redirect($pathinfo, 'nonsecure', key($requiredSchemes)));
+                return array_replace($ret, $this->redirect($rawPathinfo, 'nonsecure', key($requiredSchemes)));
             }
 
             return $ret;
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 c982a454347ad..ae54956312554 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
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 6aefd6938272c..50da489fb2a0b 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
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 c7ee2806b8ba3..51be5b0bc886f 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
@@ -58,7 +58,7 @@ public function match($pathinfo)
             if ('/a/44' === $trimmedPathinfo) {
                 $ret = array('_route' => 'a_fourth');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'a_fourth'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fourth'));
                 }
 
                 return $ret;
@@ -68,7 +68,7 @@ public function match($pathinfo)
             if ('/a/55' === $trimmedPathinfo) {
                 $ret = array('_route' => 'a_fifth');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'a_fifth'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_fifth'));
                 }
 
                 return $ret;
@@ -78,7 +78,7 @@ public function match($pathinfo)
             if ('/a/66' === $trimmedPathinfo) {
                 $ret = array('_route' => 'a_sixth');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'a_sixth'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'a_sixth'));
                 }
 
                 return $ret;
@@ -96,7 +96,7 @@ public function match($pathinfo)
             if ('/nested/group/a' === $trimmedPathinfo) {
                 $ret = array('_route' => 'nested_a');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'nested_a'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_a'));
                 }
 
                 return $ret;
@@ -106,7 +106,7 @@ public function match($pathinfo)
             if ('/nested/group/b' === $trimmedPathinfo) {
                 $ret = array('_route' => 'nested_b');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'nested_b'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_b'));
                 }
 
                 return $ret;
@@ -116,7 +116,7 @@ public function match($pathinfo)
             if ('/nested/group/c' === $trimmedPathinfo) {
                 $ret = array('_route' => 'nested_c');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'nested_c'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'nested_c'));
                 }
 
                 return $ret;
@@ -129,7 +129,7 @@ public function match($pathinfo)
             if ('/slashed/group' === $trimmedPathinfo) {
                 $ret = array('_route' => 'slashed_a');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'slashed_a'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_a'));
                 }
 
                 return $ret;
@@ -139,7 +139,7 @@ public function match($pathinfo)
             if ('/slashed/group/b' === $trimmedPathinfo) {
                 $ret = array('_route' => 'slashed_b');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'slashed_b'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_b'));
                 }
 
                 return $ret;
@@ -149,7 +149,7 @@ public function match($pathinfo)
             if ('/slashed/group/c' === $trimmedPathinfo) {
                 $ret = array('_route' => 'slashed_c');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'slashed_c'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'slashed_c'));
                 }
 
                 return $ret;
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 bb9d80b55181b..933525699c0e7 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
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 42d5778b5aeb4..bceee6f3a329d 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php
@@ -15,10 +15,10 @@ public function __construct(RequestContext $context)
         $this->context = $context;
     }
 
-    public function match($pathinfo)
+    public function match($rawPathinfo)
     {
         $allow = array();
-        $pathinfo = rawurldecode($pathinfo);
+        $pathinfo = rawurldecode($rawPathinfo);
         $trimmedPathinfo = rtrim($pathinfo, '/');
         $context = $this->context;
         $request = $this->request;
@@ -35,7 +35,7 @@ public function match($pathinfo)
             if ('/trailing/simple/no-methods' === $trimmedPathinfo) {
                 $ret = array('_route' => 'simple_trailing_slash_no_methods');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'simple_trailing_slash_no_methods'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_no_methods'));
                 }
 
                 return $ret;
@@ -50,7 +50,7 @@ public function match($pathinfo)
 
                 $ret = array('_route' => 'simple_trailing_slash_GET_method');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'simple_trailing_slash_GET_method'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_GET_method'));
                 }
 
                 return $ret;
@@ -66,7 +66,7 @@ public function match($pathinfo)
 
                 $ret = array('_route' => 'simple_trailing_slash_HEAD_method');
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'simple_trailing_slash_HEAD_method'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'simple_trailing_slash_HEAD_method'));
                 }
 
                 return $ret;
@@ -91,7 +91,7 @@ public function match($pathinfo)
             if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#s', $pathinfo, $matches)) {
                 $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ());
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'regex_trailing_slash_no_methods'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_no_methods'));
                 }
 
                 return $ret;
@@ -106,7 +106,7 @@ public function match($pathinfo)
 
                 $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ());
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'regex_trailing_slash_GET_method'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_GET_method'));
                 }
 
                 return $ret;
@@ -122,7 +122,7 @@ public function match($pathinfo)
 
                 $ret = $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ());
                 if (substr($pathinfo, -1) !== '/') {
-                    return array_replace($ret, $this->redirect($pathinfo.'/', 'regex_trailing_slash_HEAD_method'));
+                    return array_replace($ret, $this->redirect($rawPathinfo.'/', 'regex_trailing_slash_HEAD_method'));
                 }
 
                 return $ret;
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
index 8b32e0cd3d194..37ac752a6bf91 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
@@ -13,11 +13,39 @@
 
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
+use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface;
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\RequestContext;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
 class PhpMatcherDumperTest extends TestCase
 {
+    /**
+     * @var string
+     */
+    private $matcherClass;
+
+    /**
+     * @var string
+     */
+    private $dumpPath;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->matcherClass = uniqid('ProjectUrlMatcher');
+        $this->dumpPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_matcher.'.$this->matcherClass.'.php';
+    }
+
+    protected function tearDown()
+    {
+        parent::tearDown();
+
+        @unlink($this->dumpPath);
+    }
+
     /**
      * @expectedException \LogicException
      */
@@ -36,6 +64,23 @@ public function testDumpWhenSchemeIsUsedWithoutAProperDumper()
         $dumper->dump();
     }
 
+    public function testRedirectPreservesUrlEncoding()
+    {
+        $collection = new RouteCollection();
+        $collection->add('foo', new Route('/foo:bar/'));
+
+        $class = $this->generateDumpedMatcher($collection, true);
+
+        $matcher = $this->getMockBuilder($class)
+                        ->setMethods(array('redirect'))
+                        ->setConstructorArgs(array(new RequestContext()))
+                        ->getMock();
+        
+        $matcher->expects($this->once())->method('redirect')->with('/foo%3Abar/', 'foo')->willReturn(array());
+
+        $matcher->match('/foo%3Abar');
+    }
+
     /**
      * @dataProvider getRouteCollections
      */
@@ -384,4 +429,31 @@ public function getRouteCollections()
            array($trailingSlashCollection, 'url_matcher7.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')),
         );
     }
+
+    /**
+     * @param $dumper
+     */
+    private function generateDumpedMatcher(RouteCollection $collection, $redirectableStub = false)
+    {
+        $options = array('class' => $this->matcherClass);
+
+        if ($redirectableStub) {
+            $options['base_class'] = '\Symfony\Component\Routing\Tests\Matcher\Dumper\RedirectableUrlMatcherStub';
+        }
+
+        $dumper = new PhpMatcherDumper($collection);
+        $code = $dumper->dump($options);
+
+        file_put_contents($this->dumpPath, $code);
+        include $this->dumpPath;
+
+        return $this->matcherClass;
+    }
+}
+
+abstract class RedirectableUrlMatcherStub extends UrlMatcher implements RedirectableUrlMatcherInterface
+{
+    public function redirect($path, $route, $scheme = null)
+    {
+    }
 }
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
index ddd2133e9614e..0948e002adf8a 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
@@ -98,4 +98,14 @@ public function testSlashRedirectWithParams()
         ;
         $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'redirect' => 'value'), $matcher->match('/foo/baz'));
     }
+
+    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->expects($this->once())->method('redirect')->with('/foo%3Abar/')->willReturn(array());
+        $matcher->match('/foo%3Abar');
+    }
 }
diff --git a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php
index c88bce0081941..0362ccc4a277e 100644
--- a/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php
+++ b/src/Symfony/Component/Security/Core/Encoder/Argon2iPasswordEncoder.php
@@ -22,9 +22,15 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE
 {
     public static function isSupported()
     {
-        return (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I'))
-            || \function_exists('sodium_crypto_pwhash_str')
-            || \extension_loaded('libsodium');
+        if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) {
+            return true;
+        }
+
+        if (\class_exists('ParagonIE_Sodium_Compat') && \method_exists('ParagonIE_Sodium_Compat', 'crypto_pwhash_is_available')) {
+            return \ParagonIE_Sodium_Compat::crypto_pwhash_is_available();
+        }
+
+        return \function_exists('sodium_crypto_pwhash_str') || \extension_loaded('libsodium');
     }
 
     /**
diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php
index ca28d53c5cc42..e3e1c447af3cd 100644
--- a/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php
@@ -61,9 +61,9 @@ public function testVoteAuthenticatesTokenIfNecessary()
             ->will($this->returnValue(true));
 
         // first run the token has not been re-authenticated yet, after isGranted is called, it should be equal
-        $this->assertFalse($newToken === $this->tokenStorage->getToken());
+        $this->assertNotSame($newToken, $this->tokenStorage->getToken());
         $this->assertTrue($this->authorizationChecker->isGranted('foo'));
-        $this->assertTrue($newToken === $this->tokenStorage->getToken());
+        $this->assertSame($newToken, $this->tokenStorage->getToken());
     }
 
     /**
@@ -90,7 +90,7 @@ public function testIsGranted($decide)
             ->method('decide')
             ->will($this->returnValue($decide));
         $this->tokenStorage->setToken($token);
-        $this->assertTrue($decide === $this->authorizationChecker->isGranted('ROLE_FOO'));
+        $this->assertSame($decide, $this->authorizationChecker->isGranted('ROLE_FOO'));
     }
 
     public function isGrantedProvider()
diff --git a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php
index df0207ccf30fb..cce2c14af1f55 100644
--- a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php
+++ b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php
@@ -115,6 +115,7 @@ public function decode($data, $format, array $context = array())
 
         $headers = null;
         $nbHeaders = 0;
+        $headerCount = array();
         $result = array();
 
         list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
@@ -126,7 +127,9 @@ public function decode($data, $format, array $context = array())
                 $nbHeaders = $nbCols;
 
                 foreach ($cols as $col) {
-                    $headers[] = explode($keySeparator, $col);
+                    $header = explode($keySeparator, $col);
+                    $headers[] = $header;
+                    $headerCount[] = count($header);
                 }
 
                 continue;
@@ -134,7 +137,7 @@ public function decode($data, $format, array $context = array())
 
             $item = array();
             for ($i = 0; ($i < $nbCols) && ($i < $nbHeaders); ++$i) {
-                $depth = count($headers[$i]);
+                $depth = $headerCount[$i];
                 $arr = &$item;
                 for ($j = 0; $j < $depth; ++$j) {
                     // Handle nested arrays
diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php
index a533e703d523f..bee1f7d4449f4 100644
--- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php
+++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php
@@ -24,7 +24,6 @@ class JsonDecode implements DecoderInterface
 
     private $associative;
     private $recursionDepth;
-    private $lastError = JSON_ERROR_NONE;
 
     /**
      * Constructs a new JsonDecode instance.
@@ -75,7 +74,7 @@ public function decode($data, $format, array $context = array())
 
         $decodedData = json_decode($data, $associative, $recursionDepth, $options);
 
-        if (JSON_ERROR_NONE !== $this->lastError = json_last_error()) {
+        if (JSON_ERROR_NONE !== json_last_error()) {
             throw new NotEncodableValueException(json_last_error_msg());
         }
 
diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php
index ca42abc5a465d..eff486e1c85f1 100644
--- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php
+++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php
@@ -21,7 +21,6 @@
 class JsonEncode implements EncoderInterface
 {
     private $options;
-    private $lastError = JSON_ERROR_NONE;
 
     public function __construct($bitmask = 0)
     {
@@ -39,7 +38,7 @@ public function encode($data, $format, array $context = array())
 
         $encodedJson = json_encode($data, $context['json_encode_options']);
 
-        if (JSON_ERROR_NONE !== $this->lastError = json_last_error()) {
+        if (JSON_ERROR_NONE !== json_last_error()) {
             throw new NotEncodableValueException(json_last_error_msg());
         }
 
diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
index 0290dd9eef548..dd4a9b35c6f22 100644
--- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
@@ -399,6 +399,8 @@ protected function createChildContext(array $parentContext, $attribute)
     {
         if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
             $parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];
+        } else {
+            unset($parentContext[self::ATTRIBUTES]);
         }
 
         return $parentContext;
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
index 0d4e880ec7f18..f2d389a0df07e 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
@@ -683,6 +683,16 @@ public function testAttributesContextNormalize()
             ),
             $serializer->normalize($objectDummy, null, $context)
         );
+
+        $context = array('attributes' => array('foo', 'baz', 'object'));
+        $this->assertEquals(
+            array(
+                'foo' => 'foo',
+                'baz' => true,
+                'object' => array('foo' => 'innerFoo', 'bar' => 'innerBar'),
+            ),
+            $serializer->normalize($objectDummy, null, $context)
+        );
     }
 
     public function testAttributesContextDenormalize()
diff --git a/src/Symfony/Component/Templating/Tests/Helper/HelperTest.php b/src/Symfony/Component/Templating/Tests/Helper/HelperTest.php
index 8921ff19c81fc..dec9082efc30f 100644
--- a/src/Symfony/Component/Templating/Tests/Helper/HelperTest.php
+++ b/src/Symfony/Component/Templating/Tests/Helper/HelperTest.php
@@ -20,7 +20,7 @@ public function testGetSetCharset()
     {
         $helper = new ProjectTemplateHelper();
         $helper->setCharset('ISO-8859-1');
-        $this->assertTrue('ISO-8859-1' === $helper->getCharset(), '->setCharset() sets the charset set related to this helper');
+        $this->assertSame('ISO-8859-1', $helper->getCharset(), '->setCharset() sets the charset set related to this helper');
     }
 }
 
diff --git a/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php b/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php
index b4bf42b57240a..c889d21817a6d 100644
--- a/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php
+++ b/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php
@@ -23,7 +23,7 @@ class CacheLoaderTest extends TestCase
     public function testConstructor()
     {
         $loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), sys_get_temp_dir());
-        $this->assertTrue($loader->getLoader() === $varLoader, '__construct() takes a template loader as its first argument');
+        $this->assertSame($loader->getLoader(), $varLoader, '__construct() takes a template loader as its first argument');
         $this->assertEquals(sys_get_temp_dir(), $loader->getDir(), '__construct() takes a directory where to store the cache as its second argument');
     }
 
diff --git a/src/Symfony/Component/Templating/Tests/PhpEngineTest.php b/src/Symfony/Component/Templating/Tests/PhpEngineTest.php
index c7418d392f9fc..8889ae12ac23c 100644
--- a/src/Symfony/Component/Templating/Tests/PhpEngineTest.php
+++ b/src/Symfony/Component/Templating/Tests/PhpEngineTest.php
@@ -65,7 +65,7 @@ public function testGetSetHas()
         $engine[$foo] = 'bar';
         $this->assertEquals($foo, $engine->get('bar'), '->set() takes an alias as a second argument');
 
-        $this->assertTrue(isset($engine['bar']));
+        $this->assertArrayHasKey('bar', $engine);
 
         try {
             $engine->get('foobar');
@@ -75,7 +75,7 @@ public function testGetSetHas()
             $this->assertEquals('The helper "foobar" is not defined.', $e->getMessage(), '->get() throws an InvalidArgumentException if the helper is not defined');
         }
 
-        $this->assertTrue(isset($engine['bar']));
+        $this->assertArrayHasKey('bar', $engine);
         $this->assertTrue($engine->has('foo'), '->has() returns true if the helper exists');
         $this->assertFalse($engine->has('foobar'), '->has() returns false if the helper does not exist');
     }
diff --git a/src/Symfony/Component/Translation/MessageSelector.php b/src/Symfony/Component/Translation/MessageSelector.php
index c6134191bce08..31304cd0d89ed 100644
--- a/src/Symfony/Component/Translation/MessageSelector.php
+++ b/src/Symfony/Component/Translation/MessageSelector.php
@@ -49,10 +49,16 @@ class MessageSelector
      */
     public function choose($message, $number, $locale)
     {
-        preg_match_all('/(?:\|\||[^\|])++/', $message, $parts);
+        $parts = array();
+        if (preg_match('/^\|++$/', $message)) {
+            $parts = explode('|', $message);
+        } elseif (preg_match_all('/(?:\|\||[^\|])++/', $message, $matches)) {
+            $parts = $matches[0];
+        }
+
         $explicitRules = array();
         $standardRules = array();
-        foreach ($parts[0] as $part) {
+        foreach ($parts as $part) {
             $part = trim(str_replace('||', '|', $part));
 
             if (preg_match('/^(?P'.Interval::getIntervalRegexp().')\s*(?P.*?)$/xs', $part, $matches)) {
@@ -76,7 +82,7 @@ public function choose($message, $number, $locale)
         if (!isset($standardRules[$position])) {
             // when there's exactly one rule given, and that rule is a standard
             // rule, use this rule
-            if (1 === count($parts[0]) && isset($standardRules[0])) {
+            if (1 === count($parts) && isset($standardRules[0])) {
                 return $standardRules[0];
             }
 
diff --git a/src/Symfony/Component/Translation/Tests/MessageSelectorTest.php b/src/Symfony/Component/Translation/Tests/MessageSelectorTest.php
index a9b92c5cee5fc..42b7e0a3fada8 100644
--- a/src/Symfony/Component/Translation/Tests/MessageSelectorTest.php
+++ b/src/Symfony/Component/Translation/Tests/MessageSelectorTest.php
@@ -128,6 +128,10 @@ public function getChooseTests()
             array("This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1),
             // esacape pipe
             array('This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0),
+            // Empty plural set (2 plural forms) from a .PO file
+            array('', '|', 1),
+            // Empty plural set (3 plural forms) from a .PO file
+            array('', '||', 1),
         );
     }
 }
diff --git a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php
index c2932f81e8b1f..8e3b2a9f791b9 100644
--- a/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php
+++ b/src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php
@@ -227,7 +227,6 @@ class ConstraintViolationAssertion
     private $parameters = array();
     private $invalidValue = 'InvalidValue';
     private $propertyPath = 'property.path';
-    private $translationDomain;
     private $plural;
     private $code;
     private $constraint;
@@ -264,7 +263,7 @@ public function setParameters(array $parameters)
 
     public function setTranslationDomain($translationDomain)
     {
-        $this->translationDomain = $translationDomain;
+        // no-op for BC
 
         return $this;
     }
diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationListTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationListTest.php
index 1631b7aadb645..f0e6afe20da3f 100644
--- a/src/Symfony/Component/Validator/Tests/ConstraintViolationListTest.php
+++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationListTest.php
@@ -89,16 +89,16 @@ public function testArrayAccess()
         $this->list[] = $violation;
 
         $this->assertSame($violation, $this->list[0]);
-        $this->assertTrue(isset($this->list[0]));
+        $this->assertArrayHasKey(0, $this->list);
 
         unset($this->list[0]);
 
-        $this->assertFalse(isset($this->list[0]));
+        $this->assertArrayNotHasKey(0, $this->list);
 
         $this->list[10] = $violation;
 
         $this->assertSame($violation, $this->list[10]);
-        $this->assertTrue(isset($this->list[10]));
+        $this->assertArrayHasKey(10, $this->list);
     }
 
     public function testToString()
diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
index a4335d8a04973..c1263026b8c63 100644
--- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
@@ -839,7 +839,8 @@ protected function style($style, $value, $attr = array())
             $attr['href'] = $href;
         }
         if (isset($attr['href'])) {
-            $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $v);
+            $target = isset($attr['file']) ? '' : ' target="_blank"';
+            $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v);
         }
         if (isset($attr['lang'])) {
             $v = sprintf('%s', esc($attr['lang']), $v);
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php
index 525542c5fed9c..36603c51cc012 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/StubCasterTest.php
@@ -100,7 +100,28 @@ public function testLinkStub()
 
         $expectedDump = <<<'EODUMP'
 array:1 [
-  0 => "Symfony\Component\VarDumper\Tests\Caster\StubCasterTest"
+  0 => "Symfony\Component\VarDumper\Tests\Caster\StubCasterTest"
+]
+
+EODUMP;
+
+        $this->assertStringMatchesFormat($expectedDump, $dump);
+    }
+
+    public function testLinkStubWithNoFileLink()
+    {
+        $var = array(new LinkStub('example.com', 0, 'http://example.com'));
+
+        $cloner = new VarCloner();
+        $dumper = new HtmlDumper();
+        $dumper->setDumpHeader('');
+        $dumper->setDumpBoundaries('', '');
+        $dumper->setDisplayOptions(array('fileLinkFormat' => '%f:%l'));
+        $dump = $dumper->dump($cloner->cloneVar($var), true);
+
+        $expectedDump = <<<'EODUMP'
+array:1 [
+  0 => "example.com"
 ]
 
 EODUMP;
@@ -120,7 +141,7 @@ public function testClassStub()
 
         $expectedDump = <<<'EODUMP'
 array:1 [
-  0 => "hello"
+  0 => "hello"
 ]
 
 EODUMP;
@@ -161,7 +182,7 @@ public function testClassStubWithNotExistingMethod()
 
         $expectedDump = <<<'EODUMP'
 array:1 [
-  0 => "hello"
+  0 => "hello"
 ]
 
 EODUMP;
diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php
index 0ef43f786b01a..c8ded6d21ca52 100644
--- a/src/Symfony/Component/Yaml/Inline.php
+++ b/src/Symfony/Component/Yaml/Inline.php
@@ -707,7 +707,8 @@ private static function evaluateScalar($scalar, $flags, $references = array())
                         return;
                     case 0 === strpos($scalar, '!php/const'):
                         if (self::$constantSupport) {
-                            if (defined($const = self::parseScalar(substr($scalar, 11)))) {
+                            $i = 0;
+                            if (defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
                                 return constant($const);
                             }
 
diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php
index ddab3990fd4bf..57d95c1ca8eed 100644
--- a/src/Symfony/Component/Yaml/Parser.php
+++ b/src/Symfony/Component/Yaml/Parser.php
@@ -362,8 +362,6 @@ private function doParse($value, $flags)
                             @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                         }
                     } else {
-                        // remember the parsed line number here in case we need it to provide some contexts in error messages below
-                        $realCurrentLineNbKey = $this->getRealCurrentLineNb();
                         $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
                         if ('<<' === $key) {
                             $this->refs[$refMatches['ref']] = $value;
@@ -573,7 +571,27 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false)
         }
 
         if (null === $indentation) {
-            $newIndent = $this->getCurrentLineIndentation();
+            $newIndent = null;
+            $movements = 0;
+
+            do {
+                $EOF = false;
+
+                // empty and comment-like lines do not influence the indentation depth
+                if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
+                    $EOF = !$this->moveToNextLine();
+
+                    if (!$EOF) {
+                        ++$movements;
+                    }
+                } else {
+                    $newIndent = $this->getCurrentLineIndentation();
+                }
+            } while (!$EOF && null === $newIndent);
+
+            for ($i = 0; $i < $movements; ++$i) {
+                $this->moveToPreviousLine();
+            }
 
             $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
 
@@ -587,6 +605,8 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false)
         $data = array();
         if ($this->getCurrentLineIndentation() >= $newIndent) {
             $data[] = substr($this->currentLine, $newIndent);
+        } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
+            $data[] = $this->currentLine;
         } else {
             $this->moveToPreviousLine();
 
@@ -905,11 +925,15 @@ private function parseBlockScalar($style, $chomping = '', $indentation = 0)
     private function isNextLineIndented()
     {
         $currentIndentation = $this->getCurrentLineIndentation();
-        $EOF = !$this->moveToNextLine();
+        $movements = 0;
 
-        while (!$EOF && $this->isCurrentLineEmpty()) {
+        do {
             $EOF = !$this->moveToNextLine();
-        }
+
+            if (!$EOF) {
+                ++$movements;
+            }
+        } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
 
         if ($EOF) {
             return false;
@@ -917,7 +941,9 @@ private function isNextLineIndented()
 
         $ret = $this->getCurrentLineIndentation() > $currentIndentation;
 
-        $this->moveToPreviousLine();
+        for ($i = 0; $i < $movements; ++$i) {
+            $this->moveToPreviousLine();
+        }
 
         return $ret;
     }
@@ -1006,19 +1032,25 @@ private function cleanup($value)
     private function isNextLineUnIndentedCollection()
     {
         $currentIndentation = $this->getCurrentLineIndentation();
-        $notEOF = $this->moveToNextLine();
+        $movements = 0;
 
-        while ($notEOF && $this->isCurrentLineEmpty()) {
-            $notEOF = $this->moveToNextLine();
-        }
+        do {
+            $EOF = !$this->moveToNextLine();
+
+            if (!$EOF) {
+                ++$movements;
+            }
+        } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
 
-        if (false === $notEOF) {
+        if ($EOF) {
             return false;
         }
 
         $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
 
-        $this->moveToPreviousLine();
+        for ($i = 0; $i < $movements; ++$i) {
+            $this->moveToPreviousLine();
+        }
 
         return $ret;
     }
diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php
index 815c694b37abb..48fd70a3a182e 100644
--- a/src/Symfony/Component/Yaml/Tests/InlineTest.php
+++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php
@@ -58,6 +58,7 @@ public function getTestsForParsePhpConstants()
             array('!php/const PHP_INT_MAX', PHP_INT_MAX),
             array('[!php/const PHP_INT_MAX]', array(PHP_INT_MAX)),
             array('{ foo: !php/const PHP_INT_MAX }', array('foo' => PHP_INT_MAX)),
+            array('!php/const NULL', null),
         );
     }
 
@@ -82,10 +83,22 @@ public function testParsePhpConstantThrowsExceptionOnInvalidType()
     /**
      * @group legacy
      * @expectedDeprecation The !php/const: tag to indicate dumped PHP constants is deprecated since version 3.4 and will be removed in 4.0. Use the !php/const (without the colon) tag instead on line 1.
+     * @dataProvider getTestsForParseLegacyPhpConstants
      */
-    public function testDeprecatedConstantTag()
+    public function testDeprecatedConstantTag($yaml, $expectedValue)
     {
-        Inline::parse('!php/const:PHP_INT_MAX', Yaml::PARSE_CONSTANT);
+        $this->assertSame($expectedValue, Inline::parse($yaml, Yaml::PARSE_CONSTANT));
+    }
+
+    public function getTestsForParseLegacyPhpConstants()
+    {
+        return array(
+            array('!php/const:Symfony\Component\Yaml\Yaml::PARSE_CONSTANT', Yaml::PARSE_CONSTANT),
+            array('!php/const:PHP_INT_MAX', PHP_INT_MAX),
+            array('[!php/const:PHP_INT_MAX]', array(PHP_INT_MAX)),
+            array('{ foo: !php/const:PHP_INT_MAX }', array('foo' => PHP_INT_MAX)),
+            array('!php/const:NULL', null),
+        );
     }
 
     /**
diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php
index f58bc41a5afd1..ce2429c29a353 100644
--- a/src/Symfony/Component/Yaml/Tests/ParserTest.php
+++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php
@@ -2090,6 +2090,80 @@ public function testEvalRefException()
 EOE;
         $this->parser->parse($yaml);
     }
+
+    /**
+     * @dataProvider indentedMappingData
+     */
+    public function testParseIndentedMappings($yaml, $expected)
+    {
+        $this->assertSame($expected, $this->parser->parse($yaml));
+    }
+
+    public function indentedMappingData()
+    {
+        $tests = array();
+
+        $yaml = << array(
+                array(
+                    'bar' => 'foobar',
+                    'baz' => 'foobaz',
+                ),
+            ),
+        );
+        $tests['comment line is first line in indented block'] = array($yaml, $expected);
+
+        $yaml = << array(
+                array(
+                    'bar' => array(
+                        'baz' => array(1, 2, 3),
+                    ),
+                ),
+            ),
+        );
+        $tests['mapping value on new line starting with a comment line'] = array($yaml, $expected);
+
+        $yaml = << array(
+                array(
+                    'bar' => 'foobar',
+                ),
+            ),
+        );
+        $tests['mapping in sequence starting on a new line'] = array($yaml, $expected);
+
+        $yaml = << array(
+                'bar' => 'baz',
+            ),
+        );
+        $tests['blank line at the beginning of an indented mapping value'] = array($yaml, $expected);
+
+        return $tests;
+    }
 }
 
 class B