diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index cf59cb7070..03074c23db 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -7,7 +7,7 @@ in 2.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/v2.1.0...v2.1.1 -* 2.1.4 (2012-12-29) +* 2.1.4 (2012-11-29) * e5536f0: replaced magic strings by proper constants * 6a3ba52: fixed the logic in Request::isSecure() (if the information comes from a source that we trust, don't check other ones) diff --git a/README.md b/README.md index 7766b38da6..f50ccefeb3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ README ====== -[![Build Status](https://secure.travis-ci.org/symfony/symfony.png?branch=master)](http://travis-ci.org/symfony/symfony) - What is Symfony2? ----------------- diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index 83e328fb8c..825e7f2570 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -39,6 +39,65 @@ * The PasswordType is now not trimmed by default. +### Routing + + * RouteCollection does not behave like a tree structure anymore but as a flat + array of Routes. So when using PHP to build the RouteCollection, you must + make sure to add routes to the sub-collection before adding it to the parent + collection (this is not relevant when using YAML or XML for Route definitions). + + Before: + + ``` + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $rootCollection->addCollection($subCollection); + $subCollection->add('foo', new Route('/foo')); + ``` + + After: + + ``` + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $subCollection->add('foo', new Route('/foo')); + $rootCollection->addCollection($subCollection); + ``` + + Also one must call `addCollection` from the bottom to the top hierarchy. + So the correct sequence is the following (and not the reverse): + + ``` + $childCollection->->addCollection($grandchildCollection); + $rootCollection->addCollection($childCollection); + ``` + + * The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` + have been deprecated and will be removed in Symfony 2.3. + * Misusing the `RouteCollection::addPrefix` method to add defaults, requirements + or options without adding a prefix is not supported anymore. So if you called `addPrefix` + with an empty prefix or `/` only (both have no relevance), like + `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` + you need to use the new dedicated methods `addDefaults($defaultsArray)`, + `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. + * The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated + because adding options has nothing to do with adding a path prefix. If you want to add options + to all child routes of a RouteCollection, you can use `addOptions()`. + * The method `RouteCollection::getPrefix()` has been deprecated + because it suggested that all routes in the collection would have this prefix, which is + not necessarily true. On top of that, since there is no tree structure anymore, this method + is also useless. + * `RouteCollection::addCollection(RouteCollection $collection)` should now only be + used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` + will still work, but have been deprecated. The `addPrefix` method should be used for this + use-case instead. + Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))` + After: + ``` + $collection->addPrefix('/prefix', array(...), array(...)); + $parentCollection->addCollection($collection); + ``` + ### Validator * Interfaces were created for the classes `ConstraintViolation`, diff --git a/composer.json b/composer.json index a4f1a2ee5c..746590965f 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.3.3", "doctrine/common": ">2.2,<2.4-dev", - "twig/twig": ">=1.9.1,<2.0-dev" + "twig/twig": ">=1.11.0,<2.0-dev" }, "replace": { "symfony/browser-kit": "self.version", diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 6369ea3634..8e3f148448 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.1.5 +----- + + * fixed caching of choice lists when EntityType is used with the "query_builder" option + 2.1.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php index 78594ed8ba..2e385a343f 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php @@ -392,7 +392,10 @@ class EntityChoiceList extends ObjectChoiceList private function getIdentifierValues($entity) { if (!$this->em->contains($entity)) { - throw new FormException('Entities passed to the choice field must be managed'); + throw new FormException( + 'Entities passed to the choice field must be managed. Maybe ' . + 'persist them in the entity manager?' + ); } $this->em->initializeObject($entity); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 14d88e3403..0cdfe03cd0 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Persistence\ManagerRegistry; +use Symfony\Component\Form\Exception\FormException; use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; @@ -76,16 +77,16 @@ abstract class DoctrineType extends AbstractType // A second parameter ($key) is passed, so we cannot use // spl_object_hash() directly (which strictly requires // one parameter) - array_walk_recursive($choiceHashes, function ($value) { - return spl_object_hash($value); + array_walk_recursive($choiceHashes, function (&$value) { + $value = spl_object_hash($value); }); } $preferredChoiceHashes = $options['preferred_choices']; if (is_array($preferredChoiceHashes)) { - array_walk_recursive($preferredChoiceHashes, function ($value) { - return spl_object_hash($value); + array_walk_recursive($preferredChoiceHashes, function (&$value) { + $value = spl_object_hash($value); }); } @@ -130,7 +131,17 @@ abstract class DoctrineType extends AbstractType return $registry->getManager($em); } - return $registry->getManagerForClass($options['class']); + $em = $registry->getManagerForClass($options['class']); + + if (null === $em) { + throw new FormException(sprintf( + 'Class "%s" seems not to be a managed Doctrine entity. ' . + 'Did you forget to map it?', + $options['class'] + )); + } + + return $em; }; $resolver->setDefaults(array( diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 51b47be4dc..c9ffb95e9b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -11,26 +11,53 @@ namespace Symfony\Bridge\Doctrine\Form\Type; -use Doctrine\Common\Persistence\ObjectManager; +use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\QueryBuilder; class EntityType extends DoctrineType { + /** + * @var array + */ + private $loaderCache = array(); + /** * Return the default loader object. * - * @param ObjectManager $manager - * @param mixed $queryBuilder - * @param string $class + * @param ObjectManager $manager + * @param QueryBuilder|\Closure $queryBuilder + * @param string $class + * * @return ORMQueryBuilderLoader + * + * @throws UnexpectedTypeException If the passed $queryBuilder is no \Closure + * and no QueryBuilder or if the closure + * does not return a QueryBuilder. */ public function getLoader(ObjectManager $manager, $queryBuilder, $class) { - return new ORMQueryBuilderLoader( - $queryBuilder, - $manager, - $class - ); + if ($queryBuilder instanceof \Closure) { + $queryBuilder = $queryBuilder($manager->getRepository($class)); + + if (!$queryBuilder instanceof QueryBuilder) { + throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); + } + } elseif (!$queryBuilder instanceof QueryBuilder) { + throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure'); + } + + // It is important to return the same loader for identical queries, + // otherwise the caching mechanism in DoctrineType does not work + // (which expects identical loaders for the cache to work) + $hash = md5($queryBuilder->getQuery()->getDQL()); + + if (!isset($this->loaderCache[$hash])) { + $this->loaderCache[$hash] = new ORMQueryBuilderLoader($queryBuilder); + } + + return $this->loaderCache[$hash]; } public function getName() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php index cb69d8b6ae..02312de64b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php @@ -86,4 +86,4 @@ class DoctrineOrmTypeGuesserTest extends \PHPUnit_Framework_TestCase return new DoctrineOrmTypeGuesser($registry); } -} \ No newline at end of file +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index ea068c24f3..8c3ee842d5 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\Type; use Symfony\Component\Form\Tests\FormPerformanceTestCase; +use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIdentEntity; use Doctrine\ORM\Tools\SchemaTool; use Symfony\Bridge\Doctrine\Tests\DoctrineOrmTestCase; @@ -36,6 +37,9 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase $manager->expects($this->any()) ->method('getManager') ->will($this->returnValue($this->em)); + $manager->expects($this->any()) + ->method('getManagerForClass') + ->will($this->returnValue($this->em)); return array( new CoreExtension(), @@ -100,7 +104,7 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase { $this->setMaxRunningTime(1); - for ($i = 0; $i < 20; ++$i) { + for ($i = 0; $i < 40; ++$i) { $form = $this->factory->create('entity', null, array( 'class' => self::ENTITY_CLASS, )); @@ -109,4 +113,62 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase $form->createView(); } } + + /** + * @group benchmark + */ + public function testCollapsedEntityFieldWithQueryBuilder() + { + $this->setMaxRunningTime(1); + + for ($i = 0; $i < 40; ++$i) { + $form = $this->factory->create('entity', null, array( + 'class' => self::ENTITY_CLASS, + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('e')->addOrderBy('e.id', 'DESC'); + } + )); + + // force loading of the choice list + $form->createView(); + } + } + + /** + * @group benchmark + */ + public function testCollapsedEntityFieldWithChoices() + { + $choices = $this->em->createQuery('SELECT c FROM ' . self::ENTITY_CLASS . ' c')->getResult(); + $this->setMaxRunningTime(1); + + for ($i = 0; $i < 40; ++$i) { + $form = $this->factory->create('entity', null, array( + 'class' => self::ENTITY_CLASS, + 'choices' => $choices, + )); + + // force loading of the choice list + $form->createView(); + } + } + + /** + * @group benchmark + */ + public function testCollapsedEntityFieldWithPreferredChoices() + { + $choices = $this->em->createQuery('SELECT c FROM ' . self::ENTITY_CLASS . ' c')->getResult(); + $this->setMaxRunningTime(1); + + for ($i = 0; $i < 40; ++$i) { + $form = $this->factory->create('entity', null, array( + 'class' => self::ENTITY_CLASS, + 'preferred_choices' => $choices, + )); + + // force loading of the choice list + $form->createView(); + } + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php index ffe0a37b99..c4ca0eecdf 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Doctrine\Tests\Logger; - class DbalLoggerTest extends \PHPUnit_Framework_TestCase { /** diff --git a/src/Symfony/Bridge/Propel1/DataCollector/PropelDataCollector.php b/src/Symfony/Bridge/Propel1/DataCollector/PropelDataCollector.php index 644705f05c..bbb15fb9a0 100644 --- a/src/Symfony/Bridge/Propel1/DataCollector/PropelDataCollector.php +++ b/src/Symfony/Bridge/Propel1/DataCollector/PropelDataCollector.php @@ -63,7 +63,7 @@ class PropelDataCollector extends DataCollector /** * Returns the collector name. * - * @return string The collector name. + * @return string The collector name. */ public function getName() { @@ -73,7 +73,7 @@ class PropelDataCollector extends DataCollector /** * Returns queries. * - * @return array Queries + * @return array Queries */ public function getQueries() { @@ -83,7 +83,7 @@ class PropelDataCollector extends DataCollector /** * Returns the query count. * - * @return int The query count + * @return int The query count */ public function getQueryCount() { @@ -93,7 +93,7 @@ class PropelDataCollector extends DataCollector /** * Returns the total time of queries. * - * @return float The total time of queries + * @return float The total time of queries */ public function getTime() { @@ -108,7 +108,7 @@ class PropelDataCollector extends DataCollector /** * Creates an array of Build objects. * - * @return array An array of Build objects + * @return array An array of Build objects */ private function buildQueries() { @@ -138,7 +138,7 @@ class PropelDataCollector extends DataCollector /** * Count queries. * - * @return int The number of queries. + * @return int The number of queries. */ private function countQueries() { diff --git a/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php b/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php index 54fe224d31..f90217be2d 100644 --- a/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php +++ b/src/Symfony/Bridge/Propel1/Form/ChoiceList/ModelChoiceList.php @@ -45,6 +45,13 @@ class ModelChoiceList extends ObjectChoiceList */ private $loaded = false; + /** + * Whether to use the identifier for index generation + * + * @var Boolean + */ + private $identifierAsIndex = false; + /** * @param string $class * @param string $labelPath @@ -69,6 +76,10 @@ class ModelChoiceList extends ObjectChoiceList $choices = array(); } + if (1 === count($this->identifier) && $this->isInteger(current($this->identifier))) { + $this->identifierAsIndex = true; + } + parent::__construct($choices, $labelPath, array(), $groupPath); } @@ -224,7 +235,7 @@ class ModelChoiceList extends ObjectChoiceList // know that the IDs are used as indices // Attention: This optimization does not check choices for existence - if (1 === count($this->identifier)) { + if ($this->identifierAsIndex) { $indices = array(); foreach ($models as $model) { @@ -259,7 +270,7 @@ class ModelChoiceList extends ObjectChoiceList // know that the IDs are used as indices and values // Attention: This optimization does not check values for existence - if (1 === count($this->identifier)) { + if ($this->identifierAsIndex) { return $this->fixIndices($values); } @@ -283,7 +294,7 @@ class ModelChoiceList extends ObjectChoiceList */ protected function createIndex($model) { - if (1 === count($this->identifier)) { + if ($this->identifierAsIndex) { return current($this->getIdentifierValues($model)); } @@ -336,7 +347,8 @@ class ModelChoiceList extends ObjectChoiceList * exception is thrown. * * @param object $model The model for which to get the identifier - * @throws FormException If the model does not exist + * + * @throws FormException If the model does not exist */ private function getIdentifierValues($model) { @@ -345,10 +357,22 @@ class ModelChoiceList extends ObjectChoiceList } // readonly="true" models do not implement Persistent. - if ($model instanceof BaseObject and method_exists($model, 'getPrimaryKey')) { + if ($model instanceof BaseObject && method_exists($model, 'getPrimaryKey')) { return array($model->getPrimaryKey()); } return $model->getPrimaryKeys(); } + + /** + * Whether this column in an integer + * + * @param ColumnMap $column + * + * @return boolean + */ + private function isInteger(\ColumnMap $column) + { + return $column->getPdoType() === \PDO::PARAM_INT; + } } diff --git a/src/Symfony/Bridge/Propel1/Form/EventListener/TranslationCollectionFormListener.php b/src/Symfony/Bridge/Propel1/Form/EventListener/TranslationCollectionFormListener.php index 9011fb0007..ae39700736 100644 --- a/src/Symfony/Bridge/Propel1/Form/EventListener/TranslationCollectionFormListener.php +++ b/src/Symfony/Bridge/Propel1/Form/EventListener/TranslationCollectionFormListener.php @@ -89,8 +89,8 @@ class TranslationCollectionFormListener implements EventSubscriberInterface break; } } - if(!$foundData) { - throw new UnexpectedTypeException($rootData, 'Propel i18n object');; + if (!$foundData) { + throw new UnexpectedTypeException($rootData, 'Propel i18n object'); } $newTranslation = new $this->i18nClass(); diff --git a/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php b/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php index 8e38f2418e..1fd7dedf90 100644 --- a/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php +++ b/src/Symfony/Bridge/Propel1/Logger/PropelLogger.php @@ -161,7 +161,7 @@ class PropelLogger /** * Returns queries. * - * @return array Queries + * @return array Queries */ public function getQueries() { diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php index 75d2a4f731..fe2d03e05f 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/ItemQuery.php @@ -31,7 +31,10 @@ class ItemQuery public function getPrimaryKeys() { - return array('id'); + $cm = new \ColumnMap('id', new \TableMap()); + $cm->setType('INTEGER'); + + return array('id' => $cm); } /** diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/ReadOnlyItemQuery.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/ReadOnlyItemQuery.php index 8c9677f33d..0e77c26fcf 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/ReadOnlyItemQuery.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/ReadOnlyItemQuery.php @@ -22,6 +22,9 @@ class ReadOnlyItemQuery public function getPrimaryKeys() { - return array('id'); + $cm = new \ColumnMap('id', new \TableMap()); + $cm->setType('INTEGER'); + + return array('id' => $cm); } } diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItem.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItem.php index 95ac0e1526..c69fe45299 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItem.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItem.php @@ -112,8 +112,7 @@ class TranslatableItem implements \Persistent public function addTranslatableItemI18n(TranslatableItemI18n $i) { - if(!in_array($i, $this->currentTranslations)) - { + if (!in_array($i, $this->currentTranslations)) { $this->currentTranslations[$i->getLocale()] = $i; $i->setItem($this); } diff --git a/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItemI18n.php b/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItemI18n.php index c9eb690a43..1253b26c26 100644 --- a/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItemI18n.php +++ b/src/Symfony/Bridge/Propel1/Tests/Fixtures/TranslatableItemI18n.php @@ -13,8 +13,8 @@ namespace Symfony\Bridge\Propel1\Tests\Fixtures; use PropelPDO; -class TranslatableItemI18n implements \Persistent { - +class TranslatableItemI18n implements \Persistent +{ private $id; private $locale; @@ -100,7 +100,6 @@ class TranslatableItemI18n implements \Persistent { public function getLocale() { - return $this->locale; } @@ -122,7 +121,6 @@ class TranslatableItemI18n implements \Persistent { public function getValue() { - return $this->value; } @@ -134,7 +132,6 @@ class TranslatableItemI18n implements \Persistent { public function getValue2() { - return $this->value2; } } diff --git a/src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php similarity index 99% rename from src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php rename to src/Symfony/Bridge/Twig/Extension/CodeExtension.php index 65d51f4773..c65c026975 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\TwigBundle\Extension; +namespace Symfony\Bridge\Twig\Extension; if (!defined('ENT_SUBSTITUTE')) { define('ENT_SUBSTITUTE', 8); diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 0e562e4142..177e6f1e47 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -221,16 +221,18 @@ {% block form_label %} {% spaceless %} - {% if not compound %} - {% set label_attr = label_attr|merge({'for': id}) %} + {% if label is not sameas(false) %} + {% if not compound %} + {% set label_attr = label_attr|merge({'for': id}) %} + {% endif %} + {% if required %} + {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if label is empty %} + {% set label = name|humanize %} + {% endif %} + {{ label|trans({}, translation_domain) }} {% endif %} - {% if required %} - {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} - {% endif %} - {% if label is empty %} - {% set label = name|humanize %} - {% endif %} - {{ label|trans({}, translation_domain) }} {% endspaceless %} {% endblock form_label %} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php similarity index 94% rename from src/Symfony/Bundle/TwigBundle/Tests/Extension/CodeExtensionTest.php rename to src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php index 15f7b5e5bd..d935651439 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Extension/CodeExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\TwigBundle\Tests\Extension; +namespace Symfony\Bridge\Twig\Tests\Extension; -use Symfony\Bundle\TwigBundle\Extension\CodeExtension; +use Symfony\Bridge\Twig\Extension\CodeExtension; class CodeExtensionTest extends \PHPUnit_Framework_TestCase { diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 5b4535fde5..c89be59cff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * A new parameter has been added to the DIC: `router.request_context.base_url` You can customize it for your functional tests or for generating urls with the right base url when your are in the cli context. + * Added support for default templates per render tag 2.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 08922a9ac5..2681fc1a96 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -89,6 +89,7 @@ EOF } $this->outputTags($output, $input->getOption('show-private')); + return; } @@ -146,8 +147,8 @@ EOF if (null !== $showTagAttributes) { $tags = $definition->getTag($showTagAttributes); - foreach($tags as $tag) { - foreach($tag as $key => $value) { + foreach ($tags as $tag) { + foreach ($tag as $key => $value) { if (!isset($maxTags[$key])) { $maxTags[$key] = strlen($key); } @@ -173,7 +174,7 @@ EOF $format1 .= '%-'.($maxScope + 19).'s %s'; $tags = array(); - foreach($maxTags as $tagName => $length) { + foreach ($maxTags as $tagName => $length) { $tags[] = ''.$tagName.''; } $output->writeln(vsprintf($format1, $this->buildArgumentsArray('Service Id', 'Scope', 'Class Name', $tags))); @@ -184,9 +185,9 @@ EOF if ($definition instanceof Definition) { $lines = array(); if (null !== $showTagAttributes) { - foreach($definition->getTag($showTagAttributes) as $key => $tag) { + foreach ($definition->getTag($showTagAttributes) as $key => $tag) { $tagValues = array(); - foreach(array_keys($maxTags) as $tagName) { + foreach (array_keys($maxTags) as $tagName) { $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : ""; } if (0 === $key) { @@ -199,7 +200,7 @@ EOF $lines[] = $this->buildArgumentsArray($serviceId, $definition->getScope(), $definition->getClass()); } - foreach($lines as $arguments) { + foreach ($lines as $arguments) { $output->writeln(vsprintf($format, $arguments)); } } elseif ($definition instanceof Alias) { @@ -216,7 +217,7 @@ EOF protected function buildArgumentsArray($serviceId, $scope, $className, array $tagAttributes = array()) { $arguments = array($serviceId); - foreach($tagAttributes as $tagAttribute) { + foreach ($tagAttributes as $tagAttribute) { $arguments[] = $tagAttribute; } $arguments[] = $scope; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 161438674a..fa3e681b89 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -92,9 +92,7 @@ EOF ? implode(', ', $requirements['_method']) : $requirements['_method'] ) : 'ANY'; - $hostname = '' !== $route->getHostnamePattern() - ? $route->getHostnamePattern() : 'ANY'; - + $hostname = '' !== $route->getHostnamePattern() ? $route->getHostnamePattern() : 'ANY'; $maxName = max($maxName, strlen($name)); $maxMethod = max($maxMethod, strlen($method)); $maxHostname = max($maxHostname, strlen($hostname)); @@ -111,8 +109,7 @@ EOF ? implode(', ', $requirements['_method']) : $requirements['_method'] ) : 'ANY'; - $hostname = '' !== $route->getHostnamePattern() - ? $route->getHostnamePattern() : 'ANY'; + $hostname = '' !== $route->getHostnamePattern() ? $route->getHostnamePattern() : 'ANY'; $output->writeln(sprintf($format, $name, $method, $hostname, $route->getPattern())); } } @@ -127,10 +124,13 @@ EOF throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); } + $hostname = '' !== $route->getHostnamePattern() ? $route->getHostnamePattern() : 'ANY'; + $output->writeln($this->getHelper('formatter')->formatSection('router', sprintf('Route "%s"', $name))); $output->writeln(sprintf('Name %s', $name)); $output->writeln(sprintf('Pattern %s', $route->getPattern())); + $output->writeln(sprintf('HostnamePattern %s', $hostname)); $output->writeln(sprintf('Class %s', get_class($route))); $defaults = ''; @@ -139,7 +139,7 @@ EOF foreach ($d as $name => $value) { $defaults .= ($defaults ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); } - $output->writeln(sprintf('Defaults %s', $defaults)); + $output->writeln(sprintf('Defaults %s', $defaults)); $requirements = ''; $r = $route->getRequirements(); @@ -147,7 +147,8 @@ EOF foreach ($r as $name => $value) { $requirements .= ($requirements ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); } - $output->writeln(sprintf('Requirements %s', $requirements)); + $requirements = '' !== $requirements ? $requirements : 'NONE'; + $output->writeln(sprintf('Requirements %s', $requirements)); $options = ''; $o = $route->getOptions(); @@ -155,8 +156,8 @@ EOF foreach ($o as $name => $value) { $options .= ($options ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); } - $output->writeln(sprintf('Options %s', $options)); - $output->write('Regex '); + $output->writeln(sprintf('Options %s', $options)); + $output->write('Regex '); $output->writeln(preg_replace('/^ /', '', preg_replace('/^/m', ' ', $route->compile()->getRegex())), OutputInterface::OUTPUT_RAW); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index 3e7fff9c8e..87900a1242 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -78,7 +78,7 @@ class Controller extends ContainerAware * @param string $view The view name * @param array $parameters An array of parameters to pass to the view * - * @return string The renderer view + * @return string The rendered view */ public function renderView($view, array $parameters = array()) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php index 1c3fe19fb9..667ff587de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php @@ -38,6 +38,11 @@ class ControllerNameParser * Converts a short notation a:b:c to a class::method. * * @param string $controller A short notation controller (a:b:c) + * + * @return string A string with class::method + * + * @throws \InvalidArgumentException when the specified bundle is not enabled + * or the controller cannot be found */ public function parse($controller) { @@ -47,40 +52,23 @@ class ControllerNameParser list($bundle, $controller, $action) = $parts; $controller = str_replace('/', '\\', $controller); - $class = null; - $logs = array(); + $bundles = array(); + + // this throws an exception if there is no such bundle foreach ($this->kernel->getBundle($bundle, false) as $b) { $try = $b->getNamespace().'\\Controller\\'.$controller.'Controller'; - if (!class_exists($try)) { - $logs[] = sprintf('Unable to find controller "%s:%s" - class "%s" does not exist.', $bundle, $controller, $try); - } else { - $class = $try; - - break; + if (class_exists($try)) { + return $try.'::'.$action.'Action'; } + + $bundles[] = $b->getName(); + $msg = sprintf('Unable to find controller "%s:%s" - class "%s" does not exist.', $bundle, $controller, $try); } - if (null === $class) { - $this->handleControllerNotFoundException($bundle, $controller, $logs); + if (count($bundles) > 1) { + $msg = sprintf('Unable to find controller "%s:%s" in bundles %s.', $bundle, $controller, implode(', ', $bundles)); } - return $class.'::'.$action.'Action'; - } - - private function handleControllerNotFoundException($bundle, $controller, array $logs) - { - // just one log, return it as the exception - if (1 == count($logs)) { - throw new \InvalidArgumentException($logs[0]); - } - - // many logs, use a message that mentions each searched bundle - $names = array(); - foreach ($this->kernel->getBundle($bundle, false) as $b) { - $names[] = $b->getName(); - } - $msg = sprintf('Unable to find controller "%s:%s" in bundles %s.', $bundle, $controller, implode(', ', $names)); - throw new \InvalidArgumentException($msg); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php index fd6f1aaf78..105d14a8fe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.php @@ -24,12 +24,32 @@ class TemplateController extends ContainerAware /** * Renders a template. * - * @param string $template The template name + * @param string $template The template name + * @param int|null $maxAge Max age for client caching + * @param int|null $sharedAge Max age for shared (proxy) caching + * @param Boolean|null $private Whether or not caching should apply for client caches only * * @return Response A Response instance */ - public function templateAction($template) + public function templateAction($template, $maxAge = null, $sharedAge = null, $private = null) { - return $this->container->get('templating')->renderResponse($template); + /** @var $response \Symfony\Component\HttpFoundation\Response */ + $response = $this->container->get('templating')->renderResponse($template); + + if ($maxAge) { + $response->setMaxAge($maxAge); + } + + if ($sharedAge) { + $response->setSharedMaxAge($sharedAge); + } + + if ($private) { + $response->setPrivate(); + } elseif ($private === false || (null === $private && ($maxAge || $sharedAge))) { + $response->setPublic($private); + } + + return $response; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 4ff15f16e7..84734237d9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -22,22 +22,12 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; */ class Configuration implements ConfigurationInterface { - private $debug; - - /** - * Constructor - * - * @param Boolean $debug Whether to use the debug mode - */ - public function __construct($debug) - { - $this->debug = (Boolean) $debug; - } - /** * Generates the configuration tree builder. * - * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + * @return TreeBuilder The tree builder + * + * @throws \RuntimeException When using the deprecated 'charset' setting */ public function getConfigTreeBuilder() { @@ -54,7 +44,7 @@ class Configuration implements ConfigurationInterface $message = 'The charset setting is deprecated. Just remove it from your configuration file.'; if ('UTF-8' !== $v) { - $message .= sprintf(' You need to define a getCharset() method in your Application Kernel class that returns "%s".', $v); + $message .= sprintf('You need to define a getCharset() method in your Application Kernel class that returns "%s".', $v); } throw new \RuntimeException($message); @@ -179,7 +169,7 @@ class Configuration implements ConfigurationInterface ->children() ->booleanNode('auto_start') ->info('DEPRECATED! Session starts on demand') - ->defaultNull() + ->defaultFalse() ->beforeNormalization() ->ifTrue(function($v) { return null !== $v; }) ->then(function($v) { @@ -384,7 +374,7 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('cache')->defaultValue('file')->end() ->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end() - ->booleanNode('debug')->defaultValue($this->debug)->end() + ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ad1de4438c..c38844e15e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -141,11 +141,6 @@ class FrameworkExtension extends Extension )); } - public function getConfiguration(array $config, ContainerBuilder $container) - { - return new Configuration($container->getParameter('kernel.debug')); - } - /** * Loads Form configuration. * diff --git a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php index ddc5994f85..2c6138009d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/HttpKernel.php @@ -105,6 +105,7 @@ class HttpKernel extends BaseHttpKernel 'alt' => array(), 'standalone' => false, 'comment' => '', + 'default' => null, ), $options); if (!is_array($options['alt'])) { @@ -130,8 +131,16 @@ class HttpKernel extends BaseHttpKernel $uri = $this->generateInternalUri($controller, $options['attributes'], $options['query'], false); $defaultContent = null; - if ($template = $this->container->getParameter('templating.hinclude.default_template')) { - $defaultContent = $this->container->get('templating')->render($template); + $templating = $this->container->get('templating'); + + if ($options['default']) { + if ($templating->exists($options['default'])) { + $defaultContent = $templating->render($options['default']); + } else { + $defaultContent = $options['default']; + } + } elseif ($template = $this->container->getParameter('templating.hinclude.default_template')) { + $defaultContent = $templating->render($template); } return $this->renderHIncludeTag($uri, $defaultContent); @@ -226,7 +235,7 @@ class HttpKernel extends BaseHttpKernel /** * Renders an HInclude tag. * - * @param string $uri A URI + * @param string $uri A URI * @param string $defaultContent Default content */ public function renderHIncludeTag($uri, $defaultContent = null) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index a1c332a6ab..7d10cc70b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -9,6 +9,7 @@ Symfony\Component\Stopwatch\Stopwatch %kernel.cache_dir%/%kernel.container_class%.xml Symfony\Component\HttpKernel\Controller\TraceableControllerResolver + Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener @@ -26,5 +27,11 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 6a3778224b..48df23543c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -13,7 +13,7 @@ Symfony\Component\Translation\Loader\XliffFileLoader Symfony\Component\Translation\Loader\PoFileLoader Symfony\Component\Translation\Loader\MoFileLoader - Symfony\Component\Translation\Loader\QtTranslationsLoader + Symfony\Component\Translation\Loader\QtFileLoader Symfony\Component\Translation\Loader\CsvFileLoader Symfony\Component\Translation\Loader\IcuResFileLoader Symfony\Component\Translation\Loader\IcuDatFileLoader diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php index 48dd147a60..4ed04cc392 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_label.html.php @@ -1,4 +1,6 @@ + humanize($name); } ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 753a58e27a..8620ef507a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -85,20 +85,16 @@ class Router extends BaseRouter implements WarmableInterface private function resolveParameters(RouteCollection $collection) { foreach ($collection as $route) { - if ($route instanceof RouteCollection) { - $this->resolveParameters($route); - } else { - foreach ($route->getDefaults() as $name => $value) { - $route->setDefault($name, $this->resolve($value)); - } - - foreach ($route->getRequirements() as $name => $value) { - $route->setRequirement($name, $this->resolve($value)); - } - - $route->setPattern($this->resolve($route->getPattern())); - $route->setHostnamePattern($this->resolve($route->getHostnamePattern())); + foreach ($route->getDefaults() as $name => $value) { + $route->setDefault($name, $this->resolve($value)); } + + foreach ($route->getRequirements() as $name => $value) { + $route->setRequirement($name, $this->resolve($value)); + } + + $route->setPattern($this->resolve($route->getPattern())); + $route->setHostnamePattern($this->resolve($route->getHostnamePattern())); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index 20f62c6f18..6d3e191131 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -43,6 +43,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface 'failure_path' => null, 'failure_forward' => false, 'login_path' => '/login', + 'failure_path_parameter' => '_failure_path', ); public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index 1f1fa85d0a..e0347e1dc4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -33,6 +33,7 @@ security: check_path: /login_check default_target_path: /profile target_path_parameter: "user_login[_target_path]" + failure_path_parameter: "user_login[_failure_path]" username_parameter: "user_login[username]" password_parameter: "user_login[password]" csrf_parameter: "user_login[_token]" diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index 3ddc5d809a..8c03bdbbf1 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 2.2.0 ----- + * moved the exception controller to be a service (`twig.controller.exception:showAction` vs `Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction`) + * added support for multiple loaders via the "twig.loader" tag. * added automatic registration of namespaced paths for registered bundles * added support for namespaced paths diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index 54be66a8fc..daefeb4fd7 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -13,9 +13,9 @@ namespace Symfony\Bundle\TwigBundle\Controller; use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference; -use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpKernel\Exception\FlattenException; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** @@ -23,8 +23,17 @@ use Symfony\Component\HttpFoundation\Response; * * @author Fabien Potencier */ -class ExceptionController extends ContainerAware +class ExceptionController { + protected $twig; + protected $debug; + + public function __construct(\Twig_Environment $twig, $debug) + { + $this->twig = $twig; + $this->debug = $debug; + } + /** * Converts an Exception to a Response. * @@ -36,17 +45,16 @@ class ExceptionController extends ContainerAware * * @throws \InvalidArgumentException When the exception template does not exist */ - public function showAction(FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html') + public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html') { - $this->container->get('request')->setRequestFormat($format); + $request->setRequestFormat($format); - $currentContent = $this->getAndCleanOutputBuffering(); + $currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1)); - $templating = $this->container->get('templating'); $code = $exception->getStatusCode(); - return $templating->renderResponse( - $this->findTemplate($templating, $format, $code, $this->container->get('kernel')->isDebug()), + return new Response($this->twig->render( + $this->findTemplate($request, $format, $code, $this->debug), array( 'status_code' => $code, 'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '', @@ -54,19 +62,17 @@ class ExceptionController extends ContainerAware 'logger' => $logger, 'currentContent' => $currentContent, ) - ); + )); } /** * @return string */ - protected function getAndCleanOutputBuffering() + protected function getAndCleanOutputBuffering($startObLevel) { // ob_get_level() never returns 0 on some Windows configurations, so if // the level is the same two times in a row, the loop should be stopped. $previousObLevel = null; - $startObLevel = $this->container->get('request')->headers->get('X-Php-Ob-Level', -1); - $currentContent = ''; while (($obLevel = ob_get_level()) > $startObLevel && $obLevel !== $previousObLevel) { @@ -78,14 +84,14 @@ class ExceptionController extends ContainerAware } /** - * @param EngineInterface $templating - * @param string $format - * @param integer $code An HTTP response status code - * @param Boolean $debug + * @param Request $request + * @param string $format + * @param integer $code An HTTP response status code + * @param Boolean $debug * * @return TemplateReference */ - protected function findTemplate($templating, $format, $code, $debug) + protected function findTemplate(Request $request, $format, $code, $debug) { $name = $debug ? 'exception' : 'error'; if ($debug && 'html' == $format) { @@ -95,20 +101,38 @@ class ExceptionController extends ContainerAware // when not in debug, try to find a template for the specific HTTP status code and format if (!$debug) { $template = new TemplateReference('TwigBundle', 'Exception', $name.$code, $format, 'twig'); - if ($templating->exists($template)) { + if ($this->templateExists($template)) { return $template; } } // try to find a template for the given format $template = new TemplateReference('TwigBundle', 'Exception', $name, $format, 'twig'); - if ($templating->exists($template)) { + if ($this->templateExists($template)) { return $template; } // default to a generic HTML exception - $this->container->get('request')->setRequestFormat('html'); + $request->setRequestFormat('html'); return new TemplateReference('TwigBundle', 'Exception', $name, 'html', 'twig'); } + + // to be removed when the minimum required version of Twig is >= 2.0 + protected function templateExists($template) + { + $loader = $this->twig->getLoader(); + if ($loader instanceof \Twig_ExistsLoaderInterface) { + return $loader->exists($template); + } + + try { + $loader->getSource($template); + + return true; + } catch (Twig_Error_Loader $e) { + } + + return false; + } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php new file mode 100644 index 0000000000..075177d5eb --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/TwigLoaderPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * Adds services tagged twig.loader as Twig loaders + * + * @author Daniel Leech + */ +class TwigLoaderPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition('twig')) { + return; + } + + // register additional template loaders + $loaderIds = $container->findTaggedServiceIds('twig.loader'); + + if (count($loaderIds) === 0) { + throw new LogicException('No twig loaders found. You need to tag at least one loader with "twig.loader"'); + } + + if (count($loaderIds) === 1) { + $container->setAlias('twig.loader', key($loaderIds)); + } else { + $chainLoader = $container->getDefinition('twig.loader.chain'); + foreach (array_keys($loaderIds) as $id) { + $chainLoader->addMethodCall('addLoader', array(new Reference($id))); + } + $container->setAlias('twig.loader', 'twig.loader.chain'); + } + } +} diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index 3b3c8ed6e4..c327037e2b 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -34,7 +34,7 @@ class Configuration implements ConfigurationInterface $rootNode ->children() - ->scalarNode('exception_controller')->defaultValue('Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAction')->end() + ->scalarNode('exception_controller')->defaultValue('twig.controller.exception:showAction')->end() ->end() ; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 35016810ff..5b62b01c24 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -7,12 +7,13 @@ Twig_Environment Symfony\Bundle\TwigBundle\Loader\FilesystemLoader + Twig_Loader_Chain Symfony\Bundle\TwigBundle\TwigEngine Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer Symfony\Bridge\Twig\Extension\TranslationExtension Symfony\Bundle\TwigBundle\Extension\AssetsExtension Symfony\Bundle\TwigBundle\Extension\ActionsExtension - Symfony\Bundle\TwigBundle\Extension\CodeExtension + Symfony\Bridge\Twig\Extension\CodeExtension Symfony\Bridge\Twig\Extension\RoutingExtension Symfony\Bridge\Twig\Extension\YamlExtension Symfony\Bridge\Twig\Extension\FormExtension @@ -20,6 +21,7 @@ Symfony\Bridge\Twig\Form\TwigRenderer Symfony\Bridge\Twig\Translation\TwigExtractor Symfony\Component\HttpKernel\EventListener\ExceptionListener + Symfony\Bundle\TwigBundle\Controller\ExceptionController @@ -41,8 +43,11 @@ + + + @@ -107,5 +112,10 @@ %twig.exception_listener.controller% + + + + %kernel.debug% + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php index 394008332e..18523eaa76 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php @@ -12,72 +12,33 @@ namespace Symfony\Bundle\TwigBundle\Tests\Controller; use Symfony\Bundle\TwigBundle\Tests\TestCase; - use Symfony\Bundle\TwigBundle\Controller\ExceptionController; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Scope; use Symfony\Component\HttpFoundation\Request; class ExceptionControllerTest extends TestCase { - protected $controller; - protected $container; - protected $flatten; - protected $templating; - protected $kernel; - - protected function setUp() + public function testOnlyClearOwnOutputBuffers() { - parent::setUp(); - - $this->flatten = $this->getMock('Symfony\Component\HttpKernel\Exception\FlattenException'); - $this->flatten + $flatten = $this->getMock('Symfony\Component\HttpKernel\Exception\FlattenException'); + $flatten ->expects($this->once()) ->method('getStatusCode') ->will($this->returnValue(404)); - $this->controller = new ExceptionController(); - $this->kernel = $this->getMock('Symfony\\Component\\HttpKernel\\KernelInterface'); - $this->templating = $this->getMockBuilder('Symfony\\Bundle\\TwigBundle\\TwigEngine') + $twig = $this->getMockBuilder('\Twig_Environment') ->disableOriginalConstructor() ->getMock(); - $this->templating + $twig ->expects($this->any()) - ->method('renderResponse') + ->method('render') ->will($this->returnValue($this->getMock('Symfony\Component\HttpFoundation\Response'))); - $this->request = Request::create('/'); - $this->container = $this->getContainer(); - } + $twig + ->expects($this->any()) + ->method('getLoader') + ->will($this->returnValue($this->getMock('\Twig_LoaderInterface'))); + $request = Request::create('/'); + $request->headers->set('X-Php-Ob-Level', 1); - protected function tearDown() - { - parent::tearDown(); - - $this->controller = null; - $this->container = null; - $this->flatten = null; - $this->templating = null; - $this->kernel = null; - } - - public function testOnlyClearOwnOutputBuffers() - { - $this->request->headers->set('X-Php-Ob-Level', 1); - - $this->controller->setContainer($this->container); - $this->controller->showAction($this->flatten); - } - - private function getContainer() - { - $container = new ContainerBuilder(); - $container->addScope(new Scope('request')); - $container->set('request', $this->request); - $container->set('templating', $this->templating); - $container->setParameter('kernel.bundles', array()); - $container->setParameter('kernel.cache_dir', __DIR__); - $container->setParameter('kernel.root_dir', __DIR__); - $container->set('kernel', $this->kernel); - - return $container; + $controller = new ExceptionController($twig, false); + $controller->showAction($request, $flatten); } } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php new file mode 100644 index 0000000000..1f7d90e981 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php @@ -0,0 +1,87 @@ +builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $this->chainLoader = new Definition('loader'); + $this->pass = new TwigLoaderPass(); + } + + public function testMapperPassWithOneTaggedLoaders() + { + $serviceIds = array( + 'test_loader_1' => array( + ), + ); + + $this->builder->expects($this->once()) + ->method('hasDefinition') + ->with('twig') + ->will($this->returnValue(true)); + $this->builder->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('twig.loader') + ->will($this->returnValue($serviceIds)); + $this->builder->expects($this->once()) + ->method('setAlias') + ->with('twig.loader', 'test_loader_1'); + + $this->pass->process($this->builder); + } + + public function testMapperPassWithTwoTaggedLoaders() + { + $serviceIds = array( + 'test_loader_1' => array( + ), + 'test_loader_2' => array( + ), + ); + + $this->builder->expects($this->once()) + ->method('hasDefinition') + ->with('twig') + ->will($this->returnValue(true)); + $this->builder->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('twig.loader') + ->will($this->returnValue($serviceIds)); + $this->builder->expects($this->once()) + ->method('getDefinition') + ->with('twig.loader.chain') + ->will($this->returnValue($this->chainLoader)); + $this->builder->expects($this->once()) + ->method('setAlias') + ->with('twig.loader', 'twig.loader.chain'); + + $this->pass->process($this->builder); + $calls = $this->chainLoader->getMethodCalls(); + $this->assertEquals(2, count($calls)); + $this->assertEquals('addLoader', $calls[0][0]); + } + + /** + * @expectedException Symfony\Component\DependencyInjection\Exception\LogicException + */ + public function testMapperPassWithZeroTaggedLoaders() + { + $this->builder->expects($this->once()) + ->method('hasDefinition') + ->with('twig') + ->will($this->returnValue(true)); + $this->builder->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('twig.loader') + ->will($this->returnValue(array())); + + $this->pass->process($this->builder); + } +} diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index f10de14037..e778a16212 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -60,10 +60,9 @@ class TwigExtensionTest extends TestCase // Globals $calls = $container->getDefinition('twig')->getMethodCalls(); $this->assertEquals('app', $calls[0][1][0], '->load() registers services as Twig globals'); - + $this->assertEquals(new Reference('templating.globals'), $calls[0][1][1]); $this->assertEquals('foo', $calls[1][1][0], '->load() registers services as Twig globals'); $this->assertEquals(new Reference('bar'), $calls[1][1][1], '->load() registers services as Twig globals'); - $this->assertEquals('pi', $calls[2][1][0], '->load() registers variables as Twig globals'); $this->assertEquals(3.14, $calls[2][1][1], '->load() registers variables as Twig globals'); @@ -103,8 +102,7 @@ class TwigExtensionTest extends TestCase $this->compileContainer($container); $calls = $container->getDefinition('twig')->getMethodCalls(); - array_shift($calls); - foreach ($calls as $call) { + foreach (array_slice($calls, 1) as $call) { list($name, $value) = each($globals); $this->assertEquals($name, $call[1][0]); $this->assertSame($value, $call[1][1]); @@ -137,7 +135,7 @@ class TwigExtensionTest extends TestCase array('namespaced_path1', 'namespace'), array('namespaced_path2', 'namespace'), array(__DIR__.'/Fixtures/Resources/TwigBundle/views', 'Twig'), - array(realpath(__DIR__.'/../../Resources/views'), 'Twig'), + array(realpath(__DIR__.'/../..').'/Resources/views', 'Twig'), array(__DIR__.'/Fixtures/Resources/views'), ), $paths); } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php b/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php deleted file mode 100644 index 02c9210436..0000000000 --- a/src/Symfony/Bundle/TwigBundle/Tests/TwigEngineTest.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\Tests; - -use Symfony\Bundle\TwigBundle\TwigEngine; -use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; -use Symfony\Component\Templating\TemplateNameParser; - -class TwigEngineTest extends TestCase -{ - public function testEvaluateAddsAppGlobal() - { - $environment = $this->getTwigEnvironment(); - $container = $this->getContainer(); - $locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface'); - $engine = new TwigEngine($environment, new TemplateNameParser(), $locator); - - $template = $this->getMock('\Twig_TemplateInterface'); - - $environment->expects($this->once()) - ->method('loadTemplate') - ->will($this->returnValue($template)); - - $engine->render('name'); - } - - public function testEvaluateWithoutAvailableRequest() - { - $environment = $this->getTwigEnvironment(); - $container = new Container(); - $locator = $this->getMock('Symfony\Component\Config\FileLocatorInterface'); - $engine = new TwigEngine($environment, new TemplateNameParser(), $locator); - - $template = $this->getMock('\Twig_TemplateInterface'); - - $environment->expects($this->once()) - ->method('loadTemplate') - ->will($this->returnValue($template)); - - $container->set('request', null); - - $engine->render('name'); - } - - /** - * Creates a Container with a Session-containing Request service. - * - * @return Container - */ - protected function getContainer() - { - $container = new Container(); - $request = new Request(); - $session = new Session(new MockArraySessionStorage()); - - $request->setSession($session); - $container->set('request', $request); - - return $container; - } - - /** - * Creates a mock Twig_Environment object. - * - * @return \Twig_Environment - */ - protected function getTwigEnvironment() - { - return $this - ->getMockBuilder('\Twig_Environment') - ->setMethods(array('loadTemplate')) - ->getMock(); - } -} diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index a67756c0b1..88a172caab 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -14,6 +14,7 @@ namespace Symfony\Bundle\TwigBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass; +use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExceptionListenerPass; /** @@ -28,6 +29,7 @@ class TwigBundle extends Bundle parent::build($container); $container->addCompilerPass(new TwigEnvironmentPass()); + $container->addCompilerPass(new TwigLoaderPass()); $container->addCompilerPass(new ExceptionListenerPass()); } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/.gitignore b/src/Symfony/Bundle/WebProfilerBundle/.gitignore new file mode 100644 index 0000000000..44de97a36a --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index f646d50a4c..22f4eda795 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -12,27 +12,52 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; use Symfony\Component\HttpKernel\Exception\FlattenException; -use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpKernel\Debug\ExceptionHandler; use Symfony\Component\HttpFoundation\Response; -use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseExceptionController; /** * ExceptionController. * * @author Fabien Potencier */ -class ExceptionController extends BaseExceptionController +class ExceptionController { - /** - * {@inheritdoc} - */ - public function showAction(FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html') + protected $twig; + protected $debug; + protected $profiler; + + public function __construct(Profiler $profiler, \Twig_Environment $twig, $debug) { - $template = $this->container->get('kernel')->isDebug() ? 'exception' : 'error'; + $this->profiler = $profiler; + $this->twig = $twig; + $this->debug = $debug; + } + + /** + * Renders the exception panel for the given token. + * + * @param string $token The profiler token + * + * @return Response A Response instance + */ + public function showAction($token) + { + $this->profiler->disable(); + + $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); + $template = $this->getTemplate(); + + if (!$this->twig->getLoader()->exists($template)) { + $handler = new ExceptionHandler(); + + return new Response($handler->getContent($exception)); + } + $code = $exception->getStatusCode(); - return $this->container->get('templating')->renderResponse( - 'TwigBundle:Exception:'.$template.'.html.twig', + return new Response($this->twig->render( + $template, array( 'status_code' => $code, 'status_text' => Response::$statusTexts[$code], @@ -40,6 +65,52 @@ class ExceptionController extends BaseExceptionController 'logger' => null, 'currentContent' => '', ) - ); + )); + } + + /** + * Renders the exception panel stylesheet for the given token. + * + * @param string $token The profiler token + * + * @return Response A Response instance + */ + public function cssAction($token) + { + $this->profiler->disable(); + + $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); + $template = $this->getTemplate(); + + if (!$this->templateExists($template)) { + $handler = new ExceptionHandler(); + + return new Response($handler->getStylesheet($exception)); + } + + return new Response($this->twig->render('@WebProfiler/Collector/exception.css.twig')); + } + + protected function getTemplate() + { + return '@Twig/Exception/'.($this->debug ? 'exception' : 'error').'.html.twig'; + } + + // to be removed when the minimum required version of Twig is >= 2.0 + protected function templateExists($template) + { + $loader = $this->twig->getLoader(); + if ($loader instanceof \Twig_ExistsLoaderInterface) { + return $loader->exists($template); + } + + try { + $loader->getSource($template); + + return true; + } catch (Twig_Error_Loader $e) { + } + + return false; } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php b/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php index 571c7a92fa..a3a142af83 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Profiler/TemplateManager.php @@ -107,15 +107,31 @@ class TemplateManager $template = substr($template, 0, -10); } -// FIXME when Twig is able to check for template existence -/* - if (!$this->twig->exists($template.'.html.twig')) { + if (!$this->templateExists($template.'.html.twig')) { throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name)); } -*/ + $templates[$name] = $template.'.html.twig'; } return $templates; } + + // to be removed when the minimum required version of Twig is >= 2.0 + protected function templateExists($template) + { + $loader = $this->twig->getLoader(); + if ($loader instanceof \Twig_ExistsLoaderInterface) { + return $loader->exists($template); + } + + try { + $loader->getSource($template); + + return true; + } catch (Twig_Error_Loader $e) { + } + + return false; + } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index c06f0dbd21..22d12409cd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -7,6 +7,7 @@ Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController Symfony\Bundle\WebProfilerBundle\Controller\RouterController + Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController @@ -23,5 +24,11 @@ + + + + + %kernel.debug% + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig index 6dbfef293c..fa1e75ec96 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/exception.html.twig @@ -2,7 +2,7 @@ {% block head %} {{ parent() }} {% endblock %} @@ -28,7 +28,7 @@

{% else %}
- {% render 'WebProfilerBundle:Exception:show' with { 'exception': collector.exception, 'format': 'html' } %} + {% render 'web_profiler.controller.exception:showAction' with { 'token': token } %}
{% endif %} {% endblock %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index 65d93a64d2..8ab08f9170 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -1,16 +1,32 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% import _self as logger %} + {% block toolbar %} - {% if collector.counterrors %} + {% if collector.counterrors or collector.countdeprecations %} {% set icon %} Logs - {{ collector.counterrors }} + {% if collector.counterrors %} + {% set status_color = "red" %} + {% else %} + {% set status_color = "yellow" %} + {% endif %} + {% set error_count = collector.counterrors + collector.countdeprecations %} + {{ error_count }} {% endset %} {% set text %} -
- Exception - {{ collector.counterrors }} -
+ {% if collector.counterrors %} +
+ Exception + {{ collector.counterrors }} +
+ {% endif %} + {% if collector.countdeprecations %} +
+ Deprecated Calls + {{ collector.countdeprecations }} +
+ {% endif %} {% endset %} {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} {% endif %} @@ -20,9 +36,10 @@ Logger Logs - {% if collector.counterrors %} + {% if collector.counterrors or collector.countdeprecations %} + {% set error_count = collector.counterrors + collector.countdeprecations %} - {{ collector.counterrors }} + {{ error_count }} {% endif %} @@ -41,8 +58,9 @@