diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md index 33ad4d31965d3..07d0350ddfada 100644 --- a/CHANGELOG-3.1.md +++ b/CHANGELOG-3.1.md @@ -7,6 +7,36 @@ in 3.1 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.1.0...v3.1.1 +* 3.1.6 (2016-10-27) + + * bug #20291 [Yaml] Fix 7.1 compat (nicolas-grekas) + * bug #20289 Fix edge case with StreamedResponse where headers are sent twice (Nicofuma) + * bug #20267 [DependencyInjection] A decorated service should not keep the autowiring types (chalasr) + * bug #20278 [DependencyInjection] merge tags instead of completely replacing them (xabbuh) + * bug #20271 Changes related to Twig 1.27 (fabpot) + * bug #20252 Trim constant values in XmlFileLoader (lstrojny) + * bug #20239 [HttpKernel] Fix a regression in the RequestDataCollector (jakzal) + * bug #20253 [TwigBridge] Use non-deprecated Twig_Node::getTemplateLine() (fabpot) + * bug #20243 [WebProfilerBundle][btn-link] add `cursor: pointer` (aitboudad) + * bug #20175 [VarDumper] Fix source links with latests Twig versions (nicolas-grekas) + * bug #20235 [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions (klausi, nicolas-grekas) + * bug #20224 [Twig] removed deprecations added in Twig 1.27 (fabpot) + * bug #19478 fixed Filesystem:makePathRelative and added 2 more testcases (muhammedeminakbulut) + * bug #20218 [HttpFoundation] no 304 response if method is not cacheable (xabbuh) + * bug #20207 [DependencyInjection] move tags from decorated to decorating service (xabbuh) + * bug #20205 [HttpCache] fix: do not cache OPTIONS request (dmaicher) + * bug #20146 [Validator] Prevent infinite loop in PropertyMetadata (wesleylancel) + * bug #20184 [FrameworkBundle] Convert null prefix to an empty string in translation:update (chalasr) + * bug #20154 [PropertyInfo] Fix edge cases in ReflectionExtractor (nicolas-grekas) + * bug #19725 [Security] $attributes can be anything, but RoleVoter assumes strings (Jonatan Männchen) + * bug #20127 [HttpFoundation] JSONP callback validation (ro0NL) + * bug #20163 add missing use statement (xabbuh) + * bug #19961 [Console] Escape question text and default value in SymfonyStyle::ask() (chalasr) + * bug #20141 [Console] Fix validation of empty values using SymfonyQuestionHelper::ask() (chalasr) + * bug #20147 [FrameworkBundle] Alter container class instead of kernel name in cache:clear command (nicolas-grekas) + * bug #20156 Fix event annotation for arguments resolving event (Koc) + * bug #20152 [HttpKernel] Fix nullable types handling (nicolas-grekas) + * 3.1.5 (2016-10-03) * bug #20102 [Validator] Url validator not validating hosts ending in a number (gwkunze) diff --git a/CHANGELOG-3.2.md b/CHANGELOG-3.2.md index d6313f4f808dc..80a81116bae33 100644 --- a/CHANGELOG-3.2.md +++ b/CHANGELOG-3.2.md @@ -7,6 +7,53 @@ in 3.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.2.0...v3.2.1 +* 3.2.0-RC1 (2016-11-17) + + * + +* 3.2.0-RC1 (2016-11-17) + + * feature #20533 [DI] Revert "deprecate get() for uncompiled container builders" (nicolas-grekas) + * bug #20525 [TwigBundle] Give some love to exception pages (nicolas-grekas) + * bug #20484 bumped min version of Twig to 1.28 (fabpot) + * bug #20512 [DI] Fix accepting null as default env param value (nicolas-grekas) + * bug #20519 [Debug] Remove GLOBALS from exception context to avoid endless recursion (Seldaek) + * bug #20455 [ClassLoader] Fix ClassCollectionLoader inlining with __halt_compiler (giosh94mhz) + * bug #20307 [Form] Fix Date\TimeType marked as invalid on request with single_text and zero seconds (LuisDeimos) + * bug #20432 [FrameworkBundle] Add --no-prefix option to translation:update (chalasr) + * bug #20480 [FrameworkBundle] Register the ArrayDenormalizer (dunglas) + * bug #20286 [Serializer] Fix DataUriNormalizer's regex (dunglas) + * bug #20466 [Translation] fixed nested fallback catalogue using multiple locales. (aitboudad) + * bug #20465 [#18637][TranslationDebug] workaround for getFallbackLocales. (aitboudad) + * bug #20453 [Cache] Make directory hashing case insensitive (nicolas-grekas) + * bug #20428 [TwigBundle] fixed template root path (fabpot) + * feature #20447 [DI] Force env params to be string|null (nicolas-grekas) + * feature #20451 [Workflow] Added Definition builder (Nyholm) + * bug #20460 [FrameworkBundle] Fixed WorkflowCommand to support state machines (HeahDude) + * bug #20440 [TwigBridge][TwigBundle][HttpKernel] prefer getSourceContext() over getSource() (xabbuh) + * feature #19629 [Workflow] Make the Workflow support State Machines (Nyholm, lyrixx) + * bug #20287 Properly format value in UniqueEntityValidator (alcaeus) + * bug #20422 [Translation][fallback] add missing resources in parent catalogues. (aitboudad) + * bug #20378 [Form] Fixed show float values as choice value in ChoiceType (yceruto) + * feature #20416 [Bridge\Monolog][FrameworkBundle] Add & wire a DebugProcessor (nicolas-grekas) + * bug #20415 [DI][Serializer] Add missing deprecations (nicolas-grekas) + * bug #20294 Improved the design of the metrics in the profiler (javiereguiluz) + * bug #20375 [HttpFoundation][Session] Fix memcache session handler (klandaika) + * bug #20377 [Console] Fix infinite loop on missing input (chalasr) + * feature #20232 [DependencyInjection] fixed ini file values conversion (fabpot) + * feature #19490 [SecurityBundle] Integrate current firewall in Profiler (chalasr) + * feature #19398 [DX][SecurityBundle] Introduce a FirewallConfig class accessible from FirewallContext (chalasr) + * bug #20336 [HttpKernel] Base DataCollector throws warning on unsupported scheme strings (ogizanagi) + * bug #20335 [Yaml] Fix String offset cast error in Inline parser (romainneutron) + * bug #20372 [Console] simplified code (fabpot) + * bug #20342 [Form] Fix UrlType transforms valid protocols (ogizanagi) + * bug #20341 Fix YamlReferenceDumper unnamed nested prototypes (ogizanagi) + * bug #20292 Enhance GAE compat by removing some realpath() (nicolas-grekas) + * bug #20325 [VarDumper] Fix source links to Twig files (nicolas-grekas) + * bug #20328 [Console] Fix empty COLUMNS/LINES env vars (nicolas-grekas) + * bug #20326 [VarDumper] Fix dumping Twig source in stack traces (nicolas-grekas) + * bug #20321 Compatibility with Twig 1.27 (xkobal) + * 3.2.0-BETA1 (2016-10-27) * feature #19973 Added a default ide file link web view (jeremyFreeAgent) @@ -101,7 +148,6 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * feature #19325 [FrameworkBundle] Allow to specify a domain when updating translations (antograssiot) * feature #19277 [Serializer] Argument objects (theofidry, dunglas) * feature #19322 [HttpFoundation] Add Request::isMethodIdempotent method (dunglas) - * feature #17608 [DependencyInjection] Autowiring: add setter injection support (dunglas) * feature #18510 Added a SecurityUserValueResolver for controllers (iltar) * feature #19203 [Bridge/Doctrine] Reset the EM lazy-proxy instead of the EM service (nicolas-grekas) * feature #19236 [FrameworkBundle] Deprecate the service serializer.mapping.cache.doctrine.apc (Ener-Getick) @@ -149,7 +195,6 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * feature #18869 [Routing] Throw exception when PHP start tag is missing (WouterJ) * feature #18781 [Console] Display errors in quiet mode (multi-io) * feature #19011 [HttpKernel] Add convenient method ArgumentResolver:: getDefaultArgumentValueResolvers (romainneutron) - * feature #18977 [PropertyAccess] Add missing arguments to PropertyAccess::createPropertyAccessor() (chalasr) * feature #18568 [WebProfilerBundle] Fix bundle usage in Content-Security-Policy context without unsafe-inline (romainneutron) * feature #16838 [PropertyAccess] Add PSR-6 cache (dunglas) * feature #18790 [Console] Show aliases in command description instead of in different lines in application description (juanmirod) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b0646cc1303cd..41c883159c8e2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -33,8 +33,8 @@ Symfony is the result of the work of many people who made the code better - Igor Wiedler (igorw) - Grégoire Pineau (lyrixx) - Eriksen Costa (eriksencosta) - - Sarah Khalil (saro0h) - Jules Pietri (heah) + - Sarah Khalil (saro0h) - Maxime Steinhausser (ogizanagi) - Jonathan Wage (jwage) - Diego Saint Esteben (dosten) @@ -49,13 +49,13 @@ Symfony is the result of the work of many people who made the code better - Henrik Bjørnskov (henrikbjorn) - Miha Vrhovnik - Diego Saint Esteben (dii3g0) + - Robin Chalas (chalas_r) - Ener-Getick (energetick) - Konstantin Kudryashov (everzet) - Bilal Amarni (bamarni) - Florin Patan (florinpatan) - Peter Rehm (rpet) - Iltar van der Berg (kjarli) - - Robin Chalas (chalas_r) - Kevin Bond (kbond) - Andrej Hudec (pulzarraider) - Gábor Egyed (1ed) @@ -69,6 +69,7 @@ Symfony is the result of the work of many people who made the code better - Henrik Westphal (snc) - Dariusz Górecki (canni) - Douglas Greenshields (shieldo) + - Konstantin Myakshin (koc) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) @@ -76,7 +77,6 @@ Symfony is the result of the work of many people who made the code better - Titouan Galopin (tgalopin) - Daniel Holmes (dholmes) - Pierre du Plessis (pierredup) - - Konstantin Myakshin (koc) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - John Wards (johnwards) @@ -84,17 +84,19 @@ Symfony is the result of the work of many people who made the code better - Fran Moreno (franmomu) - Antoine Hérault (herzult) - Paráda József (paradajozsef) + - Jáchym Toušek (enumag) - Arnaud Le Blanc (arnaud-lb) - Jérôme Tamarelle (gromnan) - Michal Piotrowski (eventhorizon) - Tim Nagel (merk) + - Dariusz Ruminski - Brice BERNARD (brikou) - Alexander M. Turek (derrabus) - - Dariusz Ruminski - marc.weistroff - Issei Murasawa (issei_m) - lenar - Włodzimierz Gajda (gajdaw) + - Roland Franssen (ro0) - Baptiste Clavié (talus) - Alexander Schwenn (xelaris) - Florian Voutzinos (florianv) @@ -104,8 +106,6 @@ Symfony is the result of the work of many people who made the code better - Peter Kokot (maastermedia) - excelwebzone - Jacob Dreesen (jdreesen) - - Jáchym Toušek (enumag) - - Roland Franssen (ro0) - Jérémy DERUSSÉ (jderusse) - Vladimir Reznichenko (kalessil) - Tomáš Votruba (tomas_votruba) @@ -131,6 +131,7 @@ Symfony is the result of the work of many people who made the code better - Andréia Bohner (andreia) - Rafael Dohms (rdohms) - Arnaud Kleinpeter (nanocom) + - jwdeitch - Joel Wurtz (brouznouf) - Philipp Wahala (hifi) - Vyacheslav Pavlov @@ -139,6 +140,7 @@ Symfony is the result of the work of many people who made the code better - Thomas Rabaix (rande) - Vincent AUBERT (vincent) - Rouven Weßling (realityking) + - Teoh Han Hui (teohhanhui) - Mikael Pajunen - Clemens Tolboom - Helmer Aaviksoo @@ -149,7 +151,6 @@ Symfony is the result of the work of many people who made the code better - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) - Artur Kotyrba - - Teoh Han Hui (teohhanhui) - Warnar Boekkooi (boekkooi) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) @@ -159,10 +160,12 @@ Symfony is the result of the work of many people who made the code better - Richard Miller (mr_r_miller) - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) + - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - Benjamin Dulau (dbenjamin) - Mathieu Lemoine (lemoinem) - Andreas Hucks (meandmymonkey) - Noel Guilbert (noel) + - Lars Strojny (lstrojny) - Yonel Ceruto González (yonelceruto) - Stepan Anchugov (kix) - bronze1man @@ -185,13 +188,11 @@ Symfony is the result of the work of many people who made the code better - Michele Orselli (orso) - Tom Van Looy (tvlooy) - Sven Paulus (subsven) - - Lars Strojny (lstrojny) - Rui Marinho (ruimarinho) - Daniel Espendiller - Dawid Nowak - Eugene Wissner - Julien Brochet (mewt) - - jeremyFreeAgent (jeremyfreeagent) - Sergey Linnik (linniksa) - Michaël Perrin (michael.perrin) - Marcel Beerta (mazen) @@ -226,6 +227,7 @@ Symfony is the result of the work of many people who made the code better - Jakub Kucharovic (jkucharovic) - Eugene Leonovich (rybakit) - Filippo Tessarotto + - Tristan Darricau (nicofuma) - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) - GordonsLondon @@ -262,7 +264,6 @@ Symfony is the result of the work of many people who made the code better - Oleg Voronkovich - Manuel Kiessling (manuelkiessling) - Daniel Wehner - - Tristan Darricau (nicofuma) - Atsuhiro KUBO (iteman) - Andrew Moore (finewolf) - Bertrand Zuchuat (garfield-fr) @@ -387,6 +388,7 @@ Symfony is the result of the work of many people who made the code better - Ariel Ferrandini (aferrandini) - Dirk Pahl (dirkaholic) - cedric lombardot (cedriclombardot) + - David Maicher (dmaicher) - Jonas Flodén (flojon) - Christian Schmidt - Marcin Sikoń (marphi) @@ -535,6 +537,7 @@ Symfony is the result of the work of many people who made the code better - Daisuke Ohata - Vincent Simonin - Alex Bogomazov (alebo) + - maxime.steinhausser - Stefan Warman - Tristan Maindron (tmaindron) - Ke WANG (yktd26) @@ -546,11 +549,11 @@ Symfony is the result of the work of many people who made the code better - Ulumuddin Yunus (joenoez) - Luc Vieillescazes (iamluc) - Johann Saunier (prophet777) + - Michael Devery (mickadoo) - Antoine Corcy - Artur Eshenbrener - Arturs Vonda - Sascha Grossenbacher - - David Maicher (dmaicher) - Szijarto Tamas - Catalin Dan - Stephan Vock @@ -593,6 +596,7 @@ Symfony is the result of the work of many people who made the code better - Vladyslav Petrovych - Alex Xandra Albert Sim - Carson Full + - Andrey Astakhov (aast) - Trent Steel (trsteel88) - Yuen-Chi Lian - Besnik Br @@ -602,12 +606,14 @@ Symfony is the result of the work of many people who made the code better - avorobiev - Venu - Lars Vierbergen + - Jonatan Männchen - Dennis Hotson - Andrew Tchircoff (andrewtch) - michaelwilliams - 1emming - Victor Bocharsky (bocharsky_bw) - Leevi Graham (leevigraham) + - Jordan Deitch - Casper Valdemar Poulsen - Josiah (josiah) - Joschi Kuphal @@ -740,6 +746,7 @@ Symfony is the result of the work of many people who made the code better - Alexandru Furculita (afurculita) - Ben Ramsey (ramsey) - Christian Jul Jensen + - Alexandre GESLIN (alexandregeslin) - The Whole Life to Learn - Farhad Safarov - Liverbool (liverbool) @@ -870,12 +877,14 @@ Symfony is the result of the work of many people who made the code better - James Gilliland - Rhodri Pugh (rodnaph) - David de Boer (ddeboer) + - Klaus Purer - Gilles Doge (gido) - abulford - antograssiot - Brooks Boyd - Roger Webb - Dmitriy Simushev + - Ivo Bathke (ivoba) - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) @@ -964,7 +973,6 @@ Symfony is the result of the work of many people who made the code better - ChrisC - Ilya Biryukov - Kim Laï Trinh - - Jonatan Männchen - Jason Desrosiers - m.chwedziak - Philip Frank @@ -994,7 +1002,6 @@ Symfony is the result of the work of many people who made the code better - Emmanuel Vella (emmanuel.vella) - Carsten Nielsen (phreaknerd) - Mathieu Rochette - - maxime.steinhausser - Jay Severson - René Kerner - Nathaniel Catchpole @@ -1044,7 +1051,6 @@ Symfony is the result of the work of many people who made the code better - Benjamin Bender - Konrad Mohrfeldt - Lance Chen - - Andrey Astakhov (aast) - Andrew (drew) - Nikolay Labinskiy (e-moe) - kor3k kor3k (kor3k) @@ -1055,6 +1061,7 @@ Symfony is the result of the work of many people who made the code better - Mephistofeles - Hoffmann András - Olivier + - Wesley Lancel - pscheit - Zdeněk Drahoš - Dan Harper @@ -1160,6 +1167,7 @@ Symfony is the result of the work of many people who made the code better - JakeFr - Simon Sargeant - efeen + - Muhammed Akbulut - Michał Dąbrowski (defrag) - Simone Fumagalli (hpatoio) - Brian Graham (incognito) @@ -1176,6 +1184,7 @@ Symfony is the result of the work of many people who made the code better - Artem Lopata (bumz) - Nicole Cordes - Alexey Popkov + - Gijs Kunze - Artyom Protaskin - Nathanael d. Noblet - helmer @@ -1445,6 +1454,7 @@ Symfony is the result of the work of many people who made the code better - Matthias Althaus - Michaël VEROUX - Julia + - Lin Lu - arduanov - sualko - Nicolas Roudaire diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index fc81eae24fc3b..a93cfbf2f09e2 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1902,5 +1902,5 @@ UPGRADE FROM 2.x to 3.0 After: ```php - $request->query->get('foo')[bar]; + $request->query->get('foo')['bar']; ``` diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index ff9b3fe7b78ce..483b7e6ed1447 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -16,9 +16,6 @@ Debug DependencyInjection ------------------- - * Calling `get()` on a `ContainerBuilder` instance before compiling the - container is not supported anymore and will throw an exception. - * Using unsupported configuration keys in YAML configuration files raises an exception. diff --git a/composer.json b/composer.json index 139f748ac3add..9677a86460fbe 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.5.9", "doctrine/common": "~2.4", - "twig/twig": "~1.27|~2.0", + "twig/twig": "~1.28|~2.0", "psr/cache": "~1.0", "psr/log": "~1.0", "symfony/polyfill-intl-icu": "~1.0", diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index a0f8b5c46ad06..3a6a877e1fc4e 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -134,10 +134,7 @@ protected function setMappingDriverConfig(array $mappingConfig, $mappingName) throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName)); } - if (substr($mappingDirectory, 0, 7) !== 'phar://') { - $mappingDirectory = realpath($mappingDirectory); - } - $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = $mappingDirectory; + $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory; } /** diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php index 96f05eb5b60c9..f8382ed2ebfb8 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php @@ -61,7 +61,7 @@ private function updateValidatorMappingFiles(ContainerBuilder $container, $mappi foreach ($container->getParameter('kernel.bundles') as $bundle) { $reflection = new \ReflectionClass($bundle); if (is_file($file = dirname($reflection->getFileName()).'/'.$validationPath)) { - $files[] = realpath($file); + $files[] = $file; $container->addResource(new FileResource($file)); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 5919ca95734a0..b28b9d51ad67b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -20,14 +20,10 @@ use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; -use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; -use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; -use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; abstract class DoctrineType extends AbstractType { @@ -36,11 +32,6 @@ abstract class DoctrineType extends AbstractType */ protected $registry; - /** - * @var ChoiceListFactoryInterface - */ - private $choiceListFactory; - /** * @var IdReader[] */ @@ -108,15 +99,9 @@ public function getQueryBuilderPartsForCachingHash($queryBuilder) return false; } - public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null) + public function __construct(ManagerRegistry $registry) { $this->registry = $registry; - $this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator( - new PropertyAccessDecorator( - new DefaultChoiceListFactory(), - $propertyAccessor - ) - ); } public function buildForm(FormBuilderInterface $builder, array $options) diff --git a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php index 962099e36a7bc..e7f4555d07d2e 100644 --- a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php @@ -12,6 +12,8 @@ namespace Symfony\Bridge\Doctrine\Test; use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Cache\ArrayCache; +use Doctrine\ORM\Configuration; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\EntityManager; @@ -25,22 +27,19 @@ class DoctrineTestHelper /** * Returns an entity manager for testing. * + * @param Configuration|null $config + * * @return EntityManager */ - public static function createTestEntityManager() + public static function createTestEntityManager(Configuration $config = null) { if (!extension_loaded('pdo_sqlite')) { \PHPUnit_Framework_TestCase::markTestSkipped('Extension pdo_sqlite is required.'); } - $config = new \Doctrine\ORM\Configuration(); - $config->setEntityNamespaces(array('SymfonyTestsDoctrine' => 'Symfony\Bridge\Doctrine\Tests\Fixtures')); - $config->setAutoGenerateProxyClasses(true); - $config->setProxyDir(\sys_get_temp_dir()); - $config->setProxyNamespace('SymfonyTests\Doctrine'); - $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader())); - $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ArrayCache()); - $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache()); + if (null === $config) { + $config = self::createTestConfiguration(); + } $params = array( 'driver' => 'pdo_sqlite', @@ -50,6 +49,23 @@ public static function createTestEntityManager() return EntityManager::create($params, $config); } + /** + * @return Configuration + */ + public static function createTestConfiguration() + { + $config = new Configuration(); + $config->setEntityNamespaces(array('SymfonyTestsDoctrine' => 'Symfony\Bridge\Doctrine\Tests\Fixtures')); + $config->setAutoGenerateProxyClasses(true); + $config->setProxyDir(\sys_get_temp_dir()); + $config->setProxyNamespace('SymfonyTests\Doctrine'); + $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader())); + $config->setQueryCacheImpl(new ArrayCache()); + $config->setMetadataCacheImpl(new ArrayCache()); + + return $config; + } + /** * This class cannot be instantiated. */ diff --git a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php new file mode 100644 index 0000000000000..b7cbafa947aef --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Test; + +use Doctrine\Common\Persistence\ObjectRepository; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Repository\RepositoryFactory; + +/** + * @author Andreas Braun + */ +final class TestRepositoryFactory implements RepositoryFactory +{ + /** + * @var ObjectRepository[] + */ + private $repositoryList = array(); + + /** + * {@inheritdoc} + */ + public function getRepository(EntityManagerInterface $entityManager, $entityName) + { + $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); + + if (isset($this->repositoryList[$repositoryHash])) { + return $this->repositoryList[$repositoryHash]; + } + + return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName); + } + + public function setRepository(EntityManagerInterface $entityManager, $entityName, ObjectRepository $repository) + { + $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); + + $this->repositoryList[$repositoryHash] = $repository; + } + + /** + * @return ObjectRepository + */ + private function createRepository(EntityManagerInterface $entityManager, $entityName) + { + /* @var $metadata ClassMetadata */ + $metadata = $entityManager->getClassMetadata($entityName); + $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); + + return new $repositoryClassName($entityManager, $metadata); + } + + private function getRepositoryHash(EntityManagerInterface $entityManager, $entityName) + { + return $entityManager->getClassMetadata($entityName)->getName().spl_object_hash($entityManager); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 94f0787b44ff2..9fe29a3f54bc6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -44,7 +44,7 @@ protected function setUp() $this->extension->expects($this->any()) ->method('getObjectManagerElementName') ->will($this->returnCallback(function ($name) { - return 'doctrine.orm.'.$name; + return 'doctrine.orm.'.$name; })); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php index 44630a1fc51f1..6ee360e6a10aa 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php @@ -24,6 +24,9 @@ class SingleIntIdEntity /** @Column(type="string", nullable=true) */ public $name; + /** @Column(type="array", nullable=true) */ + public $phoneNumbers = array(); + public function __construct($id, $name) { $this->id = $id; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 20c6d333d16d5..21d3950313590 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -16,6 +16,7 @@ use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectRepository; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Test\TestRepositoryFactory; use Symfony\Bridge\Doctrine\Tests\Fixtures\Employee; use Symfony\Bridge\Doctrine\Tests\Fixtures\Person; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; @@ -52,9 +53,16 @@ class UniqueEntityValidatorTest extends AbstractConstraintValidatorTest */ protected $repository; + protected $repositoryFactory; + protected function setUp() { - $this->em = DoctrineTestHelper::createTestEntityManager(); + $this->repositoryFactory = new TestRepositoryFactory(); + + $config = DoctrineTestHelper::createTestConfiguration(); + $config->setRepositoryFactory($this->repositoryFactory); + + $this->em = DoctrineTestHelper::createTestEntityManager($config); $this->registry = $this->createRegistryMock($this->em); $this->createSchema($this->em); @@ -170,7 +178,7 @@ public function testValidateUniqueness() $this->buildViolation('myMessage') ->atPath('property.path.name') - ->setParameter('{{ value }}', 'Foo') + ->setParameter('{{ value }}', '"Foo"') ->setInvalidValue('Foo') ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); @@ -195,7 +203,7 @@ public function testValidateCustomErrorPath() $this->buildViolation('myMessage') ->atPath('property.path.bar') - ->setParameter('{{ value }}', 'Foo') + ->setParameter('{{ value }}', '"Foo"') ->setInvalidValue('Foo') ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); @@ -248,7 +256,7 @@ public function testValidateUniquenessWithIgnoreNull() $this->buildViolation('myMessage') ->atPath('property.path.name') - ->setParameter('{{ value }}', 'Foo') + ->setParameter('{{ value }}', '"Foo"') ->setInvalidValue('Foo') ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); @@ -281,7 +289,7 @@ public function testValidateUniquenessWithValidCustomErrorPath() $this->buildViolation('myMessage') ->atPath('property.path.name2') - ->setParameter('{{ value }}', 'Bar') + ->setParameter('{{ value }}', '"Bar"') ->setInvalidValue('Bar') ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); @@ -452,7 +460,7 @@ public function testValidateUniquenessNotToStringEntityWithAssociatedEntity() $this->buildViolation('myMessage') ->atPath('property.path.single') - ->setParameter('{{ value }}', $expectedValue) + ->setParameter('{{ value }}', '"'.$expectedValue.'"') ->setInvalidValue($expectedValue) ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); @@ -478,6 +486,44 @@ public function testAssociatedEntityWithNull() $this->assertNoViolation(); } + public function testValidateUniquenessWithArrayValue() + { + $repository = $this->createRepositoryMock(); + $this->repositoryFactory->setRepository($this->em, 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity', $repository); + + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('phoneNumbers'), + 'em' => self::EM_NAME, + 'repositoryMethod' => 'findByCustom', + )); + + $entity1 = new SingleIntIdEntity(1, 'foo'); + $entity1->phoneNumbers[] = 123; + + $repository->expects($this->once()) + ->method('findByCustom') + ->will($this->returnValue(array($entity1))) + ; + + $this->em->persist($entity1); + $this->em->flush(); + + $entity2 = new SingleIntIdEntity(2, 'bar'); + $entity2->phoneNumbers[] = 123; + $this->em->persist($entity2); + $this->em->flush(); + + $this->validator->validate($entity2, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.phoneNumbers') + ->setParameter('{{ value }}', 'array') + ->setInvalidValue(array(123)) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->assertRaised(); + } + /** * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException * @expectedExceptionMessage Object manager "foo" does not exist. @@ -551,7 +597,7 @@ public function testValidateInheritanceUniqueness() ->atPath('property.path.name') ->setInvalidValue('Foo') ->setCode('23bd9dbf-6b9b-41cd-a99e-4844bcf3077f') - ->setParameters(array('{{ value }}' => 'Foo')) + ->setParameters(array('{{ value }}' => '"Foo"')) ->assertRaised(); } diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 9dbc123572822..9867a2779f61a 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -147,7 +147,7 @@ public function validate($entity, Constraint $constraint) $this->context->buildViolation($constraint->message) ->atPath($errorPath) - ->setParameter('{{ value }}', $invalidValue) + ->setParameter('{{ value }}', $this->formatValue($invalidValue, static::OBJECT_TO_STRING | static::PRETTY_DATE)) ->setInvalidValue($invalidValue) ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->addViolation(); diff --git a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php index f1046c96a6ad1..6032750ff6422 100644 --- a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php @@ -11,6 +11,8 @@ namespace Symfony\Bridge\Monolog\Handler; +@trigger_error('The '.__NAMESPACE__.'\DebugHandler class is deprecated since version 3.2 and will be removed in 4.0. Use Symfony\Bridge\Monolog\Processor\DebugProcessor instead.', E_USER_DEPRECATED); + use Monolog\Logger; use Monolog\Handler\TestHandler; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; @@ -19,6 +21,8 @@ * DebugLogger. * * @author Jordi Boggiano + * + * @deprecated since version 3.2, to be removed in 4.0. Use Symfony\Bridge\Monolog\Processor\DebugProcessor instead. */ class DebugHandler extends TestHandler implements DebugLoggerInterface { diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index f0e108081b59c..ec6434fe791b8 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -52,6 +52,12 @@ public function countErrors() */ private function getDebugLogger() { + foreach ($this->processors as $processor) { + if ($processor instanceof DebugLoggerInterface) { + return $processor; + } + } + foreach ($this->handlers as $handler) { if ($handler instanceof DebugLoggerInterface) { return $handler; diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php new file mode 100644 index 0000000000000..22a4faac5cc9a --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Processor; + +use Monolog\Logger; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +class DebugProcessor implements DebugLoggerInterface +{ + private $records = array(); + private $errorCount = 0; + + public function __invoke(array $record) + { + $this->records[] = array( + 'timestamp' => $record['datetime']->getTimestamp(), + 'message' => $record['message'], + 'priority' => $record['level'], + 'priorityName' => $record['level_name'], + 'context' => $record['context'], + 'channel' => isset($record['channel']) ? $record['channel'] : '', + ); + switch ($record['level']) { + case Logger::ERROR: + case Logger::CRITICAL: + case Logger::ALERT: + case Logger::EMERGENCY: + ++$this->errorCount; + } + + return $record; + } + + /** + * {@inheritdoc} + */ + public function getLogs() + { + return $this->records; + } + + /** + * {@inheritdoc} + */ + public function countErrors() + { + return $this->errorCount; + } +} diff --git a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php index e685ef041f055..9597d01c96cb1 100644 --- a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -13,10 +13,14 @@ use Monolog\Handler\TestHandler; use Symfony\Bridge\Monolog\Handler\DebugHandler; +use Symfony\Bridge\Monolog\Processor\DebugProcessor; use Symfony\Bridge\Monolog\Logger; class LoggerTest extends \PHPUnit_Framework_TestCase { + /** + * @group legacy + */ public function testGetLogsWithDebugHandler() { $handler = new DebugHandler(); @@ -26,7 +30,7 @@ public function testGetLogsWithDebugHandler() $this->assertSame(1, count($logger->getLogs())); } - public function testGetLogsWithoutDebugHandler() + public function testGetLogsWithoutDebugProcessor() { $handler = new TestHandler(); $logger = new Logger(__METHOD__, array($handler)); @@ -35,6 +39,9 @@ public function testGetLogsWithoutDebugHandler() $this->assertSame(array(), $logger->getLogs()); } + /** + * @group legacy + */ public function testCountErrorsWithDebugHandler() { $handler = new DebugHandler(); @@ -53,7 +60,10 @@ public function testCountErrorsWithDebugHandler() $this->assertSame(4, $logger->countErrors()); } - public function testGetLogs() + /** + * @group legacy + */ + public function testGetLogsWithDebugHandler2() { $logger = new Logger('test'); $logger->pushHandler(new DebugHandler()); @@ -66,7 +76,7 @@ public function testGetLogs() $this->assertEquals(Logger::INFO, $record['priority']); } - public function testCountErrorsWithoutDebugHandler() + public function testCountErrorsWithoutDebugProcessor() { $handler = new TestHandler(); $logger = new Logger(__METHOD__, array($handler)); @@ -74,4 +84,47 @@ public function testCountErrorsWithoutDebugHandler() $this->assertTrue($logger->error('error message')); $this->assertSame(0, $logger->countErrors()); } + + public function testGetLogsWithDebugProcessor() + { + $handler = new TestHandler(); + $processor = new DebugProcessor(); + $logger = new Logger(__METHOD__, array($handler), array($processor)); + + $this->assertTrue($logger->error('error message')); + $this->assertSame(1, count($logger->getLogs())); + } + + public function testCountErrorsWithDebugProcessor() + { + $handler = new TestHandler(); + $processor = new DebugProcessor(); + $logger = new Logger(__METHOD__, array($handler), array($processor)); + + $this->assertTrue($logger->debug('test message')); + $this->assertTrue($logger->info('test message')); + $this->assertTrue($logger->notice('test message')); + $this->assertTrue($logger->warning('test message')); + + $this->assertTrue($logger->error('test message')); + $this->assertTrue($logger->critical('test message')); + $this->assertTrue($logger->alert('test message')); + $this->assertTrue($logger->emergency('test message')); + + $this->assertSame(4, $logger->countErrors()); + } + + public function testGetLogsWithDebugProcessor2() + { + $handler = new TestHandler(); + $logger = new Logger('test', array($handler)); + $logger->pushProcessor(new DebugProcessor()); + + $logger->addInfo('test'); + $this->assertCount(1, $logger->getLogs()); + list($record) = $logger->getLogs(); + + $this->assertEquals('test', $record['message']); + $this->assertEquals(Logger::INFO, $record['priority']); + } } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index 25f7b311ee967..ca85d8aba5994 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -68,7 +68,8 @@ public static function register($mode = 0) 'other' => array(), ); $deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $getMode) { - if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || DeprecationErrorHandler::MODE_DISABLED === $mode = $getMode()) { + $mode = $getMode(); + if ((E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) || DeprecationErrorHandler::MODE_DISABLED === $mode) { return \PHPUnit_Util_ErrorHandler::handleError($type, $msg, $file, $line, $context); } diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index a18b817870f5e..44e23902e03de 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -61,7 +61,7 @@ protected function configure() new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), )) ->setDescription('Shows a list of twig functions, filters, globals and tests') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, filters, globals and tests. Output can be filtered with an optional argument. diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index abcfcccaee86a..ebbfde12c2058 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -61,7 +61,7 @@ protected function configure() ->setDescription('Lints a template and outputs encountered errors') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addArgument('filename', InputArgument::IS_ARRAY) - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command lints a template and outputs to STDOUT the first encountered syntax error. diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index 1d7b5910ddb17..9861277db0f81 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -51,6 +51,7 @@ public function getFilters() new \Twig_SimpleFilter('file_excerpt', array($this, 'fileExcerpt'), array('is_safe' => array('html'))), new \Twig_SimpleFilter('format_file', array($this, 'formatFile'), array('is_safe' => array('html'))), new \Twig_SimpleFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))), + new \Twig_SimpleFilter('format_log_message', array($this, 'formatLogMessage'), array('is_safe' => array('html'))), new \Twig_SimpleFilter('file_link', array($this, 'getFileLink')), ); } @@ -151,7 +152,7 @@ public function fileExcerpt($file, $line, $srcContext = 3) } for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, count($content)); $i <= $max; ++$i) { - $lines[] = '
'.self::fixCodeMarkup($content[$i - 1]).''; + $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; } return '
    '.implode("\n", $lines).'
'; @@ -183,9 +184,7 @@ public function formatFile($file, $line, $text = null) $text = "$text at line $line"; if (false !== $link = $this->getFileLink($file, $line)) { - $flags = ENT_COMPAT | ENT_SUBSTITUTE; - - return sprintf('%s', htmlspecialchars($link, $flags, $this->charset), $text); + return sprintf('%s', htmlspecialchars($link, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset), $text); } return $text; @@ -215,6 +214,27 @@ public function formatFileFromText($text) }, $text); } + /** + * @internal + */ + public function formatLogMessage($message, array $context) + { + if ($context && false !== strpos($message, '{')) { + $replacements = array(); + foreach ($context as $key => $val) { + if (is_scalar($val)) { + $replacements['{'.$key.'}'] = $val; + } + } + + if ($replacements) { + $message = strtr($message, $replacements); + } + } + + return htmlspecialchars($message, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index b226d91adfe48..e64d6eae2347d 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -49,7 +49,7 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) return $node; } else { - $var = $env->getParser()->getVarName(); + $var = $this->getVarName(); $name = new \Twig_Node_Expression_AssignName($var, $node->getTemplateLine()); $this->scope->set('domain', new \Twig_Node_Expression_Name($var, $node->getTemplateLine())); @@ -123,4 +123,9 @@ private function isNamedArguments($arguments) return false; } + + private function getVarName() + { + return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php index 0235c4da327a0..e96bd4f9a3bef 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/TranslationExtensionTest.php @@ -36,7 +36,7 @@ public function testTrans($template, $expected, array $variables = array()) $twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false)); $twig->addExtension(new TranslationExtension(new Translator('en', new MessageSelector()))); - echo $twig->compile($twig->parse($twig->tokenize(new \Twig_Source($twig->getLoader()->getSource('index'), 'index'))))."\n\n"; + echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext('index'))))."\n\n"; $this->assertEquals($expected, $this->getTemplate($template)->render($variables)); } diff --git a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php index 8e5b933c81da7..5d669cde7ac94 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php @@ -80,7 +80,12 @@ public function testOneVar() } EOTXT; - $expected = preg_replace('/%(.*?)%/', '(isset($context["$1"]) ? $context["$1"] : null)', $expected); + + if (PHP_VERSION_ID >= 70000) { + $expected = preg_replace('/%(.*?)%/', '($context["$1"] ?? null)', $expected); + } else { + $expected = preg_replace('/%(.*?)%/', '(isset($context["$1"]) ? $context["$1"] : null)', $expected); + } $this->assertSame($expected, $compiler->compile($node)->getSource()); } @@ -106,7 +111,12 @@ public function testMultiVars() } EOTXT; - $expected = preg_replace('/%(.*?)%/', '(isset($context["$1"]) ? $context["$1"] : null)', $expected); + + if (PHP_VERSION_ID >= 70000) { + $expected = preg_replace('/%(.*?)%/', '($context["$1"] ?? null)', $expected); + } else { + $expected = preg_replace('/%(.*?)%/', '(isset($context["$1"]) ? $context["$1"] : null)', $expected); + } $this->assertSame($expected, $compiler->compile($node)->getSource()); } diff --git a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php index 700d169f17ca8..dff1d5f5ae090 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/FormThemeTest.php @@ -66,6 +66,10 @@ public function testCompile() protected function getVariableGetter($name) { + if (PHP_VERSION_ID >= 70000) { + return sprintf('($context["%s"] ?? null)', $name, $name); + } + return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name); } } diff --git a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php index f7f542968b098..31ab934f37ec8 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -263,6 +263,10 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() protected function getVariableGetter($name) { + if (PHP_VERSION_ID >= 70000) { + return sprintf('($context["%s"] ?? null)', $name, $name); + } + return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name); } } diff --git a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php index 9798e6ddf4b0c..4c00cdab50ade 100644 --- a/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Node/TransNodeTest.php @@ -39,15 +39,23 @@ public function testCompileStrict() protected function getVariableGetterWithoutStrictCheck($name) { + if (PHP_VERSION_ID >= 70000) { + return sprintf('($context["%s"] ?? null)', $name, $name); + } + return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name); } protected function getVariableGetterWithStrictCheck($name) { - if (version_compare(\Twig_Environment::VERSION, '2.0.0-DEV', '>=')) { + if (\Twig_Environment::MAJOR_VERSION >= 2) { return sprintf('(isset($context["%s"]) || array_key_exists("%s", $context) ? $context["%s"] : $this->notFound("%s", 0))', $name, $name, $name, $name); } - return sprintf('(isset($context["%1$s"]) ? $context["%1$s"] : $this->getContext($context, "%1$s"))', $name); + if (PHP_VERSION_ID >= 70000) { + return sprintf('($context["%s"] ?? $this->getContext($context, "%s"))', $name, $name, $name); + } + + return sprintf('(isset($context["%s"]) ? $context["%s"] : $this->getContext($context, "%s"))', $name, $name, $name); } } diff --git a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php index 917687ad34eee..950c4d0810db0 100644 --- a/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php +++ b/src/Symfony/Bridge/Twig/Translation/TwigExtractor.php @@ -64,7 +64,7 @@ public function extract($resource, MessageCatalogue $catalogue) if ($file instanceof SplFileInfo) { $e->setTemplateName($file->getRelativePathname()); } elseif ($file instanceof \SplFileInfo) { - $e->setTemplateName($file->getRealPath()); + $e->setTemplateName($file->getRealPath() ?: $file->getPathname()); } throw $e; diff --git a/src/Symfony/Bridge/Twig/TwigEngine.php b/src/Symfony/Bridge/Twig/TwigEngine.php index 3e3257e7fa0f5..1ac9d0102e63b 100644 --- a/src/Symfony/Bridge/Twig/TwigEngine.php +++ b/src/Symfony/Bridge/Twig/TwigEngine.php @@ -75,14 +75,14 @@ public function exists($name) $loader = $this->environment->getLoader(); - if ($loader instanceof \Twig_ExistsLoaderInterface) { + if ($loader instanceof \Twig_ExistsLoaderInterface || method_exists($loader, 'exists')) { return $loader->exists((string) $name); } try { // cast possible TemplateReferenceInterface to string because the // EngineInterface supports them but Twig_LoaderInterface does not - $loader->getSource((string) $name); + $loader->getSourceContext((string) $name)->getCode(); } catch (\Twig_Error_Loader $e) { return false; } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 43128a869e894..fd73a7ea3d346 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.5.9", - "twig/twig": "~1.27|~2.0" + "twig/twig": "~1.28|~2.0" }, "require-dev": { "symfony/asset": "~2.8|~3.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index 557ebd7f74987..39f7ac2f541c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -38,7 +38,7 @@ protected function configure() new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), )) ->setDescription('Dumps the current configuration for an extension') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command dumps the current configuration for an extension/bundle. diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index 9732537362f1e..2eb310ddf1701 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -39,7 +39,7 @@ protected function configure() new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), )) ->setDescription('Displays configured listeners for an application') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command displays all configured listeners: php %command.full_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php index fdec77788d952..5e2f273ac8784 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php @@ -41,7 +41,7 @@ protected function configure() )) ->setName('server:start') ->setDescription('Starts PHP built-in web server in the background') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% runs PHP's built-in web server: php %command.full_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php index 9ce6c3b15e841..8f79978a9a845 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php @@ -36,7 +36,7 @@ protected function configure() )) ->setName('server:stop') ->setDescription('Stops PHP\'s built-in web server that was started with the server:start command') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% stops PHP's built-in web server: php %command.full_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index e789a9525f720..299045d126b32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -21,6 +21,8 @@ use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\DataCollectorTranslator; +use Symfony\Component\Translation\LoggingTranslator; /** * Helps finding unused or missing translation messages in a given locale @@ -50,7 +52,7 @@ protected function configure() new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), )) ->setDescription('Displays translation messages information') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command helps finding unused or missing translation messages and comparing them with the fallback ones by inspecting the templates and translation files of a given bundle or the app folder. @@ -307,7 +309,7 @@ private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader { $fallbackCatalogues = array(); $translator = $this->getContainer()->get('translator'); - if ($translator instanceof Translator) { + if ($translator instanceof Translator || $translator instanceof DataCollectorTranslator || $translator instanceof LoggingTranslator) { foreach ($translator->getFallbackLocales() as $fallbackLocale) { if ($fallbackLocale === $locale) { continue; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 2af1e9a91a7dc..c2083e034e32c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -39,6 +39,7 @@ protected function configure() new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages, defaults to app/Resources folder'), new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'), + new InputOption('no-prefix', null, InputOption::VALUE_NONE, 'If set, no prefix is added to the translations'), new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'yml'), new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'), new InputOption('force', null, InputOption::VALUE_NONE, 'Should the update be done'), @@ -132,9 +133,8 @@ protected function execute(InputInterface $input, OutputInterface $output) // load any messages from templates $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $io->comment('Parsing templates...'); - $prefix = $input->getOption('prefix'); $extractor = $this->getContainer()->get('translation.extractor'); - $extractor->setPrefix(null === $prefix ? '' : $prefix); + $extractor->setPrefix($input->getOption('no-prefix') ? '' : $input->getOption('prefix')); foreach ($transPaths as $path) { $path .= 'views'; if (is_dir($path)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index d1b4e2a766d6e..ddfb987ff1937 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Workflow\Dumper\GraphvizDumper; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Workflow; /** * @author Grégoire Pineau @@ -55,24 +56,23 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $workflow = $this->getContainer()->get('workflow.'.$input->getArgument('name')); - $definition = $this->getProperty($workflow, 'definition'); + $container = $this->getContainer(); + $serviceId = $input->getArgument('name'); + if ($container->has('workflow.'.$serviceId)) { + $workflow = $container->get('workflow.'.$serviceId); + } elseif ($container->has('state_machine.'.$serviceId)) { + $workflow = $container->get('state_machine.'.$serviceId); + } else { + throw new \InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $serviceId)); + } $dumper = new GraphvizDumper(); - $marking = new Marking(); + foreach ($input->getArgument('marking') as $place) { $marking->mark($place); } - $output->writeln($dumper->dump($definition, $marking)); - } - - private function getProperty($object, $property) - { - $reflectionProperty = new \ReflectionProperty(get_class($object), $property); - $reflectionProperty->setAccessible(true); - - return $reflectionProperty->getValue($object); + $output->writeln($dumper->dump($workflow->getDefinition(), $marking)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index 083c12c0362d6..41551acc3dc7a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -54,7 +54,7 @@ protected function configure() $this ->setDescription($this->command->getDescription()) ->setDefinition($this->command->getDefinition()) - ->setHelp($this->command->getHelp().<<setHelp($this->command->getHelp().<<<'EOF' Or find all files in a bundle: diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php new file mode 100644 index 0000000000000..4d8e5175c673a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; + +class AddDebugLogProcessorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('profiler')) { + return; + } + if (!$container->hasDefinition('monolog.logger_prototype')) { + return; + } + if (!$container->hasDefinition('debug.log_processor')) { + return; + } + + $definition = $container->getDefinition('monolog.logger_prototype'); + $definition->addMethodCall('pushProcessor', array(new Reference('debug.log_processor'))); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index 3740922b435b2..c856c0206a5d7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -89,7 +89,7 @@ public function process(ContainerBuilder $container) private function getNamespace($namespaceSuffix, $id) { - return substr(str_replace('/', '-', base64_encode(md5($id.$namespaceSuffix, true))), 0, 10); + return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$namespaceSuffix, true))), 0, 10); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php new file mode 100644 index 0000000000000..a2f695992926d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\Workflow\Validator\DefinitionValidatorInterface; +use Symfony\Component\Workflow\Validator\StateMachineValidator; +use Symfony\Component\Workflow\Validator\WorkflowValidator; + +/** + * @author Tobias Nyholm + */ +class ValidateWorkflowsPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $taggedServices = $container->findTaggedServiceIds('workflow.definition'); + foreach ($taggedServices as $id => $tags) { + $definition = $container->get($id); + foreach ($tags as $tag) { + if (!array_key_exists('name', $tag)) { + throw new RuntimeException(sprintf('The "name" for the tag "workflow.definition" of service "%s" must be set.', $id)); + } + if (!array_key_exists('type', $tag)) { + throw new RuntimeException(sprintf('The "type" for the tag "workflow.definition" of service "%s" must be set.', $id)); + } + if (!array_key_exists('marking_store', $tag)) { + throw new RuntimeException(sprintf('The "marking_store" for the tag "workflow.definition" of service "%s" must be set.', $id)); + } + + $this->createValidator($tag)->validate($definition, $tag['name']); + } + } + } + + /** + * @param array $tag + * + * @return DefinitionValidatorInterface + */ + private function createValidator($tag) + { + if ('state_machine' === $tag['type']) { + return new StateMachineValidator(); + } + + if ('single_state' === $tag['marking_store']) { + return new WorkflowValidator(true); + } + + return new WorkflowValidator(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index c17822a3bf0ec..f33085f8724a8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -231,22 +231,31 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode) private function addWorkflowSection(ArrayNodeDefinition $rootNode) { $rootNode + ->fixXmlConfig('workflow') ->children() ->arrayNode('workflows') ->useAttributeAsKey('name') ->prototype('array') + ->fixXmlConfig('support') + ->fixXmlConfig('place') + ->fixXmlConfig('transition') ->children() + ->enumNode('type') + ->values(array('workflow', 'state_machine')) + ->defaultValue('workflow') + ->end() ->arrayNode('marking_store') - ->isRequired() + ->fixXmlConfig('argument') ->children() ->enumNode('type') - ->values(array('property_accessor', 'scalar')) + ->values(array('multiple_state', 'single_state')) ->end() ->arrayNode('arguments') ->beforeNormalization() ->ifString() ->then(function ($v) { return array($v); }) ->end() + ->requiresAtLeastOneElement() ->prototype('scalar') ->end() ->end() @@ -255,13 +264,12 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->end() ->end() ->validate() - ->always(function ($v) { - if (isset($v['type']) && isset($v['service'])) { - throw new \InvalidArgumentException('"type" and "service" could not be used together.'); - } - - return $v; - }) + ->ifTrue(function ($v) { return isset($v['type']) && isset($v['service']); }) + ->thenInvalid('"type" and "service" cannot be used together.') + ->end() + ->validate() + ->ifTrue(function ($v) { return isset($v['arguments']) && isset($v['service']); }) + ->thenInvalid('"arguments" and "service" cannot be used together.') ->end() ->end() ->arrayNode('supports') @@ -278,6 +286,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->end() ->end() ->end() + ->scalarNode('initial_place')->defaultNull()->end() ->arrayNode('places') ->isRequired() ->requiresAtLeastOneElement() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 8d66bf1cf4ffc..5c009962eabfa 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 Symfony\Bridge\Monolog\Processor\DebugProcessor; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -65,7 +66,7 @@ class FrameworkExtension extends Extension */ public function load(array $configs, ContainerBuilder $container) { - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config')); $loader->load('web.xml'); $loader->load('services.xml'); @@ -403,30 +404,59 @@ private function registerWorkflowConfiguration(array $workflows, ContainerBuilde $registryDefinition = $container->getDefinition('workflow.registry'); foreach ($workflows as $name => $workflow) { - $definitionDefinition = new Definition(Workflow\Definition::class); - $definitionDefinition->addMethodCall('addPlaces', array($workflow['places'])); + $type = $workflow['type']; + + $transitions = array(); foreach ($workflow['transitions'] as $transitionName => $transition) { - $definitionDefinition->addMethodCall('addTransition', array(new Definition(Workflow\Transition::class, array($transitionName, $transition['from'], $transition['to'])))); + if ($type === 'workflow') { + $transitions[] = new Definition(Workflow\Transition::class, array($transitionName, $transition['from'], $transition['to'])); + } elseif ($type === 'state_machine') { + foreach ($transition['from'] as $from) { + foreach ($transition['to'] as $to) { + $transitions[] = new Definition(Workflow\Transition::class, array($transitionName, $from, $to)); + } + } + } + } + + // Create a Definition + $definitionDefinition = new Definition(Workflow\Definition::class); + $definitionDefinition->setPublic(false); + $definitionDefinition->addArgument($workflow['places']); + $definitionDefinition->addArgument($transitions); + $definitionDefinition->addTag('workflow.definition', array( + 'name' => $name, + 'type' => $type, + 'marking_store' => isset($workflow['marking_store']['type']) ? $workflow['marking_store']['type'] : null, + )); + if (isset($workflow['initial_place'])) { + $definitionDefinition->addArgument($workflow['initial_place']); } + // Create MarkingStore if (isset($workflow['marking_store']['type'])) { $markingStoreDefinition = new DefinitionDecorator('workflow.marking_store.'.$workflow['marking_store']['type']); foreach ($workflow['marking_store']['arguments'] as $argument) { $markingStoreDefinition->addArgument($argument); } - } else { + } elseif (isset($workflow['marking_store']['service'])) { $markingStoreDefinition = new Reference($workflow['marking_store']['service']); } - $workflowDefinition = new DefinitionDecorator('workflow.abstract'); + // Create Workflow + $workflowDefinition = new DefinitionDecorator(sprintf('%s.abstract', $type)); $workflowDefinition->replaceArgument(0, $definitionDefinition); - $workflowDefinition->replaceArgument(1, $markingStoreDefinition); + if (isset($markingStoreDefinition)) { + $workflowDefinition->replaceArgument(1, $markingStoreDefinition); + } $workflowDefinition->replaceArgument(3, $name); - $workflowId = 'workflow.'.$name; - + // Store to container + $workflowId = sprintf('%s.%s', $type, $name); $container->setDefinition($workflowId, $workflowDefinition); + $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); + // Add workflow to Registry foreach ($workflow['supports'] as $supportedClass) { $registryDefinition->addMethodCall('add', array(new Reference($workflowId), $supportedClass)); } @@ -468,6 +498,12 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con $definition->replaceArgument(4, $debug); $definition->replaceArgument(6, $debug); + + if ($debug && class_exists(DebugProcessor::class)) { + $definition = new Definition(DebugProcessor::class); + $definition->setPublic(false); + $container->setDefinition('debug.log_processor', $definition); + } } /** @@ -805,7 +841,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder if (class_exists('Symfony\Component\Security\Core\Exception\AuthenticationException')) { $r = new \ReflectionClass('Symfony\Component\Security\Core\Exception\AuthenticationException'); - $dirs[] = dirname($r->getFileName()).'/../Resources/translations'; + $dirs[] = dirname(dirname($r->getFileName())).'/Resources/translations'; } $rootDir = $container->getParameter('kernel.root_dir'); foreach ($container->getParameter('kernel.bundles') as $bundle => $class) { @@ -938,21 +974,21 @@ private function getValidatorMappingFiles(ContainerBuilder $container) $dirname = dirname($reflection->getFileName()); if (is_file($file = $dirname.'/Resources/config/validation.xml')) { - $files[0][] = realpath($file); + $files[0][] = $file; $container->addResource(new FileResource($file)); } if (is_file($file = $dirname.'/Resources/config/validation.yml')) { - $files[1][] = realpath($file); + $files[1][] = $file; $container->addResource(new FileResource($file)); } if (is_dir($dir = $dirname.'/Resources/config/validation')) { foreach (Finder::create()->files()->in($dir)->name('*.xml') as $file) { - $files[0][] = $file->getRealPath(); + $files[0][] = $file->getPathname(); } foreach (Finder::create()->files()->in($dir)->name('*.yml') as $file) { - $files[1][] = $file->getRealPath(); + $files[1][] = $file->getPathname(); } $container->addResource(new DirectoryResource($dir)); @@ -1115,7 +1151,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $dirname = dirname($reflection->getFileName()); if (is_file($file = $dirname.'/Resources/config/serialization.xml')) { - $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', array(realpath($file))); + $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', array($file)); $definition->setPublic(false); $serializerLoaders[] = $definition; @@ -1123,7 +1159,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder } if (is_file($file = $dirname.'/Resources/config/serialization.yml')) { - $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader', array(realpath($file))); + $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader', array($file)); $definition->setPublic(false); $serializerLoaders[] = $definition; @@ -1132,13 +1168,13 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder if (is_dir($dir = $dirname.'/Resources/config/serialization')) { foreach (Finder::create()->files()->in($dir)->name('*.xml') as $file) { - $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', array($file->getRealPath())); + $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', array($file->getPathname())); $definition->setPublic(false); $serializerLoaders[] = $definition; } foreach (Finder::create()->files()->in($dir)->name('*.yml') as $file) { - $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader', array($file->getRealPath())); + $definition = new Definition('Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader', array($file->getPathname())); $definition->setPublic(false); $serializerLoaders[] = $definition; @@ -1201,7 +1237,7 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild private function registerCacheConfiguration(array $config, ContainerBuilder $container) { - $version = substr(str_replace('/', '-', base64_encode(md5(uniqid(mt_rand(), true), true))), 0, -2); + $version = substr(str_replace('/', '-', base64_encode(hash('sha256', uniqid(mt_rand(), true), true))), 0, 22); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); @@ -1265,7 +1301,7 @@ private function getKernelRootHash(ContainerBuilder $container) */ public function getXsdValidationBasePath() { - return __DIR__.'/../Resources/config/schema'; + return dirname(__DIR__).'/Resources/config/schema'; } public function getNamespace() diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 7148f56046789..72873aa6e97e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass; @@ -34,6 +35,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ValidateWorkflowsPass; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -92,9 +94,11 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new PropertyInfoPass()); $container->addCompilerPass(new ControllerArgumentValueResolverPass()); $container->addCompilerPass(new CachePoolPass()); + $container->addCompilerPass(new ValidateWorkflowsPass()); $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); if ($container->getParameter('kernel.debug')) { + $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1); $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); 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 804072e07d588..fbf53309f41be 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 @@ -8,7 +8,7 @@ - + @@ -26,8 +26,8 @@ - - + + @@ -228,41 +228,45 @@ - - - - - - - - - + + + - + + + - - - + + + - - - - - + + + + + + - + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml index d3f1fdd43ca6f..5370d38072536 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -24,10 +24,15 @@ - + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml index d37b5d3e0670d..76592087a2260 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml @@ -7,13 +7,19 @@ - + null + + + + + + null - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php index 324eb4782c2cc..bf4a4c478502b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php @@ -1 +1 @@ -block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'text')) ?> +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'text')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php index 4390687a69330..ec96cfb46b24c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php @@ -1 +1 @@ -block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'password')) ?> +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'password')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php index 59b29f4cbcfaa..8519da429b188 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/percent_widget.html.php @@ -1 +1 @@ -block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'text')) ?> % +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'text')) ?> % diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php index 1575e8292801e..e8fa18e488df8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php @@ -1 +1 @@ -block($form, 'button_widget', array('type' => isset($type) ? $type : 'reset')) ?> +block($form, 'button_widget', array('type' => isset($type) ? $type : 'reset')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php index 4e442f6ef47ae..48a33f4aa2dbc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/search_widget.html.php @@ -1 +1 @@ -block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'search')) ?> +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'search')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php index d42bb2a78ffe9..6bf71f5a1e1c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php @@ -1 +1 @@ -block($form, 'button_widget', array('type' => isset($type) ? $type : 'submit')) ?> +block($form, 'button_widget', array('type' => isset($type) ? $type : 'submit')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_widget.html.php index 0ce4ed2ca79fd..9e26318497b3c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_widget.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_widget.html.php @@ -1 +1 @@ -block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'url')) ?> +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'url')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 76bf546690725..afef4093db290 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -160,7 +160,6 @@ private function resolve($value) gettype($resolved) ) ); - }, $value); return str_replace('%%', '%', $escapedValue); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php index 3c1728575081e..3dc64a300e8c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -88,7 +88,6 @@ private function getContainer() ->will($this->returnValueMap(array( array('router', 1, $router), array('controller_name_converter', 1, $loader), - ))); return $container; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index c99c0ace2ac77..27f61c4383f24 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -136,7 +136,7 @@ private function getContainer($extractedMessages = array(), $loadedMessages = ar ->method('extract') ->will( $this->returnCallback(function ($path, $catalogue) use ($extractedMessages) { - $catalogue->add($extractedMessages); + $catalogue->add($extractedMessages); }) ); @@ -146,7 +146,7 @@ private function getContainer($extractedMessages = array(), $loadedMessages = ar ->method('loadMessages') ->will( $this->returnCallback(function ($path, $catalogue) use ($loadedMessages) { - $catalogue->add($loadedMessages); + $catalogue->add($loadedMessages); }) ); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index 243e81ed0e478..c876ee415de3c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -118,7 +118,7 @@ private function getContainer($extractedMessages = array(), $loadedMessages = ar ->method('loadMessages') ->will( $this->returnCallback(function ($path, $catalogue) use ($loadedMessages) { - $catalogue->add($loadedMessages); + $catalogue->add($loadedMessages); }) ); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php index 6644bb2a5c2e2..78a770da85a05 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -129,6 +129,7 @@ public function getDescribeCallableTestData() } abstract protected function getDescriptor(); + abstract protected function getFormat(); private function assertDescription($expectedDescription, $describedObject, array $options = array()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index 4bd93f2ba3f71..5244072ae85cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -158,9 +158,11 @@ class CallableClass public function __invoke() { } + public static function staticMethod() { } + public function method() { } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php index 1f77cdd0fe60b..53cb95837f7e8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php @@ -41,7 +41,7 @@ public function testNamespaceArgumentIsReplaced() $this->cachePoolPass->process($container); - $this->assertSame('VcRIZlUhEv', $cachePool->getArgument(0)); + $this->assertSame('kRFqMp5odS', $cachePool->getArgument(0)); } public function testArgsAreReplaced() @@ -61,7 +61,7 @@ public function testArgsAreReplaced() $this->assertInstanceOf(Reference::class, $cachePool->getArgument(0)); $this->assertSame('foobar', (string) $cachePool->getArgument(0)); - $this->assertSame('VcRIZlUhEv', $cachePool->getArgument(1)); + $this->assertSame('kRFqMp5odS', $cachePool->getArgument(1)); $this->assertSame(3, $cachePool->getArgument(2)); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_arguments_and_service.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_arguments_and_service.php new file mode 100644 index 0000000000000..d97f9700a0f1b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_arguments_and_service.php @@ -0,0 +1,31 @@ +loadFromExtension('framework', array( + 'workflows' => array( + 'my_workflow' => array( + 'marking_store' => array( + 'arguments' => array('a', 'b'), + 'service' => 'workflow_service', + ), + 'supports' => array( + FrameworkExtensionTest::class, + ), + 'places' => array( + 'first', + 'last', + ), + 'transitions' => array( + 'go' => array( + 'from' => array( + 'first', + ), + 'to' => array( + 'last', + ), + ), + ), + ), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_type_and_service.php similarity index 88% rename from src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow.php rename to src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_type_and_service.php index 7f29cc385ba5b..7d9e596408c97 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_type_and_service.php @@ -6,7 +6,8 @@ 'workflows' => array( 'my_workflow' => array( 'marking_store' => array( - 'type' => 'property_accessor', + 'type' => 'multiple_state', + 'service' => 'workflow_service', ), 'supports' => array( FrameworkExtensionTest::class, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php new file mode 100644 index 0000000000000..854a858415e00 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php @@ -0,0 +1,92 @@ +loadFromExtension('framework', array( + 'workflows' => array( + 'article' => array( + 'type' => 'workflow', + 'marking_store' => array( + 'type' => 'multiple_state', + ), + 'supports' => array( + FrameworkExtensionTest::class, + ), + 'initial_place' => 'draft', + 'places' => array( + 'draft', + 'wait_for_journalist', + 'approved_by_journalist', + 'wait_for_spellchecker', + 'approved_by_spellchecker', + 'published', + ), + 'transitions' => array( + 'request_review' => array( + 'from' => 'draft', + 'to' => array('wait_for_journalist', 'wait_for_spellchecker'), + ), + 'journalist_approval' => array( + 'from' => 'wait_for_journalist', + 'to' => 'approved_by_journalist', + ), + 'spellchecker_approval' => array( + 'from' => 'wait_for_spellchecker', + 'to' => 'approved_by_spellchecker', + ), + 'publish' => array( + 'from' => array('approved_by_journalist', 'approved_by_spellchecker'), + 'to' => 'published', + ), + ), + ), + 'pull_request' => array( + 'type' => 'state_machine', + 'marking_store' => array( + 'type' => 'single_state', + ), + 'supports' => array( + FrameworkExtensionTest::class, + ), + 'initial_place' => 'start', + 'places' => array( + 'start', + 'coding', + 'travis', + 'review', + 'merged', + 'closed', + ), + 'transitions' => array( + 'submit' => array( + 'from' => 'start', + 'to' => 'travis', + ), + 'update' => array( + 'from' => array('coding', 'travis', 'review'), + 'to' => 'travis', + ), + 'wait_for_review' => array( + 'from' => 'travis', + 'to' => 'review', + ), + 'request_change' => array( + 'from' => 'review', + 'to' => 'coding', + ), + 'accept' => array( + 'from' => 'review', + 'to' => 'merged', + ), + 'reject' => array( + 'from' => 'review', + 'to' => 'closed', + ), + 'reopen' => array( + 'from' => 'closed', + 'to' => 'review', + ), + ), + ), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow.xml deleted file mode 100644 index add799b82fd44..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - property_accessor - a - a - - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest - first - last - - - a - a - - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_arguments_and_service.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_arguments_and_service.xml new file mode 100644 index 0000000000000..02502296f77de --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_arguments_and_service.xml @@ -0,0 +1,24 @@ + + + + + + + + a + a + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + first + last + + a + a + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_type_and_service.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_type_and_service.xml new file mode 100644 index 0000000000000..7ec450f6537ee --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_type_and_service.xml @@ -0,0 +1,21 @@ + + + + + + + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + first + last + + a + a + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflows.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflows.xml new file mode 100644 index 0000000000000..924ff75111ca8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflows.xml @@ -0,0 +1,83 @@ + + + + + + + + a + a + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + draft + wait_for_journalist + approved_by_journalist + wait_for_spellchecker + approved_by_spellchecker + published + + draft + wait_for_journalist + wait_for_spellchecker + + + wait_for_journalist + approved_by_journalist + + + wait_for_spellcheker + approved_by_spellchker + + + approved_by_journalist + approved_by_spellchker + published + + + + + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + start + coding + travis + review + merged + closed + + start + travis + + + coding + travis + review + travis + + + travis + review + + + review + coding + + + review + merged + + + review + closed + + + closed + review + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_arguments_and_service.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_arguments_and_service.yml new file mode 100644 index 0000000000000..a46d4b67e6b24 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_arguments_and_service.yml @@ -0,0 +1,19 @@ +framework: + workflows: + my_workflow: + marking_store: + arguments: + - a + - b + service: workflow_service + supports: + - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + places: + - first + - last + transitions: + go: + from: + - first + to: + - last diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_type_and_service.yml similarity index 84% rename from src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow.yml rename to src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_type_and_service.yml index e9eb8e1977a9d..000ba10dfb8d2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_type_and_service.yml @@ -2,7 +2,8 @@ framework: workflows: my_workflow: marking_store: - type: property_accessor + type: multiple_state + service: workflow_service supports: - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest places: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflows.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflows.yml new file mode 100644 index 0000000000000..a933db5100fc8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflows.yml @@ -0,0 +1,65 @@ +framework: + workflows: + article: + type: workflow + marking_store: + type: multiple_state + supports: + - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + initial_place: draft + places: + - draft + - wait_for_journalist + - approved_by_journalist + - wait_for_spellchecker + - approved_by_spellchecker + - published + transitions: + request_review: + from: [draft] + to: [wait_for_journalist, wait_for_spellchecker] + journalist_approval: + from: [wait_for_journalist] + to: [approved_by_journalist] + spellchecker_approval: + from: [wait_for_spellchecker] + to: [approved_by_spellchecker] + publish: + from: [approved_by_journalist, approved_by_spellchecker] + to: [published] + pull_request: + type: state_machine + marking_store: + type: single_state + supports: + - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + initial_place: start + places: + - start + - coding + - travis + - review + - merged + - closed + transitions: + submit: + from: start + to: travis + update: + from: [coding, travis, review] + to: travis + wait_for_review: + from: travis + to: review + request_change: + from: review + to: coding + accept: + from: review + to: merged + reject: + from: review + to: closed + reopen: + from: closed + to: review diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index b90da1284319b..c1ee84685654f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -120,11 +120,69 @@ public function testDisabledProfiler() $this->assertFalse($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() does not load collectors.xml'); } - public function testWorkflow() + public function testWorkflows() + { + $container = $this->createContainerFromFile('workflows'); + + $this->assertTrue($container->hasDefinition('workflow.article', 'Workflow is registered as a service')); + $this->assertTrue($container->hasDefinition('workflow.article.definition', 'Workflow definition is registered as a service')); + + $workflowDefinition = $container->getDefinition('workflow.article.definition'); + + $this->assertSame( + array( + 'draft', + 'wait_for_journalist', + 'approved_by_journalist', + 'wait_for_spellchecker', + 'approved_by_spellchecker', + 'published', + ), + $workflowDefinition->getArgument(0), + 'Places are passed to the workflow definition' + ); + $this->assertSame(array('workflow.definition' => array(array('name' => 'article', 'type' => 'workflow', 'marking_store' => 'multiple_state'))), $workflowDefinition->getTags()); + + $this->assertTrue($container->hasDefinition('state_machine.pull_request', 'State machine is registered as a service')); + $this->assertTrue($container->hasDefinition('state_machine.pull_request.definition', 'State machine definition is registered as a service')); + $this->assertCount(4, $workflowDefinition->getArgument(1)); + $this->assertSame('draft', $workflowDefinition->getArgument(2)); + + $stateMachineDefinition = $container->getDefinition('state_machine.pull_request.definition'); + + $this->assertSame( + array( + 'start', + 'coding', + 'travis', + 'review', + 'merged', + 'closed', + ), + $stateMachineDefinition->getArgument(0), + 'Places are passed to the state machine definition' + ); + $this->assertSame(array('workflow.definition' => array(array('name' => 'pull_request', 'type' => 'state_machine', 'marking_store' => 'single_state'))), $stateMachineDefinition->getTags()); + $this->assertCount(9, $stateMachineDefinition->getArgument(1)); + $this->assertSame('start', $stateMachineDefinition->getArgument(2)); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage "type" and "service" cannot be used together. + */ + public function testWorkflowCannotHaveBothTypeAndService() { - $container = $this->createContainerFromFile('workflow'); + $this->createContainerFromFile('workflow_with_type_and_service'); + } - $this->assertTrue($container->hasDefinition('workflow.my_workflow')); + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage "arguments" and "service" cannot be used together. + */ + public function testWorkflowCannotHaveBothArgumentsAndService() + { + $this->createContainerFromFile('workflow_with_arguments_and_service'); } public function testRouter() @@ -409,11 +467,11 @@ public function testValidationPaths() // Testing symfony/framework-bundle with deps=high $this->assertStringEndsWith('symfony'.DIRECTORY_SEPARATOR.'form/Resources/config/validation.xml', $xmlMappings[0]); } - $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.xml', $xmlMappings[1]); + $this->assertStringEndsWith('TestBundle/Resources/config/validation.xml', $xmlMappings[1]); $yamlMappings = $calls[4][1][0]; $this->assertCount(1, $yamlMappings); - $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.yml', $yamlMappings[0]); + $this->assertStringEndsWith('TestBundle/Resources/config/validation.yml', $yamlMappings[0]); } public function testValidationNoStaticMethod() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php index 04df261863f48..c0ae6ec5c6604 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php @@ -1,7 +1,7 @@ This template is used for translation message extraction tests trans('single-quoted key') ?> trans('double-quoted key') ?> -trans(<<trans(<<<'EOF' heredoc key EOF ) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php new file mode 100644 index 0000000000000..ee79850c47dfb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; + +/** + * @author Kévin Dunglas + */ +class SerializerTest extends WebTestCase +{ + public function testDeserializeArrayOfObject() + { + if (!class_exists(DataUriNormalizer::class)) { + $this->markTestSkipped('This test is only applicable when using the Symfony Serializer Component version 3.1 or superior.'); + } + + static::bootKernel(array('test_case' => 'Serializer')); + $container = static::$kernel->getContainer(); + + $result = $container->get('serializer')->deserialize('{"bars": [{"id": 1}, {"id": 2}]}', Foo::class, 'json'); + + $bar1 = new Bar(); + $bar1->id = 1; + $bar2 = new Bar(); + $bar2->id = 2; + + $expected = new Foo(); + $expected->bars = array($bar1, $bar2); + + $this->assertEquals($expected, $result); + } +} + +class Foo +{ + /** + * @var Bar[] + */ + public $bars; +} + +class Bar +{ + /** + * @var int + */ + public $id; +} diff --git a/src/Symfony/Component/Workflow/MarkingStore/UniqueTransitionOutputInterface.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/bundles.php similarity index 55% rename from src/Symfony/Component/Workflow/MarkingStore/UniqueTransitionOutputInterface.php rename to src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/bundles.php index 35c00eb58d101..144db90236034 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/UniqueTransitionOutputInterface.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/bundles.php @@ -9,13 +9,8 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Workflow\MarkingStore; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; -/** - * UniqueTransitionOutputInterface. - * - * @author Grégoire Pineau - */ -interface UniqueTransitionOutputInterface -{ -} +return array( + new FrameworkBundle(), +); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml new file mode 100644 index 0000000000000..cac135c315d00 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml @@ -0,0 +1,6 @@ +imports: + - { resource: ../config/default.yml } + +framework: + serializer: { enabled: true } + property_info: { enabled: true } diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 2c66e86c34c15..544e1de1921af 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -21,6 +21,8 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\Security\Http\FirewallMapInterface; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; /** * SecurityDataCollector. @@ -33,6 +35,7 @@ class SecurityDataCollector extends DataCollector private $roleHierarchy; private $logoutUrlGenerator; private $accessDecisionManager; + private $firewallMap; /** * Constructor. @@ -41,13 +44,15 @@ class SecurityDataCollector extends DataCollector * @param RoleHierarchyInterface|null $roleHierarchy * @param LogoutUrlGenerator|null $logoutUrlGenerator * @param AccessDecisionManagerInterface|null $accessDecisionManager + * @param FirewallMapInterface|null $firewallMap */ - public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null) + public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null, AccessDecisionManagerInterface $accessDecisionManager = null, FirewallMapInterface $firewallMap = null) { $this->tokenStorage = $tokenStorage; $this->roleHierarchy = $roleHierarchy; $this->logoutUrlGenerator = $logoutUrlGenerator; $this->accessDecisionManager = $accessDecisionManager; + $this->firewallMap = $firewallMap; } /** @@ -108,7 +113,7 @@ public function collect(Request $request, Response $response, \Exception $except 'token_class' => get_class($token), 'logout_url' => $logoutUrl, 'user' => $token->getUsername(), - 'roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole();}, $assignedRoles)), + 'roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $assignedRoles)), 'inherited_roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles)), 'supports_role_hierarchy' => null !== $this->roleHierarchy, ); @@ -132,6 +137,28 @@ public function collect(Request $request, Response $response, \Exception $except $this->data['voter_strategy'] = 'unknown'; $this->data['voters'] = array(); } + + // collect firewall context information + $this->data['firewall'] = null; + if ($this->firewallMap instanceof FirewallMap) { + $firewallConfig = $this->firewallMap->getFirewallConfig($request); + if (null !== $firewallConfig) { + $this->data['firewall'] = array( + 'name' => $firewallConfig->getName(), + 'allows_anonymous' => $firewallConfig->allowsAnonymous(), + 'request_matcher' => $firewallConfig->getRequestMatcher(), + 'security_enabled' => $firewallConfig->isSecurityEnabled(), + 'stateless' => $firewallConfig->isStateless(), + 'provider' => $firewallConfig->getProvider(), + 'context' => $firewallConfig->getContext(), + 'entry_point' => $firewallConfig->getEntryPoint(), + 'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(), + 'access_denied_url' => $firewallConfig->getAccessDeniedUrl(), + 'user_checker' => $firewallConfig->getUserChecker(), + 'listeners' => $this->cloneVar($firewallConfig->getListeners()), + ); + } + } } /** @@ -255,6 +282,16 @@ public function getAccessDecisionLog() return $this->data['access_decision_log']; } + /** + * Returns the configuration of the current firewall context. + * + * @return array + */ + public function getFirewall() + { + return $this->data['firewall']; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index bf828ed22aa0e..b0aadc2f19e43 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -386,11 +386,10 @@ private function addEncodersSection(ArrayNodeDefinition $rootNode) ->children() ->arrayNode('encoders') ->example(array( - 'Acme\DemoBundle\Entity\User1' => 'sha512', - 'Acme\DemoBundle\Entity\User2' => array( - 'algorithm' => 'sha512', - 'encode_as_base64' => 'true', - 'iterations' => 5000, + 'AppBundle\Entity\User1' => 'bcrypt', + 'AppBundle\Entity\User2' => array( + 'algorithm' => 'bcrypt', + 'cost' => 13, ), )) ->requiresAtLeastOneElement() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php index 2b3310c61aae4..028e885246f61 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php @@ -25,7 +25,7 @@ interface SecurityFactoryInterface * Configures the container services required to use the authentication listener. * * @param ContainerBuilder $container - * @param string $id The unique id of the firewall + * @param string $id The unique id of the firewall * @param array $config The options array for the listener * @param string $userProvider The service id of the user provider * @param string $defaultEntryPoint @@ -48,7 +48,7 @@ public function getPosition(); /** * Defines the configuration key used to reference the provider * in the firewall configuration. - * + * * @return string */ public function getKey(); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 4398a2a36d433..5c88548bcff1d 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -110,6 +110,7 @@ public function load(array $configs, ContainerBuilder $container) 'Symfony\Component\Security\Core\Authorization\AccessDecisionManager', 'Symfony\Component\Security\Core\Authorization\AuthorizationChecker', 'Symfony\Component\Security\Core\Authorization\Voter\VoterInterface', + 'Symfony\Bundle\SecurityBundle\Security\FirewallConfig', 'Symfony\Bundle\SecurityBundle\Security\FirewallMap', 'Symfony\Bundle\SecurityBundle\Security\FirewallContext', 'Symfony\Component\HttpFoundation\RequestMatcher', @@ -236,14 +237,18 @@ private function createFirewalls($config, ContainerBuilder $container) $mapDef = $container->getDefinition('security.firewall.map'); $map = $authenticationProviders = array(); foreach ($firewalls as $name => $firewall) { - list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds); + $configId = 'security.firewall.map.config.'.$name; + + list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId); $contextId = 'security.firewall.map.context.'.$name; $context = $container->setDefinition($contextId, new DefinitionDecorator('security.firewall.context')); $context ->replaceArgument(0, $listeners) ->replaceArgument(1, $exceptionListener) + ->replaceArgument(2, new Reference($configId)) ; + $map[$contextId] = $matcher; } $mapDef->replaceArgument(1, $map); @@ -258,8 +263,11 @@ private function createFirewalls($config, ContainerBuilder $container) ; } - private function createFirewall(ContainerBuilder $container, $id, $firewall, &$authenticationProviders, $providerIds) + private function createFirewall(ContainerBuilder $container, $id, $firewall, &$authenticationProviders, $providerIds, $configId) { + $config = $container->setDefinition($configId, new DefinitionDecorator('security.firewall.config')); + $config->replaceArgument(0, $id); + // Matcher $matcher = null; if (isset($firewall['request_matcher'])) { @@ -271,11 +279,17 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $matcher = $this->createRequestMatcher($container, $pattern, $host, $methods); } + $config->replaceArgument(1, (string) $matcher); + $config->replaceArgument(2, $firewall['user_checker']); + $config->replaceArgument(3, $firewall['security']); + // Security disabled? if (false === $firewall['security']) { return array($matcher, array(), null); } + $config->replaceArgument(4, $firewall['stateless']); + // Provider id (take the first registered provider if none defined) if (isset($firewall['provider'])) { $defaultProvider = $this->getUserProviderId($firewall['provider']); @@ -283,8 +297,11 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $defaultProvider = reset($providerIds); } + $config->replaceArgument(5, $defaultProvider); + // Register listeners $listeners = array(); + $listenerKeys = array(); // Channel listener $listeners[] = new Reference('security.channel_listener'); @@ -296,11 +313,14 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a $contextKey = $firewall['context']; } + $config->replaceArgument(6, $contextKey); + $listeners[] = new Reference($this->createContextListener($container, $contextKey)); } // Logout listener if (isset($firewall['logout'])) { + $listenerKeys[] = 'logout'; $listenerId = 'security.logout_listener.'.$id; $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener')); $listener->replaceArgument(3, array( @@ -363,10 +383,13 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a // Authentication listeners list($authListeners, $defaultEntryPoint) = $this->createAuthenticationListeners($container, $id, $firewall, $authenticationProviders, $defaultProvider, $configuredEntryPoint); + $config->replaceArgument(7, $configuredEntryPoint ?: $defaultEntryPoint); + $listeners = array_merge($listeners, $authListeners); // Switch user listener if (isset($firewall['switch_user'])) { + $listenerKeys[] = 'switch_user'; $listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider)); } @@ -376,8 +399,30 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a // Exception listener $exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless'])); + if (isset($firewall['access_denied_handler'])) { + $config->replaceArgument(8, $firewall['access_denied_handler']); + } + if (isset($firewall['access_denied_url'])) { + $config->replaceArgument(9, $firewall['access_denied_url']); + } + $container->setAlias(new Alias('security.user_checker.'.$id, false), $firewall['user_checker']); + foreach ($this->factories as $position) { + foreach ($position as $factory) { + $key = str_replace('-', '_', $factory->getKey()); + if (array_key_exists($key, $firewall)) { + $listenerKeys[] = $key; + } + } + } + + if (isset($firewall['anonymous'])) { + $listenerKeys[] = 'anonymous'; + } + + $config->replaceArgument(10, $listenerKeys); + return array($matcher, $listeners, $exceptionListener); } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml index de157d51824ce..f812a9d790bb8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml @@ -11,6 +11,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index c34453cb3854f..8def320482486 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -111,6 +111,21 @@ + + + + + + + + + + + + + + + @@ -119,7 +134,6 @@ - diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig index e857f579e30b3..3830e924a190b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig @@ -16,33 +16,42 @@ {% endset %} {% set text %} - {% if collector.token %} -
- Logged in as - {{ collector.user }} -
+ {% if collector.enabled %} + {% if collector.token %} +
+ Logged in as + {{ collector.user }} +
-
- Authenticated - {{ is_authenticated ? 'Yes' : 'No' }} -
+
+ Authenticated + {{ is_authenticated ? 'Yes' : 'No' }} +
- {% if collector.token != null %} -
- Token class - {{ collector.tokenClass|abbr_class }} -
+
+ Token class + {{ collector.tokenClass|abbr_class }} +
+ {% else %} +
+ Authenticated + No +
{% endif %} - {% if collector.logoutUrl %} -
- Actions - Logout -
+ + {% if collector.firewall %} +
+ Firewall name + {{ collector.firewall.name }} +
+ {% endif %} + + {% if collector.token and collector.logoutUrl %} +
+ Actions + Logout +
{% endif %} - {% elseif collector.enabled %} -
- You are not authenticated. -
{% else %}
The security is disabled. @@ -63,57 +72,128 @@ {% block panel %}

Security Token

- {% if collector.token %} -
-
- {{ collector.user == 'anon.' ? 'Anonymous' : collector.user }} - Username + {% if collector.enabled %} + {% if collector.token %} +
+
+ {{ collector.user == 'anon.' ? 'Anonymous' : collector.user }} + Username +
+ +
+ {{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} + Authenticated +
-
- {{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} - Authenticated + + + + + + + + + + + + + + {% if collector.supportsRoleHierarchy %} + + + + + {% endif %} + + {% if collector.token %} + + + + + {% endif %} + +
PropertyValue
Roles + {{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }} + + {% if not collector.authenticated and collector.roles is empty %} +

User is not authenticated probably because they have no roles.

+ {% endif %} +
Inherited Roles{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}
Token{{ profiler_dump(collector.token) }}
+ {% elseif collector.enabled %} +
+

There is no security token.

-
+ {% endif %} - - - - - - - - - - - - - {% if collector.supportsRoleHierarchy %} - - - - - {% endif %} +

Security Firewall

- {% if collector.token %} - - - - - {% endif %} - -
PropertyValue
Roles - {{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }} - - {% if not collector.authenticated and collector.roles is empty %} -

User is not authenticated probably because they have no roles.

- {% endif %} -
Inherited Roles{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}
Token{{ profiler_dump(collector.token) }}
- {% elseif collector.enabled %} -
-

There is no security token.

-
+ {% if collector.firewall %} +
+
+ {{ collector.firewall.name }} + Name +
+
+ {{ include('@WebProfiler/Icon/' ~ (collector.firewall.security_enabled ? 'yes' : 'no') ~ '.svg') }} + Security enabled +
+
+ {{ include('@WebProfiler/Icon/' ~ (collector.firewall.stateless ? 'yes' : 'no') ~ '.svg') }} + Stateless +
+
+ {{ include('@WebProfiler/Icon/' ~ (collector.firewall.allows_anonymous ? 'yes' : 'no') ~ '.svg') }} + Allows anonymous +
+
+ + {% if collector.firewall.security_enabled %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
provider{{ collector.firewall.provider ?: '(none)' }}
context{{ collector.firewall.context ?: '(none)' }}
entry_point{{ collector.firewall.entry_point ?: '(none)' }}
user_checker{{ collector.firewall.user_checker ?: '(none)' }}
access_denied_handler{{ collector.firewall.access_denied_handler ?: '(none)' }}
access_denied_url{{ collector.firewall.access_denied_url ?: '(none)' }}
listeners{{ collector.firewall.listeners is empty ? '(none)' : profiler_dump(collector.firewall.listeners, maxDepth=1) }}
+ {% endif %} + {% elseif collector.enabled %} +
+

This request was not covered by any firewall.

+
+ {% endif %} {% else %}

The security component is disabled.

diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php new file mode 100644 index 0000000000000..a36ff955130d1 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallConfig.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +/** + * @author Robin Chalas + */ +final class FirewallConfig +{ + private $name; + private $requestMatcher; + private $userChecker; + private $securityEnabled; + private $stateless; + private $provider; + private $context; + private $entryPoint; + private $accessDeniedHandler; + private $accessDeniedUrl; + private $listeners; + + /** + * @param string $name + * @param string $requestMatcher + * @param string $userChecker + * @param bool $securityEnabled + * @param bool $stateless + * @param string|null $provider + * @param string|null $context + * @param string|null $entryPoint + * @param string|null $accessDeniedHandler + * @param string|null $accessDeniedUrl + * @param string[] $listeners + */ + public function __construct($name, $requestMatcher, $userChecker, $securityEnabled = true, $stateless = false, $provider = null, $context = null, $entryPoint = null, $accessDeniedHandler = null, $accessDeniedUrl = null, $listeners = array()) + { + $this->name = $name; + $this->requestMatcher = $requestMatcher; + $this->userChecker = $userChecker; + $this->securityEnabled = $securityEnabled; + $this->stateless = $stateless; + $this->provider = $provider; + $this->context = $context; + $this->entryPoint = $entryPoint; + $this->accessDeniedHandler = $accessDeniedHandler; + $this->accessDeniedUrl = $accessDeniedUrl; + $this->listeners = $listeners; + } + + public function getName() + { + return $this->name; + } + + /** + * @return string The request matcher service id + */ + public function getRequestMatcher() + { + return $this->requestMatcher; + } + + public function isSecurityEnabled() + { + return $this->securityEnabled; + } + + public function allowsAnonymous() + { + return in_array('anonymous', $this->listeners, true); + } + + public function isStateless() + { + return $this->stateless; + } + + /** + * @return string|null The provider service id + */ + public function getProvider() + { + return $this->provider; + } + + /** + * @return string|null The context key (will be null if the firewall is stateless) + */ + public function getContext() + { + return $this->context; + } + + /** + * @return string|null The entry_point service id if configured, null otherwise + */ + public function getEntryPoint() + { + return $this->entryPoint; + } + + /** + * @return string The user_checker service id + */ + public function getUserChecker() + { + return $this->userChecker; + } + + /** + * @return string|null The access_denied_handler service id if configured, null otherwise + */ + public function getAccessDeniedHandler() + { + return $this->accessDeniedHandler; + } + + /** + * @return string|null The access_denied_handler URL if configured, null otherwise + */ + public function getAccessDeniedUrl() + { + return $this->accessDeniedUrl; + } + + /** + * @return string[] An array of listener keys + */ + public function getListeners() + { + return $this->listeners; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index 13d096d97e951..9d00c121e5160 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -23,11 +23,22 @@ class FirewallContext { private $listeners; private $exceptionListener; + private $config; - public function __construct(array $listeners, ExceptionListener $exceptionListener = null) + public function __construct(array $listeners, ExceptionListener $exceptionListener = null, FirewallConfig $config = null) { + if (null === $config) { + @trigger_error(sprintf('"%s()" expects an instance of "%s" as third argument since version 3.2 and will trigger an error in 4.0 if not provided.', __METHOD__, FirewallConfig::class), E_USER_DEPRECATED); + } + $this->listeners = $listeners; $this->exceptionListener = $exceptionListener; + $this->config = $config; + } + + public function getConfig() + { + return $this->config; } public function getContext() diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php index da79e3c8c2926..f833a63e65966 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallMap.php @@ -35,7 +35,35 @@ public function __construct(ContainerInterface $container, array $map) $this->contexts = new \SplObjectStorage(); } + /** + * {@inheritdoc} + */ public function getListeners(Request $request) + { + $context = $this->getFirewallContext($request); + + if (null === $context) { + return array(array(), null); + } + + return $context->getContext(); + } + + /** + * @return FirewallConfig|null + */ + public function getFirewallConfig(Request $request) + { + $context = $this->getFirewallContext($request); + + if (null === $context) { + return; + } + + return $context->getConfig(); + } + + private function getFirewallContext(Request $request) { if ($this->contexts->contains($request)) { return $this->contexts[$request]; @@ -43,10 +71,8 @@ public function getListeners(Request $request) foreach ($this->map as $contextId => $requestMatcher) { if (null === $requestMatcher || $requestMatcher->matches($request)) { - return $this->contexts[$request] = $this->container->get($contextId)->getContext(); + return $this->contexts[$request] = $this->container->get($contextId); } } - - return array(array(), null); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index a9b6818d64f50..2d912721ed4f4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -12,10 +12,13 @@ namespace Symfony\Bundle\SecurityBundle\Tests\DataCollector; use Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Bundle\SecurityBundle\Security\FirewallMap; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Security\Http\FirewallMapInterface; class SecurityDataCollectorTest extends \PHPUnit_Framework_TestCase { @@ -32,6 +35,7 @@ public function testCollectWhenSecurityIsDisabled() $this->assertCount(0, $collector->getRoles()); $this->assertCount(0, $collector->getInheritedRoles()); $this->assertEmpty($collector->getUser()); + $this->assertNull($collector->getFirewall()); } public function testCollectWhenAuthenticationTokenIsNull() @@ -47,6 +51,7 @@ public function testCollectWhenAuthenticationTokenIsNull() $this->assertCount(0, $collector->getRoles()); $this->assertCount(0, $collector->getInheritedRoles()); $this->assertEmpty($collector->getUser()); + $this->assertNull($collector->getFirewall()); } /** @dataProvider provideRoles */ @@ -71,6 +76,70 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm $this->assertSame('hhamon', $collector->getUser()); } + public function testGetFirewall() + { + $firewallConfig = new FirewallConfig('dummy', 'security.request_matcher.dummy', 'security.user_checker.dummy'); + $request = $this->getRequest(); + + $firewallMap = $this + ->getMockBuilder(FirewallMap::class) + ->disableOriginalConstructor() + ->getMock(); + $firewallMap + ->expects($this->once()) + ->method('getFirewallConfig') + ->with($request) + ->willReturn($firewallConfig); + + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap); + $collector->collect($request, $this->getResponse()); + $collected = $collector->getFirewall(); + + $this->assertSame($firewallConfig->getName(), $collected['name']); + $this->assertSame($firewallConfig->allowsAnonymous(), $collected['allows_anonymous']); + $this->assertSame($firewallConfig->getRequestMatcher(), $collected['request_matcher']); + $this->assertSame($firewallConfig->isSecurityEnabled(), $collected['security_enabled']); + $this->assertSame($firewallConfig->isStateless(), $collected['stateless']); + $this->assertSame($firewallConfig->getProvider(), $collected['provider']); + $this->assertSame($firewallConfig->getContext(), $collected['context']); + $this->assertSame($firewallConfig->getEntryPoint(), $collected['entry_point']); + $this->assertSame($firewallConfig->getAccessDeniedHandler(), $collected['access_denied_handler']); + $this->assertSame($firewallConfig->getAccessDeniedUrl(), $collected['access_denied_url']); + $this->assertSame($firewallConfig->getUserChecker(), $collected['user_checker']); + $this->assertSame($firewallConfig->getListeners(), $collected['listeners']->getRawData()[0][0]); + } + + public function testGetFirewallReturnsNull() + { + $request = $this->getRequest(); + $response = $this->getResponse(); + + // Don't inject any firewall map + $collector = new SecurityDataCollector(); + $collector->collect($request, $response); + $this->assertNull($collector->getFirewall()); + + // Inject an instance that is not context aware + $firewallMap = $this + ->getMockBuilder(FirewallMapInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap); + $collector->collect($request, $response); + $this->assertNull($collector->getFirewall()); + + // Null config + $firewallMap = $this + ->getMockBuilder(FirewallMap::class) + ->disableOriginalConstructor() + ->getMock(); + + $collector = new SecurityDataCollector(null, null, null, null, $firewallMap); + $collector->collect($request, $response); + $this->assertNull($collector->getFirewall()); + } + public function provideRoles() { return array( diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index a0dc39a11629e..34e3ad56114ac 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -63,15 +63,75 @@ public function testUserProviders() public function testFirewalls() { $container = $this->getContainer('container1'); - $arguments = $container->getDefinition('security.firewall.map')->getArguments(); $listeners = array(); + $configs = array(); foreach (array_keys($arguments[1]) as $contextId) { $contextDef = $container->getDefinition($contextId); $arguments = $contextDef->getArguments(); $listeners[] = array_map(function ($ref) { return (string) $ref; }, $arguments['index_0']); + + $configDef = $container->getDefinition($arguments['index_2']); + $configs[] = array_values($configDef->getArguments()); } + $this->assertEquals(array( + array( + 'simple', + 'security.request_matcher.707b20193d4cb9f2718114abcbebb32af48f948484fc166a03482f49bf14f25e271f72c7', + 'security.user_checker', + false, + ), + array( + 'secure', + '', + 'security.user_checker', + true, + true, + 'security.user.provider.concrete.default', + 'security.authentication.form_entry_point.secure', + array( + 'logout', + 'switch_user', + 'x509', + 'remote_user', + 'form_login', + 'http_basic', + 'http_digest', + 'remember_me', + 'anonymous', + ), + ), + array( + 'host', + 'security.request_matcher.dda8b565689ad8509623ee68fb2c639cd81cd4cb339d60edbaf7d67d30e6aa09bd8c63c3', + 'security.user_checker', + true, + false, + 'security.user.provider.concrete.default', + 'host', + 'security.authentication.basic_entry_point.host', + array( + 'http_basic', + 'anonymous', + ), + ), + array( + 'with_user_checker', + '', + 'app.user_checker', + true, + false, + 'security.user.provider.concrete.default', + 'with_user_checker', + 'security.authentication.basic_entry_point.with_user_checker', + array( + 'http_basic', + 'anonymous', + ), + ), + ), $configs); + $this->assertEquals(array( array(), array( diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php index 86a69fdb7624c..b1c75bfedd755 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/UserPasswordEncoderCommandTest.php @@ -140,6 +140,7 @@ public function testEncodePasswordNoConfigForGivenUserClass() protected function setUp() { + putenv('COLUMNS='.(119 + strlen(PHP_EOL))); $kernel = $this->createKernel(array('test_case' => 'PasswordEncode')); $kernel->boot(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php new file mode 100644 index 0000000000000..eb6eeeb8dd588 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallConfigTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Security; + +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; + +class FirewallConfigTest extends \PHPUnit_Framework_TestCase +{ + public function testGetters() + { + $listeners = array('logout', 'remember_me', 'anonymous'); + $options = array( + 'request_matcher' => 'foo_request_matcher', + 'security' => false, + 'stateless' => false, + 'provider' => 'foo_provider', + 'context' => 'foo_context', + 'entry_point' => 'foo_entry_point', + 'access_denied_url' => 'foo_access_denied_url', + 'access_denied_handler' => 'foo_access_denied_handler', + 'user_checker' => 'foo_user_checker', + ); + + $config = new FirewallConfig( + 'foo_firewall', + $options['request_matcher'], + $options['user_checker'], + $options['security'], + $options['stateless'], + $options['provider'], + $options['context'], + $options['entry_point'], + $options['access_denied_handler'], + $options['access_denied_url'], + $listeners + ); + + $this->assertSame('foo_firewall', $config->getName()); + $this->assertSame($options['request_matcher'], $config->getRequestMatcher()); + $this->assertSame($options['security'], $config->isSecurityEnabled()); + $this->assertSame($options['stateless'], $config->isStateless()); + $this->assertSame($options['provider'], $config->getProvider()); + $this->assertSame($options['context'], $config->getContext()); + $this->assertSame($options['entry_point'], $config->getEntryPoint()); + $this->assertSame($options['access_denied_handler'], $config->getAccessDeniedHandler()); + $this->assertSame($options['access_denied_url'], $config->getAccessDeniedUrl()); + $this->assertSame($options['user_checker'], $config->getUserChecker()); + $this->assertTrue($config->allowsAnonymous()); + $this->assertSame($listeners, $config->getListeners()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php new file mode 100644 index 0000000000000..098e2c51f80dd --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Security; + +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; +use Symfony\Bundle\SecurityBundle\Security\FirewallContext; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\ListenerInterface; + +class FirewallContextTest extends \PHPUnit_Framework_TestCase +{ + public function testGetters() + { + $config = new FirewallConfig('main', 'request_matcher', 'user_checker'); + + $exceptionListener = $this + ->getMockBuilder(ExceptionListener::class) + ->disableOriginalConstructor() + ->getMock(); + + $listeners = array( + $this + ->getMockBuilder(ListenerInterface::class) + ->disableOriginalConstructor() + ->getMock(), + ); + + $context = new FirewallContext($listeners, $exceptionListener, $config); + + $this->assertEquals(array($listeners, $exceptionListener), $context->getContext()); + $this->assertEquals($config, $context->getConfig()); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index b296516e25ffd..132093452fe33 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -40,7 +40,7 @@ "symfony/yaml": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0", "doctrine/doctrine-bundle": "~1.4", - "twig/twig": "~1.27|~2.0" + "twig/twig": "~1.28|~2.0" }, "suggest": { "symfony/security-acl": "For using the ACL functionality of this bundle" diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index 1d348e8c31e07..f9e86c4daa21d 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -129,12 +129,12 @@ protected function templateExists($template) $template = (string) $template; $loader = $this->twig->getLoader(); - if ($loader instanceof \Twig_ExistsLoaderInterface) { + if ($loader instanceof \Twig_ExistsLoaderInterface || method_exists($loader, 'exists')) { return $loader->exists($template); } try { - $loader->getSource($template); + $loader->getSourceContext($template)->getCode(); return true; } catch (\Twig_Error_Loader $e) { diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 316cc665a4b94..603cbe8e22ef1 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -66,6 +66,7 @@ public function process(ContainerBuilder $container) if (!$container->has('templating')) { $loader = $container->getDefinition('twig.loader.native_filesystem'); + $loader->replaceArgument(1, $this->getComposerRootDir($container->getParameter('kernel.root_dir'))); $loader->addTag('twig.loader'); $loader->setMethodCalls($container->getDefinition('twig.loader.filesystem')->getMethodCalls()); @@ -91,4 +92,18 @@ public function process(ContainerBuilder $container) $container->getDefinition('twig.extension.expression')->addTag('twig.extension'); } } + + private function getComposerRootDir($rootDir) + { + $dir = $rootDir; + while (!file_exists($dir.'/composer.json')) { + if ($dir === dirname($dir)) { + return $rootDir; + } + + $dir = dirname($dir); + } + + return $dir; + } } diff --git a/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php b/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php index 4e0bef365ebbb..53fe300e29a62 100644 --- a/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php +++ b/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php @@ -58,6 +58,7 @@ public function exists($name) * Otherwise the template is located using the locator from the twig library. * * @param string|TemplateReferenceInterface $template The template + * @param bool $throw When true, a \Twig_Error_Loader exception will be thrown if a template could not be found * * @return string The path to the template file * @@ -87,7 +88,11 @@ protected function findTemplate($template, $throw = true) } if (false === $file || null === $file) { - throw $twigLoaderException; + if ($throw) { + throw $twigLoaderException; + } + + return false; } return $this->cache[$logicalName] = $file; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index fd7575ac3fe42..c82cbafbeb953 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -46,7 +46,7 @@ - %kernel.root_dir% + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/logs.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/logs.html.twig index f75ec585fcf0d..d2c3ac6940194 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/logs.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/logs.html.twig @@ -1,7 +1,7 @@
    {% for log in logs %} = 400 %} class="error"{% elseif log.priority >= 300 %} class="warning"{% endif %}> - {{ log.priorityName }} - {{ log.message }} + {{ log.priorityName }} - {{ log.message|format_log_message(log.context) }} {% endfor %}
diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php index d64fc4a6b0f74..c8cdc305c30af 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php @@ -33,7 +33,6 @@ public function testForwardRequestToConfiguredController() ->method('handle') ->with( $this->callback(function (Request $request) use ($logicalControllerName, $code) { - $this->assertEquals($logicalControllerName, $request->attributes->get('_controller')); $exception = $request->attributes->get('exception'); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php index 9804c08a1923b..64ba490388f9f 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Loader/FilesystemLoaderTest.php @@ -115,4 +115,17 @@ public function testTwigErrorIfTemplateDoesNotExist() $method->setAccessible(true); $method->invoke($loader, 'name.format.engine'); } + + public function testTwigSoftErrorIfTemplateDoesNotExist() + { + $parser = $this->getMock('Symfony\Component\Templating\TemplateNameParserInterface'); + $locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface'); + + $loader = new FilesystemLoader($locator, $parser); + $loader->addPath(__DIR__.'/../DependencyInjection/Fixtures/Resources/views'); + + $method = new \ReflectionMethod('Symfony\Bundle\TwigBundle\Loader\FilesystemLoader', 'findTemplate'); + $method->setAccessible(true); + $this->assertFalse($method->invoke($loader, 'name.format.engine', false)); + } } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 2083419282c0e..fbb5f9bf8afd5 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -20,7 +20,7 @@ "symfony/twig-bridge": "~3.2", "symfony/http-foundation": "~2.8|~3.0", "symfony/http-kernel": "~2.8|~3.0", - "twig/twig": "~1.27|~2.0" + "twig/twig": "~1.28|~2.0" }, "require-dev": { "symfony/asset": "~2.8|~3.0", diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php index 6b6b8c0a0723e..80750570e4c43 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php @@ -120,7 +120,7 @@ public function panelAction(Request $request, $token) 'panel' => $panel, 'page' => $page, 'request' => $request, - 'templates' => $this->getTemplateManager()->getTemplates($profile), + 'templates' => $this->getTemplateManager()->getNames($profile), 'is_ajax' => $request->isXmlHttpRequest(), 'profiler_markup_version' => 2, // 1 = original profiler, 2 = Symfony 2.8+ profiler )), 200, array('Content-Type' => 'text/html')); @@ -203,7 +203,7 @@ public function toolbarAction(Request $request, $token) 'request' => $request, 'position' => $position, 'profile' => $profile, - 'templates' => $this->getTemplateManager()->getTemplates($profile), + 'templates' => $this->getTemplateManager()->getNames($profile), 'profiler_url' => $url, 'token' => $token, 'profiler_markup_version' => 2, // 1 = original toolbar, 2 = Symfony 2.8+ toolbar diff --git a/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php b/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php index b0181afe87c12..696687fe019f9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php @@ -67,7 +67,9 @@ public function getName(Profile $profile, $panel) * * @param Profile $profile * - * @return array + * @return Twig_Template[] + * + * @deprecated not used anymore internally */ public function getTemplates(Profile $profile) { @@ -89,7 +91,7 @@ public function getTemplates(Profile $profile) * * @throws \UnexpectedValueException */ - protected function getNames(Profile $profile) + public function getNames(Profile $profile) { $templates = array(); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index ecb97231d95f2..931363ac6d328 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -42,6 +42,7 @@ Symfony\Component\VarDumper\Dumper\HtmlDumper::DUMP_LIGHT_ARRAY + 4096 diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig index cafdeb5ac1ba4..ea73c8bde717e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig @@ -60,10 +60,19 @@ Symfony initialization
+ {% if profile.collectors.memory %} +
+ {{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} MB + Peak memory usage +
+ {% endif %} + {% if profile.children|length > 0 %} +
+
{{ profile.children|length }} - Sub-Requests + Sub-Request{{ profile.children|length > 1 ? 's' }}
{% set subrequests_time = 0 %} @@ -73,14 +82,7 @@
{{ subrequests_time }} ms - Sub-Requests time -
- {% endif %} - - {% if profile.collectors.memory %} -
- {{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} MB - Peak memory usage + Sub-Request{{ profile.children|length > 1 ? 's' }} time
{% endif %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig index eede91a73ea8f..1c6cbc28cd213 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig @@ -112,7 +112,11 @@ {% if templates is defined %}