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); } ?>
$v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>>escape($view['translator']->trans($label, array(), $translation_domain)) ?>
+
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 %}
- {{ 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 @@
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 @@
Priority
- {% for value, text in { 100: 'DEBUG', 200: 'INFO', 250: 'NOTICE', 300: 'WARNING', 400: 'ERROR', 500: 'CRITICAL', 550: 'ALERT', 600: 'EMERGENCY' } %}
- {{ text }}
+ {# values < 0 are custom levels #}
+ {% for value, text in { 100: 'DEBUG', 200: 'INFO', 250: 'NOTICE', 300: 'WARNING', 400: 'ERROR', 500: 'CRITICAL', 550: 'ALERT', 600: 'EMERGENCY', '-100': 'DEPRECATION only' } %}
+ {{ text }}
{% endfor %}
@@ -55,15 +73,9 @@
{% if collector.logs %}
- {% for log in collector.logs if log.priority >= priority %}
+ {% for log in collector.logs if priority >= 0 and log.priority >= priority or priority < 0 and log.context.type|default(0) == priority %}
- {{ log.priorityName }} - {{ log.message }}
- {% if log.context is defined and log.context is not empty %}
-
-
- Context : {{ log.context|yaml_encode }}
-
- {% endif %}
+ {{ logger.display_message(loop.index, log) }}
{% else %}
No logs available for this priority.
@@ -75,3 +87,35 @@
{% endif %}
{% endblock %}
+
+
+{% macro display_message(log_index, log) %}
+ {% if constant('Symfony\\Component\\HttpKernel\\Debug\\ErrorHandler::TYPE_DEPRECATION') == log.context.type|default(0) %}
+ DEPRECATION - Deprecated call in {{ log.context.file|format_file(log.context.line) }}.
+ {% set id = 'sf-call-stack-' ~ log_index %}
+
+
+
+
+ {% for index, call in log.context.stack if index > 0 %}
+ {% if index == 1 %}
+
+ {% endif %}
+ {% if call.class is defined %}
+ {% set from = call.class|abbr_class ~ '::' ~ call.function|abbr_method() %}
+ {% elseif call.function is defined %}
+ {% set from = call.function|abbr_method %}
+ {% elseif call.file is defined %}
+ {% set from = call.file %}
+ {% else %}
+ {% set from = '-' %}
+ {% endif %}
+
+ Called from {{ call.file is defined and call.line is defined ? call.file|format_file(call.line, from) : from|raw }}
+
+ {{ index == log.context.stack|length - 1 ? ' ' : '' }}
+ {% endfor %}
+ {% else %}
+ {{ log.priorityName }} - {{ log.message }}
+ {% endif %}
+{% endmacro %}
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 c189ee9d01..0fa73359ec 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/layout.html.twig
@@ -13,7 +13,7 @@
{% if profile %}
-
View all
+
View last 10
Profile for:
{{ profile.method|upper }}
{% if profile.method|upper in ['GET', 'HEAD'] %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
index 6b8c045527..6f5cde5dc5 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig
@@ -273,6 +273,10 @@ ul.alt li.warning {
background-color: #ffcc00;
margin-bottom: 1px;
}
+ul.sf-call-stack li {
+ text-size: small;
+ padding: 0 0 0 20px;
+}
td.main, td.menu {
text-align: left;
margin: 0;
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ExceptionControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ExceptionControllerTest.php
deleted file mode 100644
index 03ff57263a..0000000000
--- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ExceptionControllerTest.php
+++ /dev/null
@@ -1,89 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bundle\WebProfilerBundle\Tests\Controller;
-
-use Symfony\Bundle\WebProfilerBundle\Tests\TestCase;
-
-use Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Scope;
-use Symfony\Component\DependencyInjection\Definition;
-
-class ExceptionControllerTest extends TestCase
-{
- protected $controller;
- protected $container;
- protected $flatten;
- protected $kernel;
-
- protected function setUp()
- {
- parent::setUp();
-
- $this->flatten = $this->getMock('Symfony\Component\HttpKernel\Exception\FlattenException');
- $this->flatten->expects($this->once())->method('getStatusCode')->will($this->returnValue(404));
- $this->controller = new ExceptionController();
- $this->kernel = $this->getMock('Symfony\\Component\\HttpKernel\\KernelInterface');
- $this->container = $this->getContainer();
- }
-
- protected function tearDown()
- {
- parent::tearDown();
-
- $this->controller = null;
- $this->container = null;
- $this->flatten = null;
- $this->kernel = null;
- }
-
- /**
- * @dataProvider getDebugModes
- */
- public function testShowActionDependingOnDebug($debug)
- {
- $this->container->setParameter('kernel.debug', $debug);
- $this->controller->setContainer($this->container);
- $this->controller->showAction($this->flatten);
- }
-
- public function getDebugModes()
- {
- return array(
- array(true),
- array(false),
- );
- }
-
- private function getContainer()
- {
- $container = new ContainerBuilder();
- $container->addScope(new Scope('request'));
- $container->register('request', 'Symfony\\Component\\HttpFoundation\\Request')->setScope('request');
- $container->register('templating.helper.assets', $this->getMockClass('Symfony\\Component\\Templating\\Helper\\AssetsHelper'));
- $container->register('templating.helper.router', $this->getMockClass('Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\RouterHelper'))
- ->addArgument(new Definition($this->getMockClass('Symfony\\Component\\Routing\\RouterInterface')));
- $container->register('twig', 'Twig_Environment');
- $container->register('templating.engine.twig', $this->getMockClass('Symfony\\Bundle\\TwigBundle\\TwigEngine'))
- ->addArgument($this->getMock('Twig_Environment'))
- ->addArgument($this->getMock('Symfony\\Component\\Templating\\TemplateNameParserInterface'))
- ->addArgument(new Definition($this->getMockClass('Symfony\Component\Config\FileLocatorInterface')))
- ->addArgument($this->getMock('Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables', array(), array($this->getMock('Symfony\\Component\\DependencyInjection\\Container'))));
- $container->setAlias('templating', 'templating.engine.twig');
- $container->setParameter('kernel.bundles', array());
- $container->setParameter('kernel.cache_dir', __DIR__);
- $container->setParameter('kernel.root_dir', __DIR__);
- $container->set('kernel', $this->kernel);
-
- return $container;
- }
-}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php
index eacfbe9a6d..488f2eadb8 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php
+++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Profiler/TemplateManagerTest.php
@@ -80,7 +80,7 @@ class TemplateManagerTest extends TestCase
->method('hasCollector')
->will($this->returnCallback(array($this, 'profileHasCollectorCallback')));
- $this->assertEquals('FooBundle:Collector:foo.html.twig', $this->templateManager->getName($profile, 'foo'));
+ $this->assertEquals('FooBundle:Collector:foo.html.twig', $this->templateManager->getName($profile, 'foo'));
}
/**
@@ -89,7 +89,6 @@ class TemplateManagerTest extends TestCase
*/
public function testGetTemplates()
{
-
$profile = $this->mockProfile();
$profile->expects($this->any())
->method('hasCollector')
@@ -145,6 +144,10 @@ class TemplateManagerTest extends TestCase
->method('loadTemplate')
->will($this->returnValue('loadedTemplate'));
+ $this->twigEnvironment->expects($this->any())
+ ->method('getLoader')
+ ->will($this->returnValue($this->getMock('\Twig_LoaderInterface')));
+
return $this->twigEnvironment;
}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json
index 62d1aae314..338ad281d5 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/composer.json
+++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json
@@ -17,7 +17,14 @@
],
"require": {
"php": ">=5.3.3",
- "symfony/twig-bundle": "2.2.*"
+ "symfony/http-kernel": "2.2.*",
+ "symfony/routing": "2.2.*",
+ "symfony/twig-bridge": "2.2.*"
+ },
+ "require-dev": {
+ "symfony/config": "2.2.*",
+ "symfony/dependency-injection": "2.2.*",
+ "symfony/stopwatch": "2.2.*"
},
"autoload": {
"psr-0": { "Symfony\\Bundle\\WebProfilerBundle\\": "" }
diff --git a/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist b/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist
new file mode 100644
index 0000000000..7750cbdff7
--- /dev/null
+++ b/src/Symfony/Bundle/WebProfilerBundle/phpunit.xml.dist
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./Resources
+ ./vendor
+
+
+
+
diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php
index 91d52df637..e03b73cf0b 100644
--- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Tests\BrowserKit;
+namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\Client;
use Symfony\Component\BrowserKit\History;
diff --git a/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php b/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php
index 0c077d3723..df5cc777d4 100644
--- a/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/CookieJarTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Tests\BrowserKit;
+namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\Cookie;
diff --git a/src/Symfony/Component/BrowserKit/Tests/CookieTest.php b/src/Symfony/Component/BrowserKit/Tests/CookieTest.php
index 2db3ac6f24..13e343a901 100644
--- a/src/Symfony/Component/BrowserKit/Tests/CookieTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/CookieTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Tests\BrowserKit;
+namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\Cookie;
diff --git a/src/Symfony/Component/BrowserKit/Tests/HistoryTest.php b/src/Symfony/Component/BrowserKit/Tests/HistoryTest.php
index 6794f31dde..882b730ef4 100644
--- a/src/Symfony/Component/BrowserKit/Tests/HistoryTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/HistoryTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Tests\BrowserKit;
+namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\Request;
diff --git a/src/Symfony/Component/BrowserKit/Tests/RequestTest.php b/src/Symfony/Component/BrowserKit/Tests/RequestTest.php
index c567faddee..b75b5fb5c0 100644
--- a/src/Symfony/Component/BrowserKit/Tests/RequestTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/RequestTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Tests\BrowserKit;
+namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\Request;
diff --git a/src/Symfony/Component/BrowserKit/Tests/ResponseTest.php b/src/Symfony/Component/BrowserKit/Tests/ResponseTest.php
index 1e8c638a3b..878752c75e 100644
--- a/src/Symfony/Component/BrowserKit/Tests/ResponseTest.php
+++ b/src/Symfony/Component/BrowserKit/Tests/ResponseTest.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Tests\BrowserKit;
+namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\Response;
diff --git a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php b/src/Symfony/Component/ClassLoader/UniversalClassLoader.php
index e3170ccee0..ee16e4809c 100644
--- a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php
+++ b/src/Symfony/Component/ClassLoader/UniversalClassLoader.php
@@ -242,7 +242,7 @@ class UniversalClassLoader
* Loads the given class or interface.
*
* @param string $class The name of the class
- *
+ *
* @return Boolean|null True, if loaded
*/
public function loadClass($class)
diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md
index 8426abd57b..63c244078f 100644
--- a/src/Symfony/Component/Config/CHANGELOG.md
+++ b/src/Symfony/Component/Config/CHANGELOG.md
@@ -6,6 +6,7 @@ CHANGELOG
* added numerical type handling for config definitions
* added convenience methods for optional configuration sections to ArrayNodeDefinition
+ * added a utils class for XML manipulations
2.1.0
-----
diff --git a/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php
index 1db01def30..2216c47f27 100755
--- a/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php
+++ b/src/Symfony/Component/Config/Definition/Builder/EnumNodeDefinition.php
@@ -23,7 +23,7 @@ class EnumNodeDefinition extends ScalarNodeDefinition
}
$this->values = $values;
-
+
return $this;
}
@@ -40,4 +40,4 @@ class EnumNodeDefinition extends ScalarNodeDefinition
return new EnumNode($this->name, $this->parent, $this->values);
}
-}
\ No newline at end of file
+}
diff --git a/src/Symfony/Component/Config/Definition/Processor.php b/src/Symfony/Component/Config/Definition/Processor.php
index 3b00c7e4c0..10aca917c5 100644
--- a/src/Symfony/Component/Config/Definition/Processor.php
+++ b/src/Symfony/Component/Config/Definition/Processor.php
@@ -23,12 +23,15 @@ class Processor
*
* @param NodeInterface $configTree The node tree describing the configuration
* @param array $configs An array of configuration items to process
+ * @param bool $normalizeKeys Flag indicating if config key normalization is needed. True by default.
*
* @return array The processed configuration
*/
- public function process(NodeInterface $configTree, array $configs)
+ public function process(NodeInterface $configTree, array $configs, $normalizeKeys = true)
{
- $configs = self::normalizeKeys($configs);
+ if ($normalizeKeys) {
+ $configs = self::normalizeKeys($configs);
+ }
$currentConfig = array();
foreach ($configs as $config) {
@@ -44,12 +47,13 @@ class Processor
*
* @param ConfigurationInterface $configuration The configuration class
* @param array $configs An array of configuration items to process
+ * @param bool $normalizeKeys Flag indicating if config key normalization is needed. True by default.
*
* @return array The processed configuration
*/
- public function processConfiguration(ConfigurationInterface $configuration, array $configs)
+ public function processConfiguration(ConfigurationInterface $configuration, array $configs, $normalizeKeys = true)
{
- return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
+ return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs, $normalizeKeys);
}
/**
diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php
index b9c174f94d..9acfb7b0c0 100644
--- a/src/Symfony/Component/Config/Loader/Loader.php
+++ b/src/Symfony/Component/Config/Loader/Loader.php
@@ -47,10 +47,12 @@ abstract class Loader implements LoaderInterface
*
* @param mixed $resource A Resource
* @param string $type The resource type
+ *
+ * @return mixed
*/
public function import($resource, $type = null)
{
- $this->resolve($resource)->load($resource, $type);
+ return $this->resolve($resource)->load($resource, $type);
}
/**
diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Util/document_type.xml b/src/Symfony/Component/Config/Tests/Fixtures/Util/document_type.xml
new file mode 100644
index 0000000000..4c25228263
--- /dev/null
+++ b/src/Symfony/Component/Config/Tests/Fixtures/Util/document_type.xml
@@ -0,0 +1,3 @@
+
+]>
+
diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Util/invalid.xml b/src/Symfony/Component/Config/Tests/Fixtures/Util/invalid.xml
new file mode 100644
index 0000000000..a07af9fd85
--- /dev/null
+++ b/src/Symfony/Component/Config/Tests/Fixtures/Util/invalid.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Util/invalid_schema.xml b/src/Symfony/Component/Config/Tests/Fixtures/Util/invalid_schema.xml
new file mode 100644
index 0000000000..e2725a2c2a
--- /dev/null
+++ b/src/Symfony/Component/Config/Tests/Fixtures/Util/invalid_schema.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Util/schema.xsd b/src/Symfony/Component/Config/Tests/Fixtures/Util/schema.xsd
new file mode 100644
index 0000000000..e56820f691
--- /dev/null
+++ b/src/Symfony/Component/Config/Tests/Fixtures/Util/schema.xsd
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/Symfony/Component/Config/Tests/Fixtures/Util/valid.xml b/src/Symfony/Component/Config/Tests/Fixtures/Util/valid.xml
new file mode 100644
index 0000000000..a96bb38267
--- /dev/null
+++ b/src/Symfony/Component/Config/Tests/Fixtures/Util/valid.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php b/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php
index 5b14e19777..8ce5a8b68e 100644
--- a/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php
+++ b/src/Symfony/Component/Config/Tests/Loader/LoaderTest.php
@@ -55,6 +55,15 @@ class LoaderTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Symfony\Component\Config\Exception\FileLoaderLoadException', $e, '->resolve() throws a FileLoaderLoadException if the resource cannot be loaded');
}
}
+
+ public function testImport()
+ {
+ $loader = $this->getMock('Symfony\Component\Config\Loader\Loader', array('supports', 'load'));
+ $loader->expects($this->once())->method('supports')->will($this->returnValue(true));
+ $loader->expects($this->once())->method('load')->will($this->returnValue('yes'));
+
+ $this->assertEquals('yes', $loader->import('foo'));
+ }
}
class ProjectLoader1 extends Loader
diff --git a/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php
new file mode 100644
index 0000000000..a3d264e189
--- /dev/null
+++ b/src/Symfony/Component/Config/Tests/Util/XmlUtilsTest.php
@@ -0,0 +1,129 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Tests\Loader;
+
+use Symfony\Component\Config\Util\XmlUtils;
+
+class XmlUtilsTest extends \PHPUnit_Framework_TestCase
+{
+ public function testLoadFile()
+ {
+ $fixtures = __DIR__.'/../Fixtures/Util/';
+
+ try {
+ XmlUtils::loadFile($fixtures.'invalid.xml');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('ERROR 77', $e->getMessage());
+ }
+
+ try {
+ XmlUtils::loadFile($fixtures.'document_type.xml');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('Document types are not allowed', $e->getMessage());
+ }
+
+ try {
+ XmlUtils::loadFile($fixtures.'invalid_schema.xml', $fixtures.'schema.xsd');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('ERROR 1845', $e->getMessage());
+ }
+
+ try {
+ XmlUtils::loadFile($fixtures.'invalid_schema.xml', 'invalid_callback_or_file');
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('XSD file or callable', $e->getMessage());
+ }
+
+ $mock = $this->getMock(__NAMESPACE__.'\Validator');
+ $mock->expects($this->exactly(2))->method('validate')->will($this->onConsecutiveCalls(false, true));
+
+ try {
+ XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate'));
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertContains('is not valid', $e->getMessage());
+ }
+
+ $this->assertInstanceOf('DOMDocument', XmlUtils::loadFile($fixtures.'valid.xml', array($mock, 'validate')));
+ }
+
+ /**
+ * @dataProvider getDataForConvertDomToArray
+ */
+ public function testConvertDomToArray($expected, $xml, $root = false, $checkPrefix = true)
+ {
+ $dom = new \DOMDocument();
+ $dom->loadXML($root ? $xml : ''.$xml.' ');
+
+ $this->assertSame($expected, XmlUtils::convertDomElementToArray($dom->documentElement, $checkPrefix));
+ }
+
+ public function getDataForConvertDomToArray()
+ {
+ return array(
+ array(null, ''),
+ array('bar', 'bar'),
+ array(array('bar' => 'foobar'), ' ', true),
+ array(array('foo' => null), ' '),
+ array(array('foo' => 'bar'), 'bar '),
+ array(array('foo' => array('foo' => 'bar')), ' '),
+ array(array('foo' => array('foo' => 'bar')), 'bar '),
+ array(array('foo' => array('foo' => 'bar', 'value' => 'text')), 'text '),
+ array(array('foo' => array('attr' => 'bar', 'foo' => 'text')), 'text '),
+ array(array('foo' => array('bar', 'text')), 'bar text '),
+ array(array('foo' => array(array('foo' => 'bar'), array('foo' => 'text'))), ' '),
+ array(array('foo' => array('foo' => array('bar', 'text'))), 'text '),
+ array(array('foo' => 'bar'), 'bar '),
+ array(array('foo' => 'text'), 'text '),
+ array(array('foo' => array('bar' => 'bar', 'value' => 'text')), 'text ', false, false),
+ array(array('attr' => 1, 'b' => 'hello'), 'hello 2 ', true),
+ );
+ }
+
+ /**
+ * @dataProvider getDataForPhpize
+ */
+ public function testPhpize($expected, $value)
+ {
+ $this->assertSame($expected, XmlUtils::phpize($value));
+ }
+
+ public function getDataForPhpize()
+ {
+ return array(
+ array(null, 'null'),
+ array(true, 'true'),
+ array(false, 'false'),
+ array(null, 'Null'),
+ array(true, 'True'),
+ array(false, 'False'),
+ array(0, '0'),
+ array(1, '1'),
+ array(0777, '0777'),
+ array(255, '0xFF'),
+ array(100.0, '1e2'),
+ array(-120.0, '-1.2E2'),
+ array(-10100.1, '-10100.1'),
+ array(-10100.1, '-10,100.1'),
+ array('foo', 'foo'),
+ );
+ }
+}
+
+interface Validator
+{
+ public function validate();
+}
diff --git a/src/Symfony/Component/Config/Util/XmlUtils.php b/src/Symfony/Component/Config/Util/XmlUtils.php
new file mode 100644
index 0000000000..9317e15e7c
--- /dev/null
+++ b/src/Symfony/Component/Config/Util/XmlUtils.php
@@ -0,0 +1,222 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Util;
+
+/**
+ * XMLUtils is a bunch of utility methods to XML operations.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Fabien Potencier
+ * @author Martin Hasoň
+ */
+class XmlUtils
+{
+ /**
+ * This class should not be instantiated
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Loads an XML file.
+ *
+ * @param string $file An XML file path
+ * @param string|callable $schemaOrCallable An XSD schema file path or callable
+ *
+ * @return \DOMDocument
+ *
+ * @throws \InvalidArgumentException When loading of XML file returns error
+ */
+ public static function loadFile($file, $schemaOrCallable = null)
+ {
+ $internalErrors = libxml_use_internal_errors(true);
+ $disableEntities = libxml_disable_entity_loader(true);
+ libxml_clear_errors();
+
+ $dom = new \DOMDocument();
+ $dom->validateOnParse = true;
+ if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
+ libxml_disable_entity_loader($disableEntities);
+
+ throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));
+ }
+
+ $dom->normalizeDocument();
+
+ libxml_use_internal_errors($internalErrors);
+ libxml_disable_entity_loader($disableEntities);
+
+ foreach ($dom->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ throw new \InvalidArgumentException('Document types are not allowed.');
+ }
+ }
+
+ if (null !== $schemaOrCallable) {
+ $internalErrors = libxml_use_internal_errors(true);
+ libxml_clear_errors();
+
+ $e = null;
+ if (is_callable($schemaOrCallable)) {
+ try {
+ $valid = call_user_func($schemaOrCallable, $dom, $internalErrors);
+ } catch (\Exception $e) {
+ $valid = false;
+ }
+ } elseif (!is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
+ $valid = @$dom->schemaValidate($schemaOrCallable);
+ } else {
+ libxml_use_internal_errors($internalErrors);
+
+ throw new \InvalidArgumentException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
+ }
+
+ if (!$valid) {
+ $messages = static::getXmlErrors($internalErrors);
+ if (empty($messages)) {
+ $messages = array(sprintf('The XML file "%s" is not valid.', $file));
+ }
+ throw new \InvalidArgumentException(implode("\n", $messages), 0, $e);
+ }
+
+ libxml_use_internal_errors($internalErrors);
+ }
+
+ return $dom;
+ }
+
+ /**
+ * Converts a \DomElement object to a PHP array.
+ *
+ * The following rules applies during the conversion:
+ *
+ * * Each tag is converted to a key value or an array
+ * if there is more than one "value"
+ *
+ * * The content of a tag is set under a "value" key (bar )
+ * if the tag also has some nested tags
+ *
+ * * The attributes are converted to keys ( )
+ *
+ * * The nested-tags are converted to keys (bar )
+ *
+ * @param \DomElement $element A \DomElement instance
+ * @param Boolean $checkPrefix Check prefix in an element or an attribute name
+ *
+ * @return array A PHP array
+ */
+ public static function convertDomElementToArray(\DomElement $element, $checkPrefix = true)
+ {
+ $prefix = (string) $element->prefix;
+ $empty = true;
+ $config = array();
+ foreach ($element->attributes as $name => $node) {
+ if ($checkPrefix && !in_array((string) $node->prefix, array('', $prefix), true)) {
+ continue;
+ }
+ $config[$name] = static::phpize($node->value);
+ $empty = false;
+ }
+
+ $nodeValue = false;
+ foreach ($element->childNodes as $node) {
+ if ($node instanceof \DOMText) {
+ if (trim($node->nodeValue)) {
+ $nodeValue = trim($node->nodeValue);
+ $empty = false;
+ }
+ } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
+ continue;
+ } elseif (!$node instanceof \DOMComment) {
+ $value = static::convertDomElementToArray($node, $checkPrefix);
+
+ $key = $node->localName;
+ if (isset($config[$key])) {
+ if (!is_array($config[$key]) || !is_int(key($config[$key]))) {
+ $config[$key] = array($config[$key]);
+ }
+ $config[$key][] = $value;
+ } else {
+ $config[$key] = $value;
+ }
+
+ $empty = false;
+ }
+ }
+
+ if (false !== $nodeValue) {
+ $value = static::phpize($nodeValue);
+ if (count($config)) {
+ $config['value'] = $value;
+ } else {
+ $config = $value;
+ }
+ }
+
+ return !$empty ? $config : null;
+ }
+
+ /**
+ * Converts an xml value to a php type.
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ public static function phpize($value)
+ {
+ $value = (string) $value;
+ $lowercaseValue = strtolower($value);
+
+ switch (true) {
+ case 'null' === $lowercaseValue:
+ return null;
+ case ctype_digit($value):
+ $raw = $value;
+ $cast = intval($value);
+
+ return '0' == $value[0] ? octdec($value) : (((string) $raw == (string) $cast) ? $cast : $raw);
+ case 'true' === $lowercaseValue:
+ return true;
+ case 'false' === $lowercaseValue:
+ return false;
+ case is_numeric($value):
+ return '0x' == $value[0].$value[1] ? hexdec($value) : floatval($value);
+ case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $value):
+ return floatval(str_replace(',', '', $value));
+ default:
+ return $value;
+ }
+ }
+
+ protected static function getXmlErrors($internalErrors)
+ {
+ $errors = array();
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
+ LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ $error->code,
+ trim($error->message),
+ $error->file ? $error->file : 'n/a',
+ $error->line,
+ $error->column
+ );
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $errors;
+ }
+}
diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php
index 09d93aeafc..46080820e0 100644
--- a/src/Symfony/Component/Console/Application.php
+++ b/src/Symfony/Component/Console/Application.php
@@ -866,7 +866,7 @@ class Application
return preg_replace('{^(\d+)x.*$}', '$1', $ansicon);
}
- if (preg_match('{columns:\s*(\d+)}i', $this->getConsoleMode(), $matches)) {
+ if (preg_match('{^(\d+)x\d+$}i', $this->getConsoleMode(), $matches)) {
return $matches[1];
}
}
@@ -888,7 +888,7 @@ class Application
return preg_replace('{^\d+x\d+ \(\d+x(\d+)\)$}', '$1', trim($ansicon));
}
- if (preg_match('{lines:\s*(\d+)}i', $this->getConsoleMode(), $matches)) {
+ if (preg_match('{^\d+x(\d+)$}i', $this->getConsoleMode(), $matches)) {
return $matches[1];
}
}
@@ -980,7 +980,7 @@ class Application
/**
* Runs and parses mode CON if it's available, suppressing any error output
*
- * @return string
+ * @return string x or null if it could not be parsed
*/
private function getConsoleMode()
{
@@ -996,7 +996,9 @@ class Application
fclose($pipes[2]);
proc_close($process);
- return $info;
+ if (preg_match('{--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n}', $info, $matches)) {
+ return $matches[2].'x'.$matches[1];
+ }
}
}
diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md
index a5f1722bae..ee0459e13d 100644
--- a/src/Symfony/Component/Console/CHANGELOG.md
+++ b/src/Symfony/Component/Console/CHANGELOG.md
@@ -6,6 +6,7 @@ CHANGELOG
* added support for colorization on Windows via ConEmu
* add a method to Dialog Helper to ask for a question and hide the response
+ * added support for interactive selections in console (DialogHelper::select())
2.1.0
-----
diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php
index e9af834ed3..1b11bb8105 100644
--- a/src/Symfony/Component/Console/Command/Command.php
+++ b/src/Symfony/Component/Console/Command/Command.php
@@ -247,7 +247,7 @@ class Command
* If this method is used, it overrides the code defined
* in the execute() method.
*
- * @param \Closure $code A \Closure
+ * @param callable $code A callable(InputInterface $input, OutputInterface $output)
*
* @return Command The current instance
*
@@ -255,8 +255,12 @@ class Command
*
* @api
*/
- public function setCode(\Closure $code)
+ public function setCode($code)
{
+ if (!is_callable($code)) {
+ throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.');
+ }
+
$this->code = $code;
return $this;
diff --git a/src/Symfony/Component/Console/Helper/DialogHelper.php b/src/Symfony/Component/Console/Helper/DialogHelper.php
index 232343454e..baeeefc591 100644
--- a/src/Symfony/Component/Console/Helper/DialogHelper.php
+++ b/src/Symfony/Component/Console/Helper/DialogHelper.php
@@ -24,6 +24,40 @@ class DialogHelper extends Helper
private static $shell;
private static $stty;
+ /**
+ * Asks the user to select a value.
+ *
+ * @param OutputInterface $output An Output instance
+ * @param string|array $question The question to ask
+ * @param array $choices List of choices to pick from
+ * @param Boolean $default The default answer if the user enters nothing
+ * @param integer|false $attempts Max number of times to ask before giving up (false by default, which means infinite)
+ * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked
+ *
+ * @return integer|string The selected value (the key of the choices array)
+ */
+ public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid')
+ {
+ $width = max(array_map('strlen', array_keys($choices)));
+
+ $messages = (array) $question;
+ foreach ($choices as $key => $value) {
+ $messages[] = sprintf(" [%-${width}s ] %s", $key, $value);
+ }
+
+ $output->writeln($messages);
+
+ $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage) {
+ if (empty($choices[$picked])) {
+ throw new \InvalidArgumentException(sprintf($errorMessage, $picked));
+ }
+
+ return $picked;
+ }, $attempts, $default);
+
+ return $result;
+ }
+
/**
* Asks a question to the user.
*
diff --git a/src/Symfony/Component/Console/Helper/ProgressHelper.php b/src/Symfony/Component/Console/Helper/ProgressHelper.php
index ed0ccab140..07b9a7ff8a 100644
--- a/src/Symfony/Component/Console/Helper/ProgressHelper.php
+++ b/src/Symfony/Component/Console/Helper/ProgressHelper.php
@@ -36,6 +36,7 @@ class ProgressHelper extends Helper
private $format = null;
private $redrawFreq = 1;
+ private $lastMessagesLength;
private $barCharOriginal;
/**
@@ -380,21 +381,20 @@ class ProgressHelper extends Helper
*
* @param OutputInterface $output An Output instance
* @param string|array $messages The message as an array of lines or a single string
- * @param Boolean $newline Whether to add a newline or not
- * @param integer $size The size of line
*/
- private function overwrite(OutputInterface $output, $messages, $newline = false, $size = 80)
+ private function overwrite(OutputInterface $output, $messages)
{
- $output->write(str_repeat("\x08", $size));
- $output->write($messages);
- $output->write(str_repeat(' ', $size - strlen($messages)));
-
- // clean up the end line
- $output->write(str_repeat("\x08", $size - strlen($messages)));
-
- if ($newline) {
- $output->writeln('');
+ // carriage return
+ $output->write("\x0D");
+ if ($this->lastMessagesLength!==null) {
+ // clear the line with the length of the last message
+ $output->write(str_repeat("\x20", $this->lastMessagesLength));
+ // carriage return
+ $output->write("\x0D");
}
+ $output->write($messages);
+
+ $this->lastMessagesLength=strlen($messages);
}
/**
diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php
index 14527c1351..23edd490b3 100644
--- a/src/Symfony/Component/Console/Tests/ApplicationTest.php
+++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php
@@ -242,7 +242,6 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertRegExp('/Did you mean this/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with one alternative');
}
-
$application->add(new \Foo1Command());
$application->add(new \Foo2Command());
@@ -611,15 +610,15 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($inputDefinition->hasOption('custom'));
}
-
+
public function testSettingCustomInputDefinitionOverwritesDefaultValues()
{
$application = new Application();
$application->setAutoExit(false);
$application->setCatchExceptions(false);
-
+
$application->setDefinition(new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.'))));
-
+
$inputDefinition = $application->getDefinition();
// check whether the default arguments and options are not returned any more
diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php
index eb78b8911e..166ce7879e 100644
--- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php
+++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php
@@ -240,6 +240,31 @@ class CommandTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay());
}
+ public function testSetCodeWithNonClosureCallable()
+ {
+ $command = new \TestCommand();
+ $ret = $command->setCode(array($this, 'callableMethodCommand'));
+ $this->assertEquals($command, $ret, '->setCode() implements a fluent interface');
+ $tester = new CommandTester($command);
+ $tester->execute(array());
+ $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Invalid callable provided to Command::setCode.
+ */
+ public function testSetCodeWithNonCallable()
+ {
+ $command = new \TestCommand();
+ $command->setCode(array($this, 'nonExistentMethod'));
+ }
+
+ public function callableMethodCommand(InputInterface $input, OutputInterface $output)
+ {
+ $output->writeln('from the code...');
+ }
+
public function testAsText()
{
$command = new \TestCommand();
diff --git a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
index 5f3b809ce9..33187a7d26 100644
--- a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
+++ b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php
@@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
-
namespace Symfony\Component\Console\Tests\Formatter;
use Symfony\Component\Console\Formatter\OutputFormatter;
diff --git a/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
index 658ab69b4c..d0da968a15 100644
--- a/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php
@@ -18,6 +18,31 @@ use Symfony\Component\Console\Output\StreamOutput;
class DialogHelperTest extends \PHPUnit_Framework_TestCase
{
+ public function testSelect()
+ {
+ $dialog = new DialogHelper();
+
+ $helperSet = new HelperSet(array(new FormatterHelper()));
+ $dialog->setHelperSet($helperSet);
+
+ $heroes = array('Superman', 'Batman', 'Spiderman');
+
+ $dialog->setInputStream($this->getInputStream("\n1\nFabien\n1\nFabien\nFabien\n"));
+ $this->assertEquals('2', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '2'));
+ $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes));
+ $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!'));
+
+ rewind($output->getStream());
+ $this->assertContains('Input "Fabien" is not a superhero!', stream_get_contents($output->getStream()));
+
+ try {
+ $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, 1));
+ $this->fail();
+ } catch (\InvalidArgumentException $e) {
+ $this->assertEquals('Value "Fabien" is invalid', $e->getMessage());
+ }
+ }
+
public function testAsk()
{
$dialog = new DialogHelper();
diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php
index 531d6a80dc..b5560263f6 100644
--- a/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php
@@ -79,8 +79,18 @@ class ProgressHelperTest extends \PHPUnit_Framework_TestCase
return new StreamOutput(fopen('php://memory', 'r+', false));
}
+ protected $lastMessagesLength;
+
protected function generateOutput($expected)
{
- return str_repeat("\x08", 80).$expected.str_repeat(' ', 80 - strlen($expected)).str_repeat("\x08", 80 - strlen($expected));
+ $expectedout = $expected;
+
+ if ($this->lastMessagesLength !== null) {
+ $expectedout = str_repeat("\x20", $this->lastMessagesLength)."\x0D".$expected;
+ }
+
+ $this->lastMessagesLength = strlen($expected);
+
+ return "\x0D".$expectedout;
}
}
diff --git a/src/Symfony/Component/CssSelector/Node/FunctionNode.php b/src/Symfony/Component/CssSelector/Node/FunctionNode.php
index b392b27d07..d645c20aa1 100644
--- a/src/Symfony/Component/CssSelector/Node/FunctionNode.php
+++ b/src/Symfony/Component/CssSelector/Node/FunctionNode.php
@@ -209,7 +209,6 @@ class FunctionNode implements NodeInterface
$xpath->addCondition(sprintf('contains(string(.), %s)', XPathExpr::xpathLiteral($expr)));
// FIXME: Currently case insensitive matching doesn't seem to be happening
-
return $xpath;
}
@@ -264,7 +263,6 @@ class FunctionNode implements NodeInterface
if (false === strpos($s, 'n')) {
// Just a b
-
return array(0, intval((string) $s));
}
diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
index 8f5d6b32c8..686bd5656f 100644
--- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md
+++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md
@@ -1,6 +1,14 @@
CHANGELOG
=========
+2.2.0
+-----
+
+ * added an Extension base class with sensible defaults to be used in conjunction
+ with the Config component.
+ * added PrependExtensionInterface (to be able to allow extensions to prepend
+ application configuration settings for any Bundle)
+
2.1.0
-----
@@ -12,4 +20,3 @@ CHANGELOG
(this includes dumped containers)
* [BC BREAK] fixed unescaping of class arguments, method
ParameterBag::unescapeValue() was made public
-
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php
index a9beb5b9e2..d2c98314e2 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php
@@ -29,6 +29,12 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface
$definitions = $container->getDefinitions();
$aliases = $container->getAliases();
+ foreach ($container->getExtensions() as $extension) {
+ if ($extension instanceof PrependExtensionInterface) {
+ $extension->prepend($container);
+ }
+ }
+
foreach ($container->getExtensions() as $name => $extension) {
if (!$config = $container->getExtensionConfig($name)) {
// this extension was not called
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PrependExtensionInterface.php b/src/Symfony/Component/DependencyInjection/Compiler/PrependExtensionInterface.php
new file mode 100644
index 0000000000..bba1b6cd46
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Compiler/PrependExtensionInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+interface PrependExtensionInterface
+{
+ /**
+ * Allow an extension to prepend the extension configurations.
+ *
+ * @param ContainerBuilder $container
+ */
+ public function prepend(ContainerBuilder $container);
+}
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 30514cc55d..99d49eded9 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -159,11 +159,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
/**
* Sets the resources for this configuration.
- *
+ *
* @param ResourceInterface[] $resources An array of resources
- *
+ *
* @return ContainerBuilder The current instance
- *
+ *
* @api
*/
public function setResources(array $resources)
@@ -465,6 +465,21 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $this->extensionConfigs[$name];
}
+ /**
+ * Prepends a config array to the configs of the given extension.
+ *
+ * @param string $name The name of the extension
+ * @param array $config The config to set
+ */
+ public function prependExtensionConfig($name, array $config)
+ {
+ if (!isset($this->extensionConfigs[$name])) {
+ $this->extensionConfigs[$name] = array();
+ }
+
+ array_unshift($this->extensionConfigs[$name], $config);
+ }
+
/**
* Compiles the container.
*
diff --git a/src/Symfony/Component/DependencyInjection/Extension/Extension.php b/src/Symfony/Component/DependencyInjection/Extension/Extension.php
new file mode 100644
index 0000000000..b821ea68db
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Extension/Extension.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Extension;
+
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\Definition\Processor;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+/**
+ * Provides useful features shared by many extensions.
+ *
+ * @author Fabien Potencier
+ */
+abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface
+{
+ /**
+ * Returns the base path for the XSD files.
+ *
+ * @return string The XSD base path
+ */
+ public function getXsdValidationBasePath()
+ {
+ return false;
+ }
+
+ /**
+ * Returns the namespace to be used for this extension (XML namespace).
+ *
+ * @return string The XML namespace
+ */
+ public function getNamespace()
+ {
+ return 'http://example.org/schema/dic/'.$this->getAlias();
+ }
+
+ /**
+ * Returns the recommended alias to use in XML.
+ *
+ * This alias is also the mandatory prefix to use when using YAML.
+ *
+ * This convention is to remove the "Extension" postfix from the class
+ * name and then lowercase and underscore the result. So:
+ *
+ * AcmeHelloExtension
+ *
+ * becomes
+ *
+ * acme_hello
+ *
+ * This can be overridden in a sub-class to specify the alias manually.
+ *
+ * @return string The alias
+ *
+ * @throws \BadMethodCallException When the extension name does not follow conventions
+ */
+ public function getAlias()
+ {
+ $className = get_class($this);
+ if (substr($className, -9) != 'Extension') {
+ throw new \BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.');
+ }
+ $classBaseName = substr(strrchr($className, '\\'), 1, -9);
+
+ return Container::underscore($classBaseName);
+ }
+
+ final protected function processConfiguration(ConfigurationInterface $configuration, array $configs, $normalizeKeys = true)
+ {
+ $processor = new Processor();
+
+ return $processor->processConfiguration($configuration, $configs, $normalizeKeys);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getConfiguration(array $config, ContainerBuilder $container)
+ {
+ $reflected = new \ReflectionClass($this);
+ $namespace = $reflected->getNamespaceName();
+
+ $class = $namespace . '\\Configuration';
+ if (class_exists($class)) {
+ $r = new \ReflectionClass($class);
+ $container->addResource(new FileResource($r->getFileName()));
+
+ if (!method_exists($class, '__construct')) {
+ $configuration = new $class();
+
+ return $configuration;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
index 6f2c8eba1a..f8f4336736 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Alias;
@@ -202,33 +203,17 @@ class XmlFileLoader extends FileLoader
*
* @return SimpleXMLElement
*
- * @throws \InvalidArgumentException When loading of XML file returns error
+ * @throws InvalidArgumentException When loading of XML file returns error
*/
protected function parseFile($file)
{
- $internalErrors = libxml_use_internal_errors(true);
- $disableEntities = libxml_disable_entity_loader(true);
- libxml_clear_errors();
-
- $dom = new \DOMDocument();
- $dom->validateOnParse = true;
- if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
- libxml_disable_entity_loader($disableEntities);
-
- throw new InvalidArgumentException(implode("\n", $this->getXmlErrors($internalErrors)));
- }
- $dom->normalizeDocument();
-
- libxml_use_internal_errors($internalErrors);
- libxml_disable_entity_loader($disableEntities);
-
- foreach ($dom->childNodes as $child) {
- if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
- throw new InvalidArgumentException('Document types are not allowed.');
- }
+ try {
+ $dom = XmlUtils::loadFile($file, array($this, 'validateSchema'));
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
- $this->validate($dom, $file);
+ $this->validateExtensions($dom, $file);
return simplexml_import_dom($dom, 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement');
}
@@ -285,28 +270,14 @@ class XmlFileLoader extends FileLoader
}
}
- /**
- * Validates an XML document.
- *
- * @param \DOMDocument $dom
- * @param string $file
- */
- private function validate(\DOMDocument $dom, $file)
- {
- $this->validateSchema($dom, $file);
- $this->validateExtensions($dom, $file);
- }
-
/**
* Validates a documents XML schema.
*
* @param \DOMDocument $dom
- * @param string $file
*
- * @throws RuntimeException When extension references a non-existent XSD file
- * @throws InvalidArgumentException When xml doesn't validate its xsd schema
+ * @throws RuntimeException When extension references a non-existent XSD file
*/
- private function validateSchema(\DOMDocument $dom, $file)
+ public function validateSchema(\DOMDocument $dom)
{
$schemaLocations = array('http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd'));
@@ -360,18 +331,13 @@ $imports
EOF
;
- $current = libxml_use_internal_errors(true);
- libxml_clear_errors();
-
$valid = @$dom->schemaValidateSource($source);
foreach ($tmpfiles as $tmpfile) {
@unlink($tmpfile);
}
- if (!$valid) {
- throw new InvalidArgumentException(implode("\n", $this->getXmlErrors($current)));
- }
- libxml_use_internal_errors($current);
+
+ return $valid;
}
/**
@@ -403,33 +369,6 @@ EOF
}
}
- /**
- * Returns an array of XML errors.
- *
- * @param Boolean $internalErrors
- *
- * @return array
- */
- private function getXmlErrors($internalErrors)
- {
- $errors = array();
- foreach (libxml_get_errors() as $error) {
- $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
- LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
- $error->code,
- trim($error->message),
- $error->file ? $error->file : 'n/a',
- $error->line,
- $error->column
- );
- }
-
- libxml_clear_errors();
- libxml_use_internal_errors($internalErrors);
-
- return $errors;
- }
-
/**
* Loads from an extension.
*
@@ -472,50 +411,6 @@ EOF
*/
public static function convertDomElementToArray(\DomElement $element)
{
- $empty = true;
- $config = array();
- foreach ($element->attributes as $name => $node) {
- $config[$name] = SimpleXMLElement::phpize($node->value);
- $empty = false;
- }
-
- $nodeValue = false;
- foreach ($element->childNodes as $node) {
- if ($node instanceof \DOMText) {
- if (trim($node->nodeValue)) {
- $nodeValue = trim($node->nodeValue);
- $empty = false;
- }
- } elseif (!$node instanceof \DOMComment) {
- if ($node instanceof \DOMElement && '_services' === $node->nodeName) {
- $value = new Reference($node->getAttribute('id'));
- } else {
- $value = static::convertDomElementToArray($node);
- }
-
- $key = $node->localName;
- if (isset($config[$key])) {
- if (!is_array($config[$key]) || !is_int(key($config[$key]))) {
- $config[$key] = array($config[$key]);
- }
- $config[$key][] = $value;
- } else {
- $config[$key] = $value;
- }
-
- $empty = false;
- }
- }
-
- if (false !== $nodeValue) {
- $value = SimpleXMLElement::phpize($nodeValue);
- if (count($config)) {
- $config['value'] = $value;
- } else {
- $config = $value;
- }
- }
-
- return !$empty ? $config : null;
+ return XmlUtils::convertDomElementToArray($element);
}
}
diff --git a/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php b/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php
index d154602fd3..cc5e311987 100644
--- a/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php
+++ b/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\DependencyInjection;
+use Symfony\Component\Config\Util\XmlUtils;
+
/**
* SimpleXMLElement class.
*
@@ -101,27 +103,6 @@ class SimpleXMLElement extends \SimpleXMLElement
*/
public static function phpize($value)
{
- $value = (string) $value;
- $lowercaseValue = strtolower($value);
-
- switch (true) {
- case 'null' === $lowercaseValue:
- return null;
- case ctype_digit($value):
- $raw = $value;
- $cast = intval($value);
-
- return '0' == $value[0] ? octdec($value) : (((string) $raw == (string) $cast) ? $cast : $raw);
- case 'true' === $lowercaseValue:
- return true;
- case 'false' === $lowercaseValue:
- return false;
- case is_numeric($value):
- return '0x' == $value[0].$value[1] ? hexdec($value) : floatval($value);
- case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $value):
- return floatval(str_replace(',', '', $value));
- default:
- return $value;
- }
+ return XmlUtils::phpize($value);
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
index c9e6b07847..66658efdeb 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
@@ -548,6 +548,28 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$container->compile();
$container->setDefinition('a', new Definition());
}
+
+ /**
+ * @covers Symfony\Component\DependencyInjection\ContainerBuilder::getExtensionConfig
+ * @covers Symfony\Component\DependencyInjection\ContainerBuilder::prependExtensionConfig
+ */
+ public function testExtensionConfig()
+ {
+ $container = new ContainerBuilder();
+
+ $configs = $container->getExtensionConfig('foo');
+ $this->assertEmpty($configs);
+
+ $first = array('foo' => 'bar');
+ $container->prependExtensionConfig('foo', $first);
+ $configs = $container->getExtensionConfig('foo');
+ $this->assertEquals(array($first), $configs);
+
+ $second = array('ding' => 'dong');
+ $container->prependExtensionConfig('foo', $second);
+ $configs = $container->getExtensionConfig('foo');
+ $this->assertEquals(array($second, $first), $configs);
+ }
}
class FooClass {}
diff --git a/src/Symfony/Component/Filesystem/CHANGELOG.md b/src/Symfony/Component/Filesystem/CHANGELOG.md
index 769a1e59cb..d20ba07cdc 100644
--- a/src/Symfony/Component/Filesystem/CHANGELOG.md
+++ b/src/Symfony/Component/Filesystem/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+2.2.0
+-----
+
+ * added a delete option for the mirror() method
+
2.1.0
-----
diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php
index 23c58721fe..86709c3e4b 100644
--- a/src/Symfony/Component/Filesystem/Filesystem.php
+++ b/src/Symfony/Component/Filesystem/Filesystem.php
@@ -336,11 +336,30 @@ class Filesystem
* Valid options are:
* - $options['override'] Whether to override an existing file on copy or not (see copy())
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
+ * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array())
{
+ $targetDir = rtrim($targetDir, '/\\');
+ $originDir = rtrim($originDir, '/\\');
+
+ // Iterate in destination folder to remove obsolete entries
+ if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
+ $deleteIterator = $iterator;
+ if (null === $deleteIterator) {
+ $flags = \FilesystemIterator::SKIP_DOTS;
+ $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
+ }
+ foreach ($deleteIterator as $file) {
+ $origin = str_replace($targetDir, $originDir, $file->getPathname());
+ if (!$this->exists($origin)) {
+ $this->remove($file);
+ }
+ }
+ }
+
$copyOnWindows = false;
if (isset($options['copy_on_windows']) && !function_exists('symlink')) {
$copyOnWindows = $options['copy_on_windows'];
@@ -351,16 +370,13 @@ class Filesystem
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
}
- $targetDir = rtrim($targetDir, '/\\');
- $originDir = rtrim($originDir, '/\\');
-
foreach ($iterator as $file) {
$target = str_replace($originDir, $targetDir, $file->getPathname());
if ($copyOnWindows) {
if (is_link($file) || is_file($file)) {
$this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
- } else if (is_dir($file)) {
+ } elseif (is_dir($file)) {
$this->mkdir($target);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file));
@@ -368,9 +384,9 @@ class Filesystem
} else {
if (is_link($file)) {
$this->symlink($file, $target);
- } else if (is_dir($file)) {
+ } elseif (is_dir($file)) {
$this->mkdir($target);
- } else if (is_file($file)) {
+ } elseif (is_file($file)) {
$this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file));
diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
index 4a6b8df23c..8d2e769179 100644
--- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
+++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
@@ -798,6 +798,24 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase
$this->assertTrue(is_dir($targetPath.'directory'));
$this->assertFileEquals($file1, $targetPath.'directory'.DIRECTORY_SEPARATOR.'file1');
$this->assertFileEquals($file2, $targetPath.'file2');
+
+ $this->filesystem->remove($file1);
+
+ $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => false));
+ $this->assertTrue($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1'));
+
+ $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true));
+ $this->assertFalse($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1'));
+
+ file_put_contents($file1, 'FILE1');
+
+ $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true));
+ $this->assertTrue($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1'));
+
+ $this->filesystem->remove($directory);
+ $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true));
+ $this->assertFalse($this->filesystem->exists($targetPath.'directory'));
+ $this->assertFalse($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1'));
}
public function testMirrorCopiesLinks()
diff --git a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php
index 38ac2835fd..839a601638 100644
--- a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php
+++ b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php
@@ -34,6 +34,22 @@ abstract class AbstractAdapter implements AdapterInterface
protected $paths = array();
protected $notPaths = array();
+ private static $areSupported = array();
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isSupported()
+ {
+ $name = $this->getName();
+
+ if (!array_key_exists($name, self::$areSupported)) {
+ self::$areSupported[$name] = $this->canBeUsed();
+ }
+
+ return self::$areSupported[$name];
+ }
+
/**
* {@inheritdoc}
*/
@@ -193,4 +209,17 @@ abstract class AbstractAdapter implements AdapterInterface
return $this;
}
+
+ /**
+ * Returns whether the adapter is supported in the current environment.
+ *
+ * This method should be implemented in all adapters. Do not implement
+ * isSupported in the adapters as the generic implementation provides a cache
+ * layer.
+ *
+ * @see isSupported
+ *
+ * @return Boolean Whether the adapter is supported
+ */
+ abstract protected function canBeUsed();
}
diff --git a/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php
index 9cde873a64..4af3e8c75c 100644
--- a/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php
+++ b/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php
@@ -59,11 +59,11 @@ abstract class AbstractFindAdapter extends AbstractAdapter
$find->add('-follow');
}
- $find->add('-mindepth')->add($this->minDepth+1);
+ $find->add('-mindepth')->add($this->minDepth + 1);
// warning! INF < INF => true ; INF == INF => false ; INF === INF => true
// https://bugs.php.net/bug.php?id=9118
if (INF !== $this->maxDepth) {
- $find->add('-maxdepth')->add($this->maxDepth+1);
+ $find->add('-maxdepth')->add($this->maxDepth + 1);
}
if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
@@ -118,13 +118,14 @@ abstract class AbstractFindAdapter extends AbstractAdapter
/**
* {@inheritdoc}
*/
- public function isSupported()
+ protected function canBeUsed()
{
return $this->shell->testCommand('find');
}
/**
* @param Command $command
+ * @param string $dir
*
* @return Command
*/
@@ -140,7 +141,7 @@ abstract class AbstractFindAdapter extends AbstractAdapter
/**
* @param Command $command
* @param string[] $names
- * @param bool $not
+ * @param Boolean $not
*/
private function buildNamesFiltering(Command $command, array $names, $not = false)
{
@@ -183,7 +184,8 @@ abstract class AbstractFindAdapter extends AbstractAdapter
* @param Command $command
* @param string $dir
* @param string[] $paths
- * @param bool $not
+ * @param Boolean $not
+ *
* @return void
*/
private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
@@ -226,33 +228,23 @@ abstract class AbstractFindAdapter extends AbstractAdapter
foreach ($sizes as $i => $size) {
$command->add($i > 0 ? '-and' : null);
- if ('<=' === $size->getOperator()) {
- $command->add('-size -'.($size->getTarget()+1).'c');
- continue;
+ switch ($size->getOperator()) {
+ case '<=':
+ $command->add('-size -' . ($size->getTarget() + 1) . 'c');
+ break;
+ case '>=':
+ $command->add('-size +'. ($size->getTarget() - 1) . 'c');
+ break;
+ case '>':
+ $command->add('-size +' . $size->getTarget() . 'c');
+ break;
+ case '!=':
+ $command->add('-size -' . $size->getTarget() . 'c');
+ $command->add('-size +' . $size->getTarget() . 'c');
+ case '<':
+ default:
+ $command->add('-size -' . $size->getTarget() . 'c');
}
-
- if ('<' === $size->getOperator()) {
- $command->add('-size -'.$size->getTarget().'c');
- continue;
- }
-
- if ('>=' === $size->getOperator()) {
- $command->add('-size +'.($size->getTarget()-1).'c');
- continue;
- }
-
- if ('>' === $size->getOperator()) {
- $command->add('-size +'.$size->getTarget().'c');
- continue;
- }
-
- if ('!=' === $size->getOperator()) {
- $command->add('-size -'.$size->getTarget().'c');
- $command->add('-size +'.$size->getTarget().'c');
- continue;
- }
-
- $command->add('-size '.$size->getTarget().'c');
}
}
@@ -265,7 +257,7 @@ abstract class AbstractFindAdapter extends AbstractAdapter
foreach ($dates as $i => $date) {
$command->add($i > 0 ? '-and' : null);
- $mins = (int) round((time()-$date->getTarget())/60);
+ $mins = (int) round((time()-$date->getTarget()) / 60);
if (0 > $mins) {
// mtime is in the future
@@ -274,39 +266,30 @@ abstract class AbstractFindAdapter extends AbstractAdapter
return;
}
- if ('<=' === $date->getOperator()) {
- $command->add('-mmin +'.($mins-1));
- continue;
+ switch ($date->getOperator()) {
+ case '<=':
+ $command->add('-mmin +' . ($mins - 1));
+ break;
+ case '>=':
+ $command->add('-mmin -' . ($mins + 1));
+ break;
+ case '>':
+ $command->add('-mmin -' . $mins);
+ break;
+ case '!=':
+ $command->add('-mmin +' . $mins.' -or -mmin -' . $mins);
+ break;
+ case '<':
+ default:
+ $command->add('-mmin +' . $mins);
}
-
- if ('<' === $date->getOperator()) {
- $command->add('-mmin +'.$mins);
- continue;
- }
-
- if ('>=' === $date->getOperator()) {
- $command->add('-mmin -'.($mins+1));
- continue;
- }
-
- if ('>' === $date->getOperator()) {
- $command->add('-mmin -'.$mins);
- continue;
- }
-
- if ('!=' === $date->getOperator()) {
- $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
- continue;
- }
-
- $command->add('-mmin '.$mins);
}
}
/**
* @param Command $command
* @param array $contains
- * @param bool $not
+ * @param Boolean $not
*/
private function buildContentFiltering(Command $command, array $contains, $not = false)
{
@@ -323,8 +306,9 @@ abstract class AbstractFindAdapter extends AbstractAdapter
}
/**
- * @param \Symfony\Component\Finder\Shell\Command $command
- * @param string $sort
+ * @param Command $command
+ * @param string $sort
+ *
* @throws \InvalidArgumentException
*/
private function buildSorting(Command $command, $sort)
@@ -332,6 +316,7 @@ abstract class AbstractFindAdapter extends AbstractAdapter
switch ($sort) {
case SortableIterator::SORT_BY_NAME:
$command->ins('sort')->add('| sort');
+
return;
case SortableIterator::SORT_BY_TYPE:
$format = '%y';
diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php
index 829297b3ec..f28ffb3189 100644
--- a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php
+++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php
@@ -17,14 +17,14 @@ namespace Symfony\Component\Finder\Adapter;
interface AdapterInterface
{
/**
- * @param bool $followLinks
+ * @param Boolean $followLinks
*
* @return AdapterInterface Current instance
*/
public function setFollowLinks($followLinks);
/**
- * @param int $mode
+ * @param integer $mode
*
* @return AdapterInterface Current instance
*/
@@ -94,14 +94,14 @@ interface AdapterInterface
public function setFilters(array $filters);
/**
- * @param \Closure|int $sort
+ * @param \Closure|integer $sort
*
* @return AdapterInterface Current instance
*/
public function setSort($sort);
/**
- * @param array $path
+ * @param array $paths
*
* @return AdapterInterface Current instance
*/
@@ -124,7 +124,7 @@ interface AdapterInterface
/**
* Tests adapter support for current platform.
*
- * @return bool
+ * @return Boolean
*/
public function isSupported();
diff --git a/src/Symfony/Component/Finder/Adapter/BsdFindAdapter.php b/src/Symfony/Component/Finder/Adapter/BsdFindAdapter.php
index 12f2f69768..cb4e9d872c 100644
--- a/src/Symfony/Component/Finder/Adapter/BsdFindAdapter.php
+++ b/src/Symfony/Component/Finder/Adapter/BsdFindAdapter.php
@@ -11,7 +11,6 @@
namespace Symfony\Component\Finder\Adapter;
-use Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Shell\Command;
@@ -22,14 +21,6 @@ use Symfony\Component\Finder\Shell\Command;
*/
class BsdFindAdapter extends AbstractFindAdapter
{
- /**
- * {@inheritdoc}
- */
- public function isSupported()
- {
- return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::isSupported();
- }
-
/**
* {@inheritdoc}
*/
@@ -38,12 +29,25 @@ class BsdFindAdapter extends AbstractFindAdapter
return 'bsd_find';
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed();
+ }
+
/**
* {@inheritdoc}
*/
protected function buildFormatSorting(Command $command, $format)
{
- $command->get('find')->add('-print0 | xargs -0 stat -f')->arg($format.' %h/%f\\n')
- ->add('| sort | cut')->arg('-d ')->arg('-f2-');
+ $command
+ ->get('find')
+ ->add('-print0 | xargs -0 stat -f')
+ ->arg($format.' %h/%f\\n')
+ ->add('| sort | cut')
+ ->arg('-d ')
+ ->arg('-f2-');
}
}
diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
index bd903581d2..6f5a4eb212 100644
--- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
+++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php
@@ -11,7 +11,6 @@
namespace Symfony\Component\Finder\Adapter;
-use Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Shell\Shell;
use Symfony\Component\Finder\Shell\Command;
@@ -22,14 +21,6 @@ use Symfony\Component\Finder\Shell\Command;
*/
class GnuFindAdapter extends AbstractFindAdapter
{
- /**
- * {@inheritdoc}
- */
- public function isSupported()
- {
- return $this->shell->getType() === Shell::TYPE_UNIX && parent::isSupported();
- }
-
/**
* {@inheritdoc}
*/
@@ -43,8 +34,22 @@ class GnuFindAdapter extends AbstractFindAdapter
*/
protected function buildFormatSorting(Command $command, $format)
{
- $command->get('find')->add('-printf')->arg($format.' %h/%f\\n')
- ->add('| sort | cut')->arg('-d ')->arg('-f2-');
+ $command
+ ->get('find')
+ ->add('-printf')
+ ->arg($format.' %h/%f\\n')
+ ->add('| sort | cut')
+ ->arg('-d ')
+ ->arg('-f2-')
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBeUsed()
+ {
+ return $this->shell->getType() === Shell::TYPE_UNIX && parent::canBeUsed();
}
/**
diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php
index 3607165f9b..dfc842f6b5 100644
--- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php
+++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php
@@ -83,16 +83,16 @@ class PhpAdapter extends AbstractAdapter
/**
* {@inheritdoc}
*/
- public function isSupported()
+ public function getName()
{
- return true;
+ return 'php';
}
/**
* {@inheritdoc}
*/
- public function getName()
+ protected function canBeUsed()
{
- return 'php';
+ return true;
}
}
diff --git a/src/Symfony/Component/Finder/Exception/ExceptionInterface.php b/src/Symfony/Component/Finder/Exception/ExceptionInterface.php
index 86d391b91e..b0337c1aaa 100644
--- a/src/Symfony/Component/Finder/Exception/ExceptionInterface.php
+++ b/src/Symfony/Component/Finder/Exception/ExceptionInterface.php
@@ -10,5 +10,5 @@ interface ExceptionInterface
/**
* @return \Symfony\Component\Finder\Adapter\AdapterInterface
*/
- function getAdapter();
+ public function getAdapter();
}
diff --git a/src/Symfony/Component/Finder/Expression/Expression.php b/src/Symfony/Component/Finder/Expression/Expression.php
index b08be159a5..9c87ddf3da 100644
--- a/src/Symfony/Component/Finder/Expression/Expression.php
+++ b/src/Symfony/Component/Finder/Expression/Expression.php
@@ -41,7 +41,7 @@ class Expression implements ValueInterface
{
try {
$this->value = Regex::create($expr);
- } catch(\InvalidArgumentException $e) {
+ } catch (\InvalidArgumentException $e) {
$this->value = new Glob($expr);
}
}
diff --git a/src/Symfony/Component/Finder/Expression/ValueInterface.php b/src/Symfony/Component/Finder/Expression/ValueInterface.php
index 37342b02e9..34ce0e7ce4 100644
--- a/src/Symfony/Component/Finder/Expression/ValueInterface.php
+++ b/src/Symfony/Component/Finder/Expression/ValueInterface.php
@@ -21,28 +21,28 @@ interface ValueInterface
*
* @return string
*/
- function render();
+ public function render();
/**
* Renders string representation of pattern.
*
* @return string
*/
- function renderPattern();
+ public function renderPattern();
/**
* Returns value case sensitivity.
*
* @return bool
*/
- function isCaseSensitive();
+ public function isCaseSensitive();
/**
* Returns expression type.
*
* @return int
*/
- function getType();
+ public function getType();
/**
* @param string $expr
diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php
index cbaa9acaa3..aa32cd4e33 100644
--- a/src/Symfony/Component/Finder/Finder.php
+++ b/src/Symfony/Component/Finder/Finder.php
@@ -65,9 +65,11 @@ class Finder implements \IteratorAggregate, \Countable
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
- $this->addAdapter(new GnuFindAdapter());
- $this->addAdapter(new BsdFindAdapter());
- $this->addAdapter(new PhpAdapter(), -50);
+ $this
+ ->addAdapter(new GnuFindAdapter())
+ ->addAdapter(new BsdFindAdapter())
+ ->addAdapter(new PhpAdapter(), -50)
+ ;
}
/**
@@ -86,7 +88,7 @@ class Finder implements \IteratorAggregate, \Countable
* Registers a finder engine implementation.
*
* @param AdapterInterface $adapter An adapter instance
- * @param int $priority Highest is selected first
+ * @param integer $priority Highest is selected first
*
* @return Finder The current Finder instance
*/
@@ -418,9 +420,20 @@ class Finder implements \IteratorAggregate, \Countable
return $this;
}
+ /**
+ * Adds VCS patterns.
+ *
+ * @see ignoreVCS
+ *
+ * @param string|string[] $pattern VCS patterns to ignore
+ */
public static function addVCSPattern($pattern)
{
- self::$vcsPatterns[] = $pattern;
+ foreach ((array) $pattern as $p) {
+ self::$vcsPatterns[] = $p;
+ }
+
+ self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
@@ -615,8 +628,8 @@ class Finder implements \IteratorAggregate, \Countable
*/
public function getIterator()
{
- if (0 === count($this->dirs)) {
- throw new \LogicException('You must call the in() method before iterating over a Finder.');
+ if (0 === count($this->dirs) && 0 === count($this->iterators)) {
+ throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === count($this->dirs) && 0 === count($this->iterators)) {
@@ -641,6 +654,10 @@ class Finder implements \IteratorAggregate, \Countable
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @param mixed $iterator
+ *
+ * @return Finder The finder
+ *
+ * @throws \InvalidArgumentException When the given argument is not iterable.
*/
public function append($iterator)
{
@@ -657,6 +674,8 @@ class Finder implements \IteratorAggregate, \Countable
} else {
throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
}
+
+ return $this;
}
/**
@@ -685,6 +704,8 @@ class Finder implements \IteratorAggregate, \Countable
* @param $dir
*
* @return \Iterator
+ *
+ * @throws \RuntimeException When none of the adapters are supported
*/
private function searchInDirectory($dir)
{
@@ -697,16 +718,12 @@ class Finder implements \IteratorAggregate, \Countable
}
foreach ($this->adapters as $adapter) {
- if (!$adapter['adapter']->isSupported()) {
- continue;
- }
-
- try {
- return $this
- ->buildAdapter($adapter['adapter'])
- ->searchInDirectory($dir);
- } catch(ExceptionInterface $e) {
- continue;
+ if ($adapter['adapter']->isSupported()) {
+ try {
+ return $this
+ ->buildAdapter($adapter['adapter'])
+ ->searchInDirectory($dir);
+ } catch (ExceptionInterface $e) {}
}
}
diff --git a/src/Symfony/Component/Finder/SplFileInfo.php b/src/Symfony/Component/Finder/SplFileInfo.php
index ee3d8ecf43..e9d4f7b72f 100644
--- a/src/Symfony/Component/Finder/SplFileInfo.php
+++ b/src/Symfony/Component/Finder/SplFileInfo.php
@@ -62,10 +62,14 @@ class SplFileInfo extends \SplFileInfo
*/
public function getContents()
{
- $file = new \SplFileObject($this->getRealpath(), 'rb');
- ob_start();
- $file->fpassthru();
+ $level = error_reporting(0);
+ $content = file_get_contents($this->getRealpath());
+ error_reporting($level);
+ if (false === $content) {
+ $error = error_get_last();
+ throw new \RuntimeException($error['message']);
+ }
- return ob_get_clean();
+ return $content;
}
}
diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php
index c7143bfd76..0cbae14b88 100644
--- a/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php
+++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php
@@ -42,16 +42,16 @@ class DummyAdapter extends AbstractAdapter
/**
* {@inheritdoc}
*/
- public function isSupported()
+ public function getName()
{
- return true;
+ return 'yes';
}
/**
* {@inheritdoc}
*/
- public function getName()
+ protected function canBeUsed()
{
- return 'yes';
+ return true;
}
}
diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php
index 00fc022fdf..6e6ed24b61 100644
--- a/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php
+++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php
@@ -30,16 +30,16 @@ class FailingAdapter extends AbstractAdapter
/**
* {@inheritdoc}
*/
- public function isSupported()
+ public function getName()
{
- return true;
+ return 'failing';
}
/**
* {@inheritdoc}
*/
- function getName()
+ protected function canBeUsed()
{
- return 'failing';
+ return true;
}
}
diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php
index 2321195e94..5a260b0dfc 100644
--- a/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php
+++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php
@@ -42,16 +42,16 @@ class NamedAdapter extends AbstractAdapter
/**
* {@inheritdoc}
*/
- public function isSupported()
+ public function getName()
{
- return true;
+ return $this->name;
}
/**
* {@inheritdoc}
*/
- function getName()
+ protected function canBeUsed()
{
- return $this->name;
+ return true;
}
}
diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php
index d02a13e032..1f91b98a60 100644
--- a/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php
+++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php
@@ -29,16 +29,16 @@ class UnsupportedAdapter extends AbstractAdapter
/**
* {@inheritdoc}
*/
- public function isSupported()
+ public function getName()
{
- return false;
+ return 'unsupported';
}
/**
* {@inheritdoc}
*/
- function getName()
+ protected function canBeUsed()
{
- return 'unsupported';
+ return false;
}
}
diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php
index 9b8384b5cc..143cff2fde 100644
--- a/src/Symfony/Component/Finder/Tests/FinderTest.php
+++ b/src/Symfony/Component/Finder/Tests/FinderTest.php
@@ -26,10 +26,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
self::$tmpDir = sys_get_temp_dir().'/symfony2_finder';
}
- /**
- * @dataProvider getAdaptersTestData
- */
- public function testCreate($adapter)
+ public function testCreate()
{
$this->assertInstanceOf('Symfony\Component\Finder\Finder', Finder::create());
}
@@ -209,7 +206,6 @@ class FinderTest extends Iterator\RealIteratorTestCase
$finder = $this->buildFinder($adapter);
$this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false));
$this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator());
-
}
/**
@@ -356,9 +352,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
*/
public function testRelativePath($adapter)
{
- $finder = $this->buildFinder($adapter);
-
- $finder->in(self::$tmpDir);
+ $finder = $this->buildFinder($adapter)->in(self::$tmpDir);
$paths = array();
@@ -379,9 +373,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
*/
public function testRelativePathname($adapter)
{
- $finder = $this->buildFinder($adapter);
-
- $finder->in(self::$tmpDir)->sortByName();
+ $finder = $this->buildFinder($adapter)->in(self::$tmpDir)->sortByName();
$paths = array();
@@ -408,7 +400,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
$finder1 = $this->buildFinder($adapter);
$finder1->directories()->in(self::$tmpDir);
- $finder->append($finder1);
+ $finder = $finder->append($finder1);
$this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator());
}
@@ -426,10 +418,30 @@ class FinderTest extends Iterator\RealIteratorTestCase
$this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator());
}
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAppendReturnsAFinder($adapter)
+ {
+ $this->assertInstanceOf('Symfony\\Component\\Finder\\Finder', $this->buildFinder($adapter)->append(array()));
+ }
+
+ /**
+ * @dataProvider getAdaptersTestData
+ */
+ public function testAppendDoesNotRequireIn($adapter)
+ {
+ $finder = $this->buildFinder($adapter);
+ $finder->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo');
+
+ $finder1 = Finder::create()->append($finder);
+
+ $this->assertIterator(iterator_to_array($finder->getIterator()), $finder1->getIterator());
+ }
+
public function testCountDirectories()
{
- $finder = new Finder();
- $directory = $finder->directories()->in(self::$tmpDir);
+ $directory = Finder::create()->directories()->in(self::$tmpDir);
$i = 0;
foreach ($directory as $dir) {
@@ -441,8 +453,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
public function testCountFiles()
{
- $finder = new Finder();
- $files = $finder->files()->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $files = Finder::create()->files()->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures');
$i = 0;
foreach ($files as $file) {
@@ -452,17 +463,13 @@ class FinderTest extends Iterator\RealIteratorTestCase
$this->assertCount($i, $files);
}
+ /**
+ * @expectedException \LogicException
+ */
public function testCountWithoutIn()
{
- $finder = new Finder();
- $finder->files();
-
- try {
- count($finder);
- $this->fail('Countable makes use of the getIterator command');
- } catch (\Exception $e) {
- $this->assertInstanceOf('LogicException', $e, '->getIterator() throws \LogicException when no logic has been entered');
- }
+ $finder = Finder::create()->files();
+ count($finder);
}
protected function toAbsolute($files)
@@ -619,37 +626,10 @@ class FinderTest extends Iterator\RealIteratorTestCase
return $this->buildTestData($tests);
}
- private function buildFinder(Adapter\AdapterInterface $adapter)
- {
- return Finder::create()
- ->removeAdapters()
- ->addAdapter($adapter);
- }
-
- private function getValidAdapters()
- {
- return array_filter(
- array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()),
- function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); }
- );
- }
-
- private function buildTestData(array $tests)
- {
- $data = array();
- foreach ($this->getValidAdapters() as $adapter) {
- foreach ($tests as $test) {
- $data[] = array_merge(array($adapter), $test);
- }
- }
-
- return $data;
- }
-
/**
* @dataProvider getTestPathData
*/
- public function testPath(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, $expected)
+ public function testPath(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, array $expected)
{
$finder = $this->buildFinder($adapter);
$finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures')
@@ -700,4 +680,37 @@ class FinderTest extends Iterator\RealIteratorTestCase
return $this->buildTestData($tests);
}
+
+ private function buildTestData(array $tests)
+ {
+ $data = array();
+ foreach ($this->getValidAdapters() as $adapter) {
+ foreach ($tests as $test) {
+ $data[] = array_merge(array($adapter), $test);
+ }
+ }
+
+ return $data;
+ }
+
+ private function buildFinder(Adapter\AdapterInterface $adapter)
+ {
+ return Finder::create()
+ ->removeAdapters()
+ ->addAdapter($adapter);
+ }
+
+ private function getValidAdapters()
+ {
+ return array_filter(
+ array(
+ new Adapter\BsdFindAdapter(),
+ new Adapter\GnuFindAdapter(),
+ new Adapter\PhpAdapter()
+ ),
+ function (Adapter\AdapterInterface $adapter) {
+ return $adapter->isSupported();
+ }
+ );
+ }
}
diff --git a/src/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php
index 1deea0c036..c4b9795918 100644
--- a/src/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php
+++ b/src/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php
@@ -38,7 +38,6 @@ class FilenameFilterIteratorTest extends IteratorTestCase
array(array(), array('/\.php$/'), array('test.py')),
);
}
-
}
class InnerNameIterator extends \ArrayIterator
diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php b/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php
index 50312860bd..a50e4c9fcc 100644
--- a/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php
+++ b/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php
@@ -28,7 +28,6 @@ class MockSplFileInfo extends \SplFileInfo
if (is_string($param)) {
parent::__construct($param);
} elseif (is_array($param)) {
-
$defaults = array(
'name' => 'file.txt',
'contents' => null,
@@ -132,5 +131,4 @@ class MockSplFileInfo extends \SplFileInfo
{
return $this->relativePathname;
}
-
}
diff --git a/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php
index 6a155d4d7c..7278bebadd 100644
--- a/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php
+++ b/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php
@@ -83,4 +83,3 @@ class PathFilterIteratorTest extends IteratorTestCase
}
}
-
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
index fcccfdd0bd..4fbca4b451 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
@@ -261,7 +261,7 @@ class ChoiceList implements ChoiceListInterface
{
// Add choices to the nested buckets
foreach ($choices as $group => $choice) {
- if (!isset($labels[$group])) {
+ if (!array_key_exists($group, $labels)) {
throw new \InvalidArgumentException('The structures of the choices and labels array do not match.');
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
index cc3f9272eb..673fd5fde2 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php
@@ -32,12 +32,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
*
* @see BaseDateTimeTransformer::formats for available format options
*
- * @param string $inputTimezone The name of the input timezone
- * @param string $outputTimezone The name of the output timezone
- * @param integer $dateFormat The date format
- * @param integer $timeFormat The time format
- * @param \IntlDateFormatter $calendar An \IntlDateFormatter instance
- * @param string $pattern A pattern to pass to \IntlDateFormatter
+ * @param string $inputTimezone The name of the input timezone
+ * @param string $outputTimezone The name of the output timezone
+ * @param integer $dateFormat The date format
+ * @param integer $timeFormat The time format
+ * @param integer $calendar One of the \IntlDateFormatter calendar constants
+ * @param string $pattern A pattern to pass to \IntlDateFormatter
*
* @throws UnexpectedTypeException If a format is not supported
* @throws UnexpectedTypeException if a timezone is not a string
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php
index 4062759913..7131ff6519 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php
@@ -53,7 +53,6 @@ class DateTimeToRfc3339Transformer extends BaseDateTimeTransformer
return null;
}
-
$dateTime = new \DateTime($rfc3339);
if ($this->outputTimezone !== $this->inputTimezone) {
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php
index a257fd3ce8..cbaac96859 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php
@@ -22,31 +22,73 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
*/
class DateTimeToStringTransformer extends BaseDateTimeTransformer
{
- private $format;
+ /**
+ * Format used for generating strings
+ * @var string
+ */
+ private $generateFormat;
+
+ /**
+ * Format used for parsing strings
+ *
+ * Different than the {@link $generateFormat} because formats for parsing
+ * support additional characters in PHP that are not supported for
+ * generating strings.
+ *
+ * @var string
+ */
+ private $parseFormat;
+
+ /**
+ * Whether to parse by appending a pipe "|" to the parse format.
+ *
+ * This only works as of PHP 5.3.8.
+ *
+ * @var Boolean
+ */
+ private $parseUsingPipe;
/**
* Transforms a \DateTime instance to a string
*
* @see \DateTime::format() for supported formats
*
- * @param string $inputTimezone The name of the input timezone
- * @param string $outputTimezone The name of the output timezone
- * @param string $format The date format
+ * @param string $inputTimezone The name of the input timezone
+ * @param string $outputTimezone The name of the output timezone
+ * @param string $format The date format
+ * @param Boolean $parseUsingPipe Whether to parse by appending a pipe "|" to the parse format
*
* @throws UnexpectedTypeException if a timezone is not a string
*/
- public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s')
+ public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s', $parseUsingPipe = null)
{
parent::__construct($inputTimezone, $outputTimezone);
- $this->format = $format;
+ $this->generateFormat = $this->parseFormat = $format;
+
+ // The pipe in the parser pattern only works as of PHP 5.3.8
+ $this->parseUsingPipe = null === $parseUsingPipe
+ ? version_compare(phpversion(), '5.3.8', '>=')
+ : $parseUsingPipe;
+
+ // See http://php.net/manual/en/datetime.createfromformat.php
+ // The character "|" in the format makes sure that the parts of a date
+ // that are *not* specified in the format are reset to the corresponding
+ // values from 1970-01-01 00:00:00 instead of the current time.
+ // Without "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 12:32:47",
+ // where the time corresponds to the current server time.
+ // With "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 00:00:00",
+ // which is at least deterministic and thus used here.
+ if ($this->parseUsingPipe && false === strpos($this->parseFormat, '|')) {
+ $this->parseFormat .= '|';
+ }
}
/**
* Transforms a DateTime object into a date string with the configured format
* and timezone
*
- * @param DateTime $value A DateTime object
+ * @param \DateTime $value A DateTime object
*
* @return string A value as produced by PHP's date() function
*
@@ -70,7 +112,7 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
}
- return $value->format($this->format);
+ return $value->format($this->generateFormat);
}
/**
@@ -78,7 +120,7 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer
*
* @param string $value A value as produced by PHP's date() function
*
- * @return \DateTime An instance of \DateTime
+ * @return \DateTime An instance of \DateTime
*
* @throws UnexpectedTypeException if the given value is not a string
* @throws TransformationFailedException if the date could not be parsed
@@ -95,20 +137,89 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer
}
try {
- $dateTime = new \DateTime($value, new \DateTimeZone($this->outputTimezone));
- $lastErrors = \DateTime::getLastErrors();
- if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) {
- throw new \UnexpectedValueException(implode(', ', array_merge(array_values($lastErrors['warnings']), array_values($lastErrors['errors']))));
+ $outputTz = new \DateTimeZone($this->outputTimezone);
+ $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz);
+
+ // On PHP versions < 5.3.8 we need to emulate the pipe operator
+ // and reset parts not given in the format to their equivalent
+ // of the UNIX base timestamp.
+ if (!$this->parseUsingPipe) {
+ list($year, $month, $day, $hour, $minute, $second) = explode('-', $dateTime->format('Y-m-d-H-i-s'));
+
+ // Check which of the date parts are present in the pattern
+ preg_match(
+ '/(' .
+ '(?[djDl])|' .
+ '(?[FMmn])|' .
+ '(?[Yy])|' .
+ '(?[ghGH])|' .
+ '(?i)|' .
+ '(?s)|' .
+ '(?z)|' .
+ '(?U)|' .
+ '[^djDlFMmnYyghGHiszU]' .
+ ')*/',
+ $this->parseFormat,
+ $matches
+ );
+
+ // preg_match() does not guarantee to set all indices, so
+ // set them unless given
+ $matches = array_merge(array(
+ 'day' => false,
+ 'month' => false,
+ 'year' => false,
+ 'hour' => false,
+ 'minute' => false,
+ 'second' => false,
+ 'dayofyear' => false,
+ 'timestamp' => false,
+ ), $matches);
+
+ // Reset all parts that don't exist in the format to the
+ // corresponding part of the UNIX base timestamp
+ if (!$matches['timestamp']) {
+ if (!$matches['dayofyear']) {
+ if (!$matches['day']) {
+ $day = 1;
+ }
+ if (!$matches['month']) {
+ $month = 1;
+ }
+ }
+ if (!$matches['year']) {
+ $year = 1970;
+ }
+ if (!$matches['hour']) {
+ $hour = 0;
+ }
+ if (!$matches['minute']) {
+ $minute = 0;
+ }
+ if (!$matches['second']) {
+ $second = 0;
+ }
+ $dateTime->setDate($year, $month, $day);
+ $dateTime->setTime($hour, $minute, $second);
+ }
}
- // Force value to be in same format as given to transform
- if ($value !== $dateTime->format($this->format)) {
- $dateTime = new \DateTime($dateTime->format($this->format), new \DateTimeZone($this->outputTimezone));
+ $lastErrors = \DateTime::getLastErrors();
+
+ if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) {
+ throw new TransformationFailedException(
+ implode(', ', array_merge(
+ array_values($lastErrors['warnings']),
+ array_values($lastErrors['errors'])
+ ))
+ );
}
if ($this->inputTimezone !== $this->outputTimezone) {
$dateTime->setTimeZone(new \DateTimeZone($this->inputTimezone));
}
+ } catch (TransformationFailedException $e) {
+ throw $e;
} catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index 52a681f269..c659a77f04 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
@@ -180,6 +180,10 @@ class DateType extends AbstractType
);
};
+ $format = function (Options $options) {
+ return $options['widget'] === 'single_text' ? DateType::HTML5_FORMAT : DateType::DEFAULT_FORMAT;
+ };
+
// BC until Symfony 2.3
$modelTimezone = function (Options $options) {
return $options['data_timezone'];
@@ -196,7 +200,7 @@ class DateType extends AbstractType
'days' => range(1, 31),
'widget' => 'choice',
'input' => 'datetime',
- 'format' => self::HTML5_FORMAT,
+ 'format' => $format,
'model_timezone' => $modelTimezone,
'view_timezone' => $viewTimezone,
// Deprecated timezone options
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
index 675eff313f..d6c9932cc4 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
@@ -46,7 +46,8 @@ class FileType extends AbstractType
{
$resolver->setDefaults(array(
'compound' => false,
- 'data_class' => 'Symfony\Component\HttpFoundation\File\File'
+ 'data_class' => 'Symfony\Component\HttpFoundation\File\File',
+ 'empty_data' => null,
));
}
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index 5514fead5e..307db6798f 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -706,7 +706,11 @@ class Form implements \IteratorAggregate, FormInterface
}
}
- return FormUtil::isEmpty($this->modelData) || array() === $this->modelData;
+ return FormUtil::isEmpty($this->modelData) ||
+ // arrays, countables
+ 0 === count($this->modelData) ||
+ // traversables that are not countable
+ ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData));
}
/**
diff --git a/src/Symfony/Component/Form/Guess/Guess.php b/src/Symfony/Component/Form/Guess/Guess.php
index 84407925a7..f24407dd73 100644
--- a/src/Symfony/Component/Form/Guess/Guess.php
+++ b/src/Symfony/Component/Form/Guess/Guess.php
@@ -90,7 +90,7 @@ abstract class Guess
*/
public function __construct($confidence)
{
- if (self::VERY_HIGH_CONFIDENCE !== $confidence && self::HIGH_CONFIDENCE !== $confidence &&
+ if (self::VERY_HIGH_CONFIDENCE !== $confidence && self::HIGH_CONFIDENCE !== $confidence &&
self::MEDIUM_CONFIDENCE !== $confidence && self::LOW_CONFIDENCE !== $confidence) {
throw new \InvalidArgumentException('The confidence should be one of the constants defined in Guess.');
}
diff --git a/src/Symfony/Component/Form/Resources/translations/validators.gl.xlf b/src/Symfony/Component/Form/Resources/translations/validators.gl.xlf
new file mode 100644
index 0000000000..db23fe2be6
--- /dev/null
+++ b/src/Symfony/Component/Form/Resources/translations/validators.gl.xlf
@@ -0,0 +1,19 @@
+
+
+
+
+
+ This form should not contain extra fields.
+ Este formulario non debería conter campos adicionais.
+
+
+ The uploaded file was too large. Please try to upload a smaller file.
+ O arquivo subido é demasiado grande. Por favor, suba un arquivo máis pequeno.
+
+
+ The CSRF token is invalid. Please try to resubmit the form.
+ O token CSRF non é válido. Por favor, probe a enviar novamente o formulario
+
+
+
+
diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
index 576d7890da..ac9b18b065 100644
--- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
@@ -526,6 +526,23 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
);
}
+ public function testLabelIsNotRenderedWhenSetToFalse()
+ {
+ $form = $this->factory->createNamed('name', 'text', null, array(
+ 'label' => false
+ ));
+ $html = $this->renderRow($form->createView());
+
+ $this->assertMatchesXpath($html,
+'/div
+ [
+ ./input[@id="name"]
+ ]
+ [count(//label)=0]
+'
+ );
+ }
+
/**
* @dataProvider themeBlockInheritanceProvider
*/
diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
index 2388ba6b67..96e13d67f3 100644
--- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -811,14 +811,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
[@id="name_date"]
[
./select
- [@id="name_date_year"]
- [./option[@value="2011"][@selected="selected"]]
- /following-sibling::select
[@id="name_date_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
[@id="name_date_day"]
[./option[@value="3"][@selected="selected"]]
+ /following-sibling::select
+ [@id="name_date_year"]
+ [./option[@value="2011"][@selected="selected"]]
]
/following-sibling::div
[@id="name_time"]
@@ -851,14 +851,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
[@id="name_date"]
[
./select
- [@id="name_date_year"]
- [./option[@value=""][.="[trans]Change&Me[/trans]"]]
- /following-sibling::select
[@id="name_date_month"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
[@id="name_date_day"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
+ /following-sibling::select
+ [@id="name_date_year"]
+ [./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
/following-sibling::div
[@id="name_time"]
@@ -891,14 +891,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
[@id="name_date"]
[
./select
- [@id="name_date_year"]
- [./option[@value="2011"][@selected="selected"]]
- /following-sibling::select
[@id="name_date_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
[@id="name_date_day"]
[./option[@value="3"][@selected="selected"]]
+ /following-sibling::select
+ [@id="name_date_year"]
+ [./option[@value="2011"][@selected="selected"]]
]
/following-sibling::div
[@id="name_time"]
@@ -930,14 +930,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
[@id="name_date"]
[
./select
- [@id="name_date_year"]
- [./option[@value="2011"][@selected="selected"]]
- /following-sibling::select
[@id="name_date_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
[@id="name_date_day"]
[./option[@value="3"][@selected="selected"]]
+ /following-sibling::select
+ [@id="name_date_year"]
+ [./option[@value="2011"][@selected="selected"]]
]
/following-sibling::div
[@id="name_time"]
@@ -1033,14 +1033,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/div
[
./select
- [@id="name_year"]
- [./option[@value="2011"][@selected="selected"]]
- /following-sibling::select
[@id="name_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
[@id="name_day"]
[./option[@value="3"][@selected="selected"]]
+ /following-sibling::select
+ [@id="name_year"]
+ [./option[@value="2011"][@selected="selected"]]
]
[count(./select)=3]
'
@@ -1060,14 +1060,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/div
[
./select
- [@id="name_year"]
- [./option[@value=""][.="[trans]Change&Me[/trans]"]]
- /following-sibling::select
[@id="name_month"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
[@id="name_day"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
+ /following-sibling::select
+ [@id="name_year"]
+ [./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
[count(./select)=3]
'
@@ -1087,14 +1087,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/div
[
./select
- [@id="name_year"]
- [./option[@value=""][.="[trans]Change&Me[/trans]"]]
- /following-sibling::select
[@id="name_month"]
[./option[@value="1"]]
/following-sibling::select
[@id="name_day"]
[./option[@value="1"]]
+ /following-sibling::select
+ [@id="name_year"]
+ [./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
[count(./select)=3]
'
@@ -1112,10 +1112,6 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/div
[
./input
- [@id="name_year"]
- [@type="text"]
- [@value="2011"]
- /following-sibling::input
[@id="name_month"]
[@type="text"]
[@value="2"]
@@ -1123,6 +1119,10 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
[@id="name_day"]
[@type="text"]
[@value="3"]
+ /following-sibling::input
+ [@id="name_year"]
+ [@type="text"]
+ [@value="2011"]
]
[count(./input)=3]
'
@@ -1166,14 +1166,14 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/div
[
./select
- [@id="name_year"]
- [./option[@value="2000"][@selected="selected"]]
- /following-sibling::select
[@id="name_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
[@id="name_day"]
[./option[@value="3"][@selected="selected"]]
+ /following-sibling::select
+ [@id="name_year"]
+ [./option[@value="2000"][@selected="selected"]]
]
[count(./select)=3]
'
@@ -1192,10 +1192,6 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/div
[
./select
- [@id="name_year"]
- [./option[@value=""][.="[trans][/trans]"]]
- [./option[@value="1950"][@selected="selected"]]
- /following-sibling::select
[@id="name_month"]
[./option[@value=""][.="[trans][/trans]"]]
[./option[@value="1"][@selected="selected"]]
@@ -1203,6 +1199,10 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
[@id="name_day"]
[./option[@value=""][.="[trans][/trans]"]]
[./option[@value="1"][@selected="selected"]]
+ /following-sibling::select
+ [@id="name_year"]
+ [./option[@value=""][.="[trans][/trans]"]]
+ [./option[@value="1950"][@selected="selected"]]
]
[count(./select)=3]
'
diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
index c21c712c5c..efa957fb0b 100644
--- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
@@ -39,6 +39,25 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
);
}
+ public function testLabelIsNotRenderedWhenSetToFalse()
+ {
+ $form = $this->factory->createNamed('name', 'text', null, array(
+ 'label' => false
+ ));
+ $html = $this->renderRow($form->createView());
+
+ $this->assertMatchesXpath($html,
+'/tr
+ [
+ ./td
+ [count(//label)=0]
+ /following-sibling::td
+ [./input[@id="name"]]
+ ]
+'
+ );
+ }
+
public function testRepeatedRow()
{
$form = $this->factory->createNamed('name', 'repeated');
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php
index 86533e8af8..63eae9bf7f 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php
@@ -184,4 +184,17 @@ class ChoiceListTest extends \PHPUnit_Framework_TestCase
array('A')
);
}
+
+ public function testLabelsContainingNull()
+ {
+ $this->list = new ChoiceList(
+ array($this->obj1, $this->obj2),
+ array('A', null)
+ );
+
+ $this->assertEquals(
+ array(0 => new ChoiceView($this->obj1, '0', 'A'), 1 => new ChoiceView($this->obj2, '1', null)),
+ $this->list->getRemainingViews()
+ );
+ }
}
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeTestCase.php
index 9683aa1e20..e9596b3efb 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeTestCase.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeTestCase.php
@@ -18,4 +18,3 @@ abstract class DateTimeTestCase extends LocalizedTestCase
self::assertEquals($expected->format('c'), $actual->format('c'));
}
}
-
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php
index ab0bf2f2d5..f776f5e12d 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php
@@ -34,7 +34,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
$this->dateTimeWithoutSeconds = null;
}
- public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
+ public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
if ($expected instanceof \DateTime && $actual instanceof \DateTime) {
$expected = $expected->format('c');
@@ -44,54 +44,59 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
}
- public function testTransformShortDate()
+ public function dataProvider()
{
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::SHORT);
- $this->assertEquals('03.02.10 04:05', $transformer->transform($this->dateTime));
+ return array(
+ array(\IntlDateFormatter::SHORT, null, null, '03.02.10 04:05', '2010-02-03 04:05:00 UTC'),
+ array(\IntlDateFormatter::MEDIUM, null, null, '03.02.2010 04:05', '2010-02-03 04:05:00 UTC'),
+ array(\IntlDateFormatter::LONG, null, null, '03. Februar 2010 04:05', '2010-02-03 04:05:00 UTC'),
+ array(\IntlDateFormatter::FULL, null, null, 'Mittwoch, 03. Februar 2010 04:05', '2010-02-03 04:05:00 UTC'),
+ array(\IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, null, '03.02.10', '2010-02-03 00:00:00 UTC'),
+ array(\IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE, null, '03.02.2010', '2010-02-03 00:00:00 UTC'),
+ array(\IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, '03. Februar 2010', '2010-02-03 00:00:00 UTC'),
+ array(\IntlDateFormatter::FULL, \IntlDateFormatter::NONE, null, 'Mittwoch, 03. Februar 2010', '2010-02-03 00:00:00 UTC'),
+ array(null, \IntlDateFormatter::SHORT, null, '03.02.2010 04:05', '2010-02-03 04:05:00 UTC'),
+ array(null, \IntlDateFormatter::MEDIUM, null, '03.02.2010 04:05:06', '2010-02-03 04:05:06 UTC'),
+ array(null, \IntlDateFormatter::LONG, null,
+ '03.02.2010 04:05:06 GMT' . ($this->isLowerThanIcuVersion('4.8') ? '+00:00' : ''),
+ '2010-02-03 04:05:06 UTC'),
+ // see below for extra test case for time format FULL
+ array(\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, null, '04:05', '1970-01-01 04:05:00 UTC'),
+ array(\IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, null, '04:05:06', '1970-01-01 04:05:06 UTC'),
+ array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null,
+ '04:05:06 GMT' . ($this->isLowerThanIcuVersion('4.8') ? '+00:00' : ''),
+ '1970-01-01 04:05:06 UTC'),
+ array(null, null, 'yyyy-MM-dd HH:mm:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'),
+ array(null, null, 'yyyy-MM-dd HH:mm', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'),
+ array(null, null, 'yyyy-MM-dd HH', '2010-02-03 04', '2010-02-03 04:00:00 UTC'),
+ array(null, null, 'yyyy-MM-dd', '2010-02-03', '2010-02-03 00:00:00 UTC'),
+ array(null, null, 'yyyy-MM', '2010-02', '2010-02-01 00:00:00 UTC'),
+ array(null, null, 'yyyy', '2010', '2010-01-01 00:00:00 UTC'),
+ array(null, null, 'dd-MM-yyyy', '03-02-2010', '2010-02-03 00:00:00 UTC'),
+ array(null, null, 'HH:mm:ss', '04:05:06', '1970-01-01 04:05:06 UTC'),
+ array(null, null, 'HH:mm:00', '04:05:00', '1970-01-01 04:05:00 UTC'),
+ array(null, null, 'HH:mm', '04:05', '1970-01-01 04:05:00 UTC'),
+ array(null, null, 'HH', '04', '1970-01-01 04:00:00 UTC'),
+ );
}
- public function testTransformMediumDate()
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testTransform($dateFormat, $timeFormat, $pattern, $output, $input)
{
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::MEDIUM);
+ $transformer = new DateTimeToLocalizedStringTransformer(
+ 'UTC',
+ 'UTC',
+ $dateFormat,
+ $timeFormat,
+ \IntlDateFormatter::GREGORIAN,
+ $pattern
+ );
- $this->assertEquals('03.02.2010 04:05', $transformer->transform($this->dateTime));
- }
+ $input = new \DateTime($input);
- public function testTransformLongDate()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::LONG);
-
- $this->assertEquals('03. Februar 2010 04:05', $transformer->transform($this->dateTime));
- }
-
- public function testTransformFullDate()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL);
-
- $this->assertEquals('Mittwoch, 03. Februar 2010 04:05', $transformer->transform($this->dateTime));
- }
-
- public function testTransformShortTime()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::SHORT);
-
- $this->assertEquals('03.02.2010 04:05', $transformer->transform($this->dateTime));
- }
-
- public function testTransformMediumTime()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::MEDIUM);
-
- $this->assertEquals('03.02.2010 04:05:06', $transformer->transform($this->dateTime));
- }
-
- public function testTransformLongTime()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::LONG);
-
- $expected = $this->isLowerThanIcuVersion('4.8') ? '03.02.2010 04:05:06 GMT+00:00' : '03.02.2010 04:05:06 GMT';
-
- $this->assertEquals($expected, $transformer->transform($this->dateTime));
+ $this->assertEquals($output, $transformer->transform($input));
}
public function testTransformFullTime()
@@ -143,7 +148,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
}
/**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testTransformRequiresValidDateTime()
{
@@ -162,53 +167,23 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
//$transformer->transform(1.5);
}
- public function testReverseTransformShortDate()
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testReverseTransform($dateFormat, $timeFormat, $pattern, $input, $output)
{
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::SHORT);
+ $transformer = new DateTimeToLocalizedStringTransformer(
+ 'UTC',
+ 'UTC',
+ $dateFormat,
+ $timeFormat,
+ \IntlDateFormatter::GREGORIAN,
+ $pattern
+ );
- $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03.02.10 04:05'));
- }
+ $output = new \DateTime($output);
- public function testReverseTransformMediumDate()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::MEDIUM);
-
- $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03.02.2010 04:05'));
- }
-
- public function testReverseTransformLongDate()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::LONG);
-
- $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03. Februar 2010 04:05'));
- }
-
- public function testReverseTransformFullDate()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL);
-
- $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('Mittwoch, 03. Februar 2010 04:05'));
- }
-
- public function testReverseTransformShortTime()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::SHORT);
-
- $this->assertDateTimeEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('03.02.2010 04:05'));
- }
-
- public function testReverseTransformMediumTime()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::MEDIUM);
-
- $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('03.02.2010 04:05:06'));
- }
-
- public function testReverseTransformLongTime()
- {
- $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::LONG);
-
- $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('03.02.2010 04:05:06 GMT+00:00'));
+ $this->assertEquals($output, $transformer->reverseTransform($input));
}
public function testReverseTransformFullTime()
@@ -256,7 +231,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
}
/**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testReverseTransformRequiresString()
{
@@ -265,7 +240,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
}
/**
- * @expectedException Symfony\Component\Form\Exception\TransformationFailedException
+ * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransformWrapsIntlErrors()
{
@@ -274,7 +249,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
}
/**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testValidateDateFormatOption()
{
@@ -282,7 +257,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
}
/**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testValidateTimeFormatOption()
{
@@ -290,7 +265,7 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
}
/**
- * @expectedException Symfony\Component\Form\Exception\TransformationFailedException
+ * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransformWithNonExistingDate()
{
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php
index 16fe2b29ed..804549fd80 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php
@@ -15,30 +15,59 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransf
class DateTimeToStringTransformerTest extends DateTimeTestCase
{
- public function testTransform()
+ public function dataProvider()
{
- $transformer = new DateTimeToStringTransformer('UTC', 'UTC', 'Y-m-d H:i:s');
+ return array(
+ array('Y-m-d H:i:s', '2010-02-03 16:05:06', '2010-02-03 16:05:06 UTC'),
+ array('Y-m-d H:i:00', '2010-02-03 16:05:00', '2010-02-03 16:05:00 UTC'),
+ array('Y-m-d H:i', '2010-02-03 16:05', '2010-02-03 16:05:00 UTC'),
+ array('Y-m-d H', '2010-02-03 16', '2010-02-03 16:00:00 UTC'),
+ array('Y-m-d', '2010-02-03', '2010-02-03 00:00:00 UTC'),
+ array('Y-m', '2010-02', '2010-02-01 00:00:00 UTC'),
+ array('Y', '2010', '2010-01-01 00:00:00 UTC'),
+ array('d-m-Y', '03-02-2010', '2010-02-03 00:00:00 UTC'),
+ array('H:i:s', '16:05:06', '1970-01-01 16:05:06 UTC'),
+ array('H:i:00', '16:05:00', '1970-01-01 16:05:00 UTC'),
+ array('H:i', '16:05', '1970-01-01 16:05:00 UTC'),
+ array('H', '16', '1970-01-01 16:00:00 UTC'),
- $input = new \DateTime('2010-02-03 04:05:06 UTC');
- $output = clone $input;
- $output->setTimezone(new \DateTimeZone('UTC'));
- $output = $output->format('Y-m-d H:i:s');
+ // different day representations
+ array('Y-m-j', '2010-02-3', '2010-02-03 00:00:00 UTC'),
+ array('Y-z', '2010-33', '2010-02-03 00:00:00 UTC'),
+ array('z', '33', '1970-02-03 00:00:00 UTC'),
- $this->assertEquals($output, $transformer->transform($input));
+ // not bijective
+ //array('Y-m-D', '2010-02-Wed', '2010-02-03 00:00:00 UTC'),
+ //array('Y-m-l', '2010-02-Wednesday', '2010-02-03 00:00:00 UTC'),
+
+ // different month representations
+ array('Y-n-d', '2010-2-03', '2010-02-03 00:00:00 UTC'),
+ array('Y-M-d', '2010-Feb-03', '2010-02-03 00:00:00 UTC'),
+ array('Y-F-d', '2010-February-03', '2010-02-03 00:00:00 UTC'),
+
+ // different year representations
+ array('y-m-d', '10-02-03', '2010-02-03 00:00:00 UTC'),
+
+ // different time representations
+ array('G:i:s', '16:05:06', '1970-01-01 16:05:06 UTC'),
+ array('g:i:s a', '4:05:06 pm', '1970-01-01 16:05:06 UTC'),
+ array('h:i:s a', '04:05:06 pm', '1970-01-01 16:05:06 UTC'),
+
+ // seconds since unix
+ array('U', '1265213106', '2010-02-03 16:05:06 UTC'),
+ );
}
/**
- * @dataProvider getFormatAndDateTime
+ * @dataProvider dataProvider
*/
- public function testTransformRandomFormat($format, $datetime)
+ public function testTransform($format, $output, $input)
{
$transformer = new DateTimeToStringTransformer('UTC', 'UTC', $format);
- $input = new \DateTime($datetime);
- $output = clone $input;
- $output->setTimezone(new \DateTimeZone('UTC'));
+ $input = new \DateTime($input);
- $this->assertEquals($output->format($format), $transformer->transform($input));
+ $this->assertEquals($output, $transformer->transform($input));
}
public function testTransform_empty()
@@ -68,39 +97,32 @@ class DateTimeToStringTransformerTest extends DateTimeTestCase
$transformer->transform('1234');
}
- public function testReverseTransform()
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testReverseTransformBeforePhp538($format, $input, $output)
{
- $reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', 'Y-m-d H:i:s');
+ $reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', $format, false);
- $output = new \DateTime('2010-02-03 04:05:06 UTC');
- $input = $output->format('Y-m-d H:i:s');
+ $output = new \DateTime($output);
$this->assertDateTimeEquals($output, $reverseTransformer->reverseTransform($input));
}
/**
- * @dataProvider getFormatAndDateTime
+ * @dataProvider dataProvider
*/
- public function testReverseTransformRandomFormat($format, $datetime)
+ public function testReverseTransformAsOfPhp538($format, $input, $output)
{
+ if (version_compare(phpversion(), '5.3.8', '<')) {
+ $this->markTestSkipped('Requires PHP 5.3.8 or newer');
+ }
+
$reverseTransformer = new DateTimeToStringTransformer('UTC', 'UTC', $format);
- $dateTime = new \DateTime($datetime);
- $input = $dateTime->format($format);
+ $output = new \DateTime($output);
- $this->assertDateTimeEquals($dateTime, $reverseTransformer->reverseTransform($input));
- }
-
- public function getFormatAndDateTime()
- {
- return array(
- array('Y-m-d H:i:s', '2010-02-03 04:05:06 UTC'),
- array('Y-m-d H:i:00', '2010-02-03 04:05:00 UTC'),
- array('Y-m-d', '2010-02-03 UTC'),
- array('d-m-Y', '03-02-2010 UTC'),
- array('H:i:s', '04:05:06 UTC'),
- array('H:i:00', '04:05:00 UTC'),
- );
+ $this->assertDateTimeEquals($output, $reverseTransformer->reverseTransform($input));
}
public function testReverseTransform_empty()
@@ -114,7 +136,7 @@ class DateTimeToStringTransformerTest extends DateTimeTestCase
{
$reverseTransformer = new DateTimeToStringTransformer('America/New_York', 'Asia/Hong_Kong', 'Y-m-d H:i:s');
- $output = new \DateTime('2010-02-03 04:05:06 Asia/Hong_Kong');
+ $output = new \DateTime('2010-02-03 16:05:06 Asia/Hong_Kong');
$input = $output->format('Y-m-d H:i:s');
$output->setTimeZone(new \DateTimeZone('America/New_York'));
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
index 13b9d06207..fdff501668 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
@@ -510,18 +510,18 @@ class DateTypeTest extends LocalizedTestCase
$form = $this->factory->create('date');
$view = $form->createView();
- $this->assertSame('{{ year }}-{{ month }}-{{ day }}', $view->vars['date_pattern']);
+ $this->assertSame('{{ day }}.{{ month }}.{{ year }}', $view->vars['date_pattern']);
}
public function testPassDatePatternToViewDifferentFormat()
{
$form = $this->factory->create('date', null, array(
- 'format' => \IntlDateFormatter::MEDIUM,
+ 'format' => \IntlDateFormatter::LONG,
));
$view = $form->createView();
- $this->assertSame('{{ day }}.{{ month }}.{{ year }}', $view->vars['date_pattern']);
+ $this->assertSame('{{ day }}. {{ month }} {{ year }}', $view->vars['date_pattern']);
}
public function testPassDatePatternToViewDifferentPattern()
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
index 30b8798e2d..68efc0cb20 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
@@ -11,16 +11,37 @@
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
-use Symfony\Component\HttpFoundation\File\UploadedFile;
-
class FileTypeTest extends TypeTestCase
{
- public function testFormBuilderIfEntityHasFile()
+ // https://github.com/symfony/symfony/pull/5028
+ public function testSetData()
{
- $this->factory->createBuilder('file')
- ->getForm()
- ->setData($this->createUploadedFileMock('abcdef', 'original.jpg', true))
- ;
+ $form = $this->factory->createBuilder('file')->getForm();
+ $data = $this->createUploadedFileMock('abcdef', 'original.jpg', true);
+
+ $form->setData($data);
+
+ $this->assertSame($data, $form->getData());
+ }
+
+ public function testBind()
+ {
+ $form = $this->factory->createBuilder('file')->getForm();
+ $data = $this->createUploadedFileMock('abcdef', 'original.jpg', true);
+
+ $form->bind($data);
+
+ $this->assertSame($data, $form->getData());
+ }
+
+ // https://github.com/symfony/symfony/issues/6134
+ public function testBindEmpty()
+ {
+ $form = $this->factory->createBuilder('file')->getForm();
+
+ $form->bind(null);
+
+ $this->assertNull($form->getData());
}
public function testDontPassValueToView()
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php
index 6fbddeb6a4..1a8e4ad07b 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php
@@ -482,6 +482,7 @@ class FormTypeTest extends TypeTestCase
$builder->get('referenceCopy')->addViewTransformer(new CallbackTransformer(
function () {},
function ($value) { // reverseTransform
+
return 'foobar';
}
));
@@ -507,6 +508,7 @@ class FormTypeTest extends TypeTestCase
$builder->get('referenceCopy')->addViewTransformer(new CallbackTransformer(
function () {},
function ($value) use ($ref2) { // reverseTransform
+
return $ref2;
}
));
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
index 5c0d960870..1f51a9dc35 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php
@@ -105,9 +105,9 @@ class TimeTypeTest extends LocalizedTestCase
'widget' => 'single_text',
));
- $form->bind('03:04:05');
+ $form->bind('03:04');
- $this->assertEquals(new \DateTime('03:04:00 UTC'), $form->getData());
+ $this->assertEquals(new \DateTime('1970-01-01 03:04:00 UTC'), $form->getData());
$this->assertEquals('03:04', $form->getViewData());
}
@@ -162,7 +162,7 @@ class TimeTypeTest extends LocalizedTestCase
'widget' => 'single_text',
));
- $form->bind('03:04:05');
+ $form->bind('03:04');
$this->assertEquals('03:04:00', $form->getData());
$this->assertEquals('03:04', $form->getViewData());
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
index f47618fb28..a3d9f26d98 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
@@ -12,13 +12,11 @@
namespace Symfony\Component\Form\Tests\Extension\Validator\Constraints;
use Symfony\Component\Form\FormBuilder;
-use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
-use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\NotBlank;
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php
index b3d9608cc3..e192f07108 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php
@@ -227,7 +227,6 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
{
// The mapping must be deterministic! If a child has the property path "[street]",
// "data[street]" should be mapped, but "data.street" should not!
-
return array(
// mapping target, child name, its property path, grand child name, its property path, violation path
array(self::LEVEL_0, 'address', 'address', 'street', 'street', ''),
@@ -1260,7 +1259,6 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
// 1) the error actually maps to an existing child and
// 2) the property path of that child (relative to the form providing
// the mapping) matches the left side of the mapping
-
return array(
// mapping target, map from, map to, child name, its property path, grand child name, its property path, violation path
array(self::LEVEL_1, 'foo', 'address', 'foo', 'foo', 'address', 'address', 'street', 'street', 'children[foo].children[street].data'),
diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php
index 0346740bf5..0df934b135 100644
--- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php
+++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php
@@ -22,6 +22,36 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
use Symfony\Component\Form\Tests\Fixtures\FixedFilterListener;
+class SimpleFormTest_Countable implements \Countable
+{
+ private $count;
+
+ public function __construct($count)
+ {
+ $this->count = $count;
+ }
+
+ public function count()
+ {
+ return $this->count;
+ }
+}
+
+class SimpleFormTest_Traversable implements \IteratorAggregate
+{
+ private $iterator;
+
+ public function __construct($count)
+ {
+ $this->iterator = new \ArrayIterator($count > 0 ? array_fill(0, $count, 'Foo') : array());
+ }
+
+ public function getIterator()
+ {
+ return $this->iterator;
+ }
+}
+
class SimpleFormTest extends AbstractFormTest
{
public function testDataIsInitializedToConfiguredValue()
@@ -67,7 +97,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\AlreadyBoundException
+ * @expectedException \Symfony\Component\Form\Exception\AlreadyBoundException
*/
public function testBindThrowsExceptionIfAlreadyBound()
{
@@ -172,6 +202,42 @@ class SimpleFormTest extends AbstractFormTest
$this->assertTrue($this->form->isEmpty());
}
+ public function testEmptyIfEmptyCountable()
+ {
+ $this->form = new Form(new FormConfigBuilder('name', __NAMESPACE__ . '\SimpleFormTest_Countable', $this->dispatcher));
+
+ $this->form->setData(new SimpleFormTest_Countable(0));
+
+ $this->assertTrue($this->form->isEmpty());
+ }
+
+ public function testNotEmptyIfFilledCountable()
+ {
+ $this->form = new Form(new FormConfigBuilder('name', __NAMESPACE__ . '\SimpleFormTest_Countable', $this->dispatcher));
+
+ $this->form->setData(new SimpleFormTest_Countable(1));
+
+ $this->assertFalse($this->form->isEmpty());
+ }
+
+ public function testEmptyIfEmptyTraversable()
+ {
+ $this->form = new Form(new FormConfigBuilder('name', __NAMESPACE__ . '\SimpleFormTest_Traversable', $this->dispatcher));
+
+ $this->form->setData(new SimpleFormTest_Traversable(0));
+
+ $this->assertTrue($this->form->isEmpty());
+ }
+
+ public function testNotEmptyIfFilledTraversable()
+ {
+ $this->form = new Form(new FormConfigBuilder('name', __NAMESPACE__ . '\SimpleFormTest_Traversable', $this->dispatcher));
+
+ $this->form->setData(new SimpleFormTest_Traversable(1));
+
+ $this->assertFalse($this->form->isEmpty());
+ }
+
public function testEmptyIfNull()
{
$this->form->setData(null);
@@ -239,7 +305,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\AlreadyBoundException
+ * @expectedException \Symfony\Component\Form\Exception\AlreadyBoundException
*/
public function testSetParentThrowsExceptionIfAlreadyBound()
{
@@ -261,7 +327,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\AlreadyBoundException
+ * @expectedException \Symfony\Component\Form\Exception\AlreadyBoundException
*/
public function testSetDataThrowsExceptionIfAlreadyBound()
{
@@ -675,7 +741,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\FormException
+ * @expectedException \Symfony\Component\Form\Exception\FormException
* @expectedExceptionMessage A form with an empty name cannot have a parent form.
*/
public function testFormCannotHaveEmptyNameNotInRootLevel()
@@ -721,7 +787,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\FormException
+ * @expectedException \Symfony\Component\Form\Exception\FormException
*/
public function testViewDataMustNotBeObjectIfDataClassIsNull()
{
@@ -751,7 +817,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\FormException
+ * @expectedException \Symfony\Component\Form\Exception\FormException
*/
public function testViewDataMustBeObjectIfDataClassIsSet()
{
@@ -766,7 +832,7 @@ class SimpleFormTest extends AbstractFormTest
}
/**
- * @expectedException Symfony\Component\Form\Exception\FormException
+ * @expectedException \Symfony\Component\Form\Exception\FormException
*/
public function testSetDataCannotInvokeItself()
{
diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md
index 4b7d0c64ac..13ed8dd7c9 100644
--- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md
+++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
2.2.0
-----
+ * added a IpUtils class to check if an IP belongs to a CIDR
* added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
* disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to enable it)
* Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
diff --git a/src/Symfony/Component/HttpFoundation/File/File.php b/src/Symfony/Component/HttpFoundation/File/File.php
index d3d20f583a..729d870cfc 100644
--- a/src/Symfony/Component/HttpFoundation/File/File.php
+++ b/src/Symfony/Component/HttpFoundation/File/File.php
@@ -106,6 +106,20 @@ class File extends \SplFileInfo
* @api
*/
public function move($directory, $name = null)
+ {
+ $target = $this->getTargetFile($directory, $name);
+
+ if (!@rename($this->getPathname(), $target)) {
+ $error = error_get_last();
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
+
+ protected function getTargetFile($directory, $name = null)
{
if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true)) {
@@ -117,14 +131,7 @@ class File extends \SplFileInfo
$target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
- if (!@rename($this->getPathname(), $target)) {
- $error = error_get_last();
- throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
- }
-
- @chmod($target, 0666 & ~umask());
-
- return new File($target);
+ return new File($target, false);
}
/**
diff --git a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php
index 4bf920ef0f..63c5386a11 100644
--- a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php
+++ b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php
@@ -202,8 +202,21 @@ class UploadedFile extends File
*/
public function move($directory, $name = null)
{
- if ($this->isValid() && ($this->test || is_uploaded_file($this->getPathname()))) {
- return parent::move($directory, $name);
+ if ($this->isValid()) {
+ if ($this->test) {
+ return parent::move($directory, $name);
+ } elseif (is_uploaded_file($this->getPathname())) {
+ $target = $this->getTargetFile($directory, $name);
+
+ if (!@move_uploaded_file($this->getPathname(), $target)) {
+ $error = error_get_last();
+ throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
+ }
+
+ @chmod($target, 0666 & ~umask());
+
+ return $target;
+ }
}
throw new FileException(sprintf('The file "%s" has not been uploaded via Http', $this->getPathname()));
diff --git a/src/Symfony/Component/HttpFoundation/HeaderBag.php b/src/Symfony/Component/HttpFoundation/HeaderBag.php
index f26899d007..2360e55bb4 100644
--- a/src/Symfony/Component/HttpFoundation/HeaderBag.php
+++ b/src/Symfony/Component/HttpFoundation/HeaderBag.php
@@ -317,7 +317,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
$cacheControl = array();
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
- $cacheControl[strtolower($match[1])] = isset($match[2]) && $match[2] ? $match[2] : (isset($match[3]) ? $match[3] : true);
+ $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true);
}
return $cacheControl;
diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php
new file mode 100644
index 0000000000..2e3e1aa746
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/IpUtils.php
@@ -0,0 +1,111 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation;
+
+/**
+ * Http utility functions.
+ *
+ * @author Fabien Potencier
+ */
+class IpUtils
+{
+ /**
+ * This class should not be instantiated
+ */
+ private function __construct() {}
+
+ /**
+ * Validates an IPv4 or IPv6 address.
+ *
+ * @param string $requestIp
+ * @param string $ip
+ *
+ * @return boolean Whether the IP is valid
+ */
+ public static function checkIp($requestIp, $ip)
+ {
+ if (false !== strpos($requestIp, ':')) {
+ return self::checkIp6($requestIp, $ip);
+ }
+
+ return self::checkIp4($requestIp, $ip);
+ }
+
+ /**
+ * Validates an IPv4 address.
+ *
+ * @param string $requestIp
+ * @param string $ip
+ *
+ * @return boolean Whether the IP is valid
+ */
+ public static function checkIp4($requestIp, $ip)
+ {
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ($netmask < 1 || $netmask > 32) {
+ return false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 32;
+ }
+
+ return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
+ }
+
+ /**
+ * Validates an IPv6 address.
+ *
+ * @author David Soria Parra
+ * @see https://github.com/dsp/v6tools
+ *
+ * @param string $requestIp
+ * @param string $ip
+ *
+ * @return boolean Whether the IP is valid
+ *
+ * @throws \RuntimeException When IPV6 support is not enabled
+ */
+ public static function checkIp6($requestIp, $ip)
+ {
+ if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) {
+ throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
+ }
+
+ if (false !== strpos($ip, '/')) {
+ list($address, $netmask) = explode('/', $ip, 2);
+
+ if ($netmask < 1 || $netmask > 128) {
+ return false;
+ }
+ } else {
+ $address = $ip;
+ $netmask = 128;
+ }
+
+ $bytesAddr = unpack("n*", inet_pton($address));
+ $bytesTest = unpack("n*", inet_pton($requestIp));
+
+ for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) {
+ $left = $netmask - 16 * ($i-1);
+ $left = ($left <= 16) ? $left : 16;
+ $mask = ~(0xffff >> $left) & 0xffff;
+ if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php
index 12505dd7dc..33b2f14e23 100644
--- a/src/Symfony/Component/HttpFoundation/Request.php
+++ b/src/Symfony/Component/HttpFoundation/Request.php
@@ -991,6 +991,8 @@ class Request
*
* @return string
*
+ * @throws \UnexpectedValueException when the host name is invalid
+ *
* @api
*/
public function getHost()
@@ -998,20 +1000,24 @@ class Request
if (self::$trustProxy && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) {
$elements = explode(',', $host);
- $host = trim($elements[count($elements) - 1]);
- } else {
- if (!$host = $this->headers->get('HOST')) {
- if (!$host = $this->server->get('SERVER_NAME')) {
- $host = $this->server->get('SERVER_ADDR', '');
- }
+ $host = $elements[count($elements) - 1];
+ } elseif (!$host = $this->headers->get('HOST')) {
+ if (!$host = $this->server->get('SERVER_NAME')) {
+ $host = $this->server->get('SERVER_ADDR', '');
}
}
- // Remove port number from host
- $host = preg_replace('/:\d+$/', '', $host);
-
+ // trim and remove port number from host
// host is lowercase as per RFC 952/2181
- return trim(strtolower($host));
+ $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
+
+ // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
+ // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
+ if ($host && !preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host)) {
+ throw new \UnexpectedValueException('Invalid Host');
+ }
+
+ return $host;
}
/**
diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php
index 7371d17242..49b92f0e2c 100644
--- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php
+++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php
@@ -143,96 +143,10 @@ class RequestMatcher implements RequestMatcherInterface
return false;
}
- if (null !== $this->ip && !$this->checkIp($request->getClientIp(), $this->ip)) {
+ if (null !== $this->ip && !IpUtils::checkIp($request->getClientIp(), $this->ip)) {
return false;
}
return true;
}
-
- /**
- * Validates an IP address.
- *
- * @param string $requestIp
- * @param string $ip
- *
- * @return boolean True valid, false if not.
- */
- protected function checkIp($requestIp, $ip)
- {
- // IPv6 address
- if (false !== strpos($requestIp, ':')) {
- return $this->checkIp6($requestIp, $ip);
- } else {
- return $this->checkIp4($requestIp, $ip);
- }
- }
-
- /**
- * Validates an IPv4 address.
- *
- * @param string $requestIp
- * @param string $ip
- *
- * @return boolean True valid, false if not.
- */
- protected function checkIp4($requestIp, $ip)
- {
- if (false !== strpos($ip, '/')) {
- list($address, $netmask) = explode('/', $ip, 2);
-
- if ($netmask < 1 || $netmask > 32) {
- return false;
- }
- } else {
- $address = $ip;
- $netmask = 32;
- }
-
- return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
- }
-
- /**
- * Validates an IPv6 address.
- *
- * @author David Soria Parra
- * @see https://github.com/dsp/v6tools
- *
- * @param string $requestIp
- * @param string $ip
- *
- * @return boolean True valid, false if not.
- */
- protected function checkIp6($requestIp, $ip)
- {
- if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) {
- throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
- }
-
- if (false !== strpos($ip, '/')) {
- list($address, $netmask) = explode('/', $ip, 2);
-
- if ($netmask < 1 || $netmask > 128) {
- return false;
- }
- } else {
- $address = $ip;
- $netmask = 128;
- }
-
- $bytesAddr = unpack("n*", inet_pton($address));
- $bytesTest = unpack("n*", inet_pton($requestIp));
-
- for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) {
- $left = $netmask - 16 * ($i-1);
- $left = ($left <= 16) ? $left : 16;
- $mask = ~(0xffff >> $left) & 0xffff;
- if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
- return false;
- }
- }
-
- return true;
- }
}
-
diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php
index ac56f9b2df..f24c491057 100644
--- a/src/Symfony/Component/HttpFoundation/Response.php
+++ b/src/Symfony/Component/HttpFoundation/Response.php
@@ -245,6 +245,12 @@ class Response
$this->setProtocolVersion('1.1');
}
+ // Check if we need to send extra expire info headers
+ if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) {
+ $this->headers->set('pragma', 'no-cache');
+ $this->headers->set('expires', -1);
+ }
+
return $this;
}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
index 4a262aade3..69ebae9542 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -43,13 +43,18 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* * data_field: The field name for storing the session data [default: data]
* * time_field: The field name for storing the timestamp [default: time]
*
- * @param \Mongo $mongo A "Mongo" instance
- * @param array $options An associative array of field options
+ * @param \Mongo|\MongoClient $mongo A MongoClient or Mongo instance
+ * @param array $options An associative array of field options
*
+ * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
* @throws \InvalidArgumentException When "database" or "collection" not provided
*/
- public function __construct(\Mongo $mongo, array $options)
+ public function __construct($mongo, array $options)
{
+ if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
+ throw new \InvalidArgumentException('MongoClient or Mongo instance required');
+ }
+
if (!isset($options['database']) || !isset($options['collection'])) {
throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler');
}
@@ -150,7 +155,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
private function getCollection()
{
if (null === $this->collection) {
- $this->collection = $this->mongo->selectDB($this->options['database'])->selectCollection($this->options['collection']);
+ $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
}
return $this->collection;
diff --git a/src/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php b/src/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php
index ae12aa0476..94a075ac46 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php
@@ -102,7 +102,6 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('gif', $file->getClientOriginalExtension());
}
-
/**
* @expectedException Symfony\Component\HttpFoundation\File\Exception\FileException
*/
diff --git a/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php
index 20d3603038..b1bfefbf87 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php
@@ -168,6 +168,13 @@ class HeaderBagTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control'));
}
+ public function testCacheControlDirectiveParsingQuotedZero()
+ {
+ $bag = new HeaderBag(array('cache-control' => 'max-age="0"'));
+ $this->assertTrue($bag->hasCacheControlDirective('max-age'));
+ $this->assertEquals(0, $bag->getCacheControlDirective('max-age'));
+ }
+
public function testCacheControlDirectiveOverrideWithReplace()
{
$bag = new HeaderBag(array('cache-control' => 'private, max-age=100'));
diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php
new file mode 100644
index 0000000000..5b94bd2bd0
--- /dev/null
+++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpFoundation\Tests;
+
+use Symfony\Component\HttpFoundation\IpUtils;
+
+class IpUtilsTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider testIpv4Provider
+ */
+ public function testIpv4($matches, $remoteAddr, $cidr)
+ {
+ $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+ }
+
+ public function testIpv4Provider()
+ {
+ return array(
+ array(true, '192.168.1.1', '192.168.1.1'),
+ array(true, '192.168.1.1', '192.168.1.1/1'),
+ array(true, '192.168.1.1', '192.168.1.0/24'),
+ array(false, '192.168.1.1', '1.2.3.4/1'),
+ array(false, '192.168.1.1', '192.168.1/33'),
+ );
+ }
+
+ /**
+ * @dataProvider testIpv6Provider
+ */
+ public function testIpv6($matches, $remoteAddr, $cidr)
+ {
+ if (!defined('AF_INET6')) {
+ $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".');
+ }
+
+ $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr));
+ }
+
+ public function testIpv6Provider()
+ {
+ return array(
+ array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
+ array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
+ array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'),
+ array(true, '0:0:0:0:0:0:0:1', '::1'),
+ array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'),
+ );
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testAnIpv6WithOptionDisabledIpv6()
+ {
+ if (defined('AF_INET6')) {
+ $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".');
+ }
+
+ IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65');
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php
index 0e7b9eff66..0e1a0f5caf 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php
@@ -16,78 +16,6 @@ use Symfony\Component\HttpFoundation\Request;
class RequestMatcherTest extends \PHPUnit_Framework_TestCase
{
- /**
- * @dataProvider testIpv4Provider
- */
- public function testIpv4($matches, $remoteAddr, $cidr)
- {
- $request = Request::create('', 'get', array(), array(), array(), array('REMOTE_ADDR' => $remoteAddr));
-
- $matcher = new RequestMatcher();
- $matcher->matchIp($cidr);
-
- $this->assertEquals($matches, $matcher->matches($request));
- }
-
- public function testIpv4Provider()
- {
- return array(
- array(true, '192.168.1.1', '192.168.1.1'),
- array(true, '192.168.1.1', '192.168.1.1/1'),
- array(true, '192.168.1.1', '192.168.1.0/24'),
- array(false, '192.168.1.1', '1.2.3.4/1'),
- array(false, '192.168.1.1', '192.168.1/33'),
- );
- }
-
- /**
- * @dataProvider testIpv6Provider
- */
- public function testIpv6($matches, $remoteAddr, $cidr)
- {
- if (!defined('AF_INET6')) {
- $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".');
- }
-
- $request = Request::create('', 'get', array(), array(), array(), array('REMOTE_ADDR' => $remoteAddr));
-
- $matcher = new RequestMatcher();
- $matcher->matchIp($cidr);
-
- $this->assertEquals($matches, $matcher->matches($request));
- }
-
- public function testIpv6Provider()
- {
- return array(
- array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
- array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'),
- array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'),
- array(true, '0:0:0:0:0:0:0:1', '::1'),
- array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'),
- );
- }
-
- public function testAnIpv6WithOptionDisabledIpv6()
- {
- if (defined('AF_INET6')) {
- $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".');
- }
-
- $request = Request::create('', 'get', array(), array(), array(), array('REMOTE_ADDR' => '2a01:198:603:0:396e:4789:8e99:890f'));
-
- $matcher = new RequestMatcher();
- $matcher->matchIp('2a01:198:603:0::/65');
-
- try {
- $matcher->matches($request);
-
- $this->fail('An expected RuntimeException has not been raised.');
- } catch (\Exception $e) {
- $this->assertInstanceOf('\RuntimeException', $e);
- }
- }
-
/**
* @dataProvider testMethodFixtures
*/
@@ -200,4 +128,3 @@ class RequestMatcherTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($matcher->matches($request));
}
}
-
diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
index d751efe656..a178b2fd8d 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
@@ -618,9 +618,6 @@ class RequestTest extends \PHPUnit_Framework_TestCase
$this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string');
}
- /**
- * @covers Symfony\Component\HttpFoundation\Request::getHost
- */
public function testGetHost()
{
$request = new Request();
@@ -644,6 +641,16 @@ class RequestTest extends \PHPUnit_Framework_TestCase
$this->stopTrustingProxyData();
}
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testGetHostWithFakeHttpHostValue()
+ {
+ $request = new Request();
+ $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.host.com?query=string'));
+ $request->getHost();
+ }
+
/**
* @covers Symfony\Component\HttpFoundation\Request::setMethod
* @covers Symfony\Component\HttpFoundation\Request::getMethod
diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
index c14dc4b83d..4fc93c33ef 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
@@ -351,6 +351,23 @@ class ResponseTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('', $response->getContent());
}
+ public function testPrepareSetsPragmaOnHttp10Only()
+ {
+ $request = Request::create('/', 'GET');
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0');
+
+ $response = new Response('foo');
+ $response->prepare($request);
+ $this->assertEquals('no-cache', $response->headers->get('pragma'));
+ $this->assertEquals('-1', $response->headers->get('expires'));
+
+ $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1');
+ $response = new Response('foo');
+ $response->prepare($request);
+ $this->assertFalse($response->headers->has('pragma'));
+ $this->assertFalse($response->headers->has('expires'));
+ }
+
public function testSetCache()
{
$response = new Response();
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
index 409296ef1d..1cfd1175cb 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
@@ -27,11 +27,13 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
- if (!class_exists('\Mongo')) {
- $this->markTestSkipped('MongoDbSessionHandler requires the mongo extension.');
+ if (!extension_loaded('mongo')) {
+ $this->markTestSkipped('MongoDbSessionHandler requires the PHP "mongo" extension.');
}
- $this->mongo = $this->getMockBuilder('Mongo')
+ $mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? 'Mongo' : 'MongoClient';
+
+ $this->mongo = $this->getMockBuilder($mongoClass)
->disableOriginalConstructor()
->getMock();
@@ -46,6 +48,22 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
}
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForInvalidMongo()
+ {
+ new MongoDbSessionHandler(new \stdClass(), $this->options);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorShouldThrowExceptionForMissingOptions()
+ {
+ new MongoDbSessionHandler($this->mongo, array());
+ }
+
public function testOpenMethodAlwaysReturnTrue()
{
$this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
@@ -58,22 +76,13 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
public function testWrite()
{
- $database = $this->getMockBuilder('MongoDB')
- ->disableOriginalConstructor()
- ->getMock();
-
$collection = $this->getMockBuilder('MongoCollection')
->disableOriginalConstructor()
->getMock();
$this->mongo->expects($this->once())
- ->method('selectDB')
- ->with($this->options['database'])
- ->will($this->returnValue($database));
-
- $database->expects($this->once())
->method('selectCollection')
- ->with($this->options['collection'])
+ ->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$that = $this;
@@ -96,22 +105,13 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
public function testReplaceSessionData()
{
- $database = $this->getMockBuilder('MongoDB')
- ->disableOriginalConstructor()
- ->getMock();
-
$collection = $this->getMockBuilder('MongoCollection')
->disableOriginalConstructor()
->getMock();
$this->mongo->expects($this->once())
- ->method('selectDB')
- ->with($this->options['database'])
- ->will($this->returnValue($database));
-
- $database->expects($this->once())
->method('selectCollection')
- ->with($this->options['collection'])
+ ->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$data = array();
@@ -130,22 +130,13 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
public function testDestroy()
{
- $database = $this->getMockBuilder('MongoDB')
- ->disableOriginalConstructor()
- ->getMock();
-
$collection = $this->getMockBuilder('MongoCollection')
->disableOriginalConstructor()
->getMock();
$this->mongo->expects($this->once())
- ->method('selectDB')
- ->with($this->options['database'])
- ->will($this->returnValue($database));
-
- $database->expects($this->once())
->method('selectCollection')
- ->with($this->options['collection'])
+ ->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$collection->expects($this->once())
@@ -157,29 +148,20 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
public function testGc()
{
- $database = $this->getMockBuilder('MongoDB')
- ->disableOriginalConstructor()
- ->getMock();
-
$collection = $this->getMockBuilder('MongoCollection')
->disableOriginalConstructor()
->getMock();
$this->mongo->expects($this->once())
- ->method('selectDB')
- ->with($this->options['database'])
- ->will($this->returnValue($database));
-
- $database->expects($this->once())
->method('selectCollection')
- ->with($this->options['collection'])
+ ->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$that = $this;
$collection->expects($this->once())
->method('remove')
- ->will($this->returnCallback(function($criteria) use($that) {
+ ->will($this->returnCallback(function($criteria) use ($that) {
$that->assertInstanceOf('MongoDate', $criteria[$that->options['time_field']]['$lt']);
$that->assertGreaterThanOrEqual(time() - -1, $criteria[$that->options['time_field']]['$lt']->sec);
}));
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
index 9f58f2d583..de2c4939f0 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php
@@ -54,6 +54,7 @@ class NativeFileSessionHandlerTest extends \PHPUnit_Framework_TestCase
public function savePathDataProvider()
{
$base = sys_get_temp_dir();
+
return array(
array("$base/foo", "$base/foo", "$base/foo"),
array("5;$base/foo", "5;$base/foo", "$base/foo"),
diff --git a/src/Symfony/Component/HttpKernel/.gitignore b/src/Symfony/Component/HttpKernel/.gitignore
index 44de97a36a..38c15605ed 100644
--- a/src/Symfony/Component/HttpKernel/.gitignore
+++ b/src/Symfony/Component/HttpKernel/.gitignore
@@ -1,4 +1,5 @@
vendor/
composer.lock
phpunit.xml
-
+Tests/ProjectContainer.php
+Tests/classes.map
\ No newline at end of file
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
index 97f7165303..f08720e807 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Debug\ErrorHandler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
@@ -38,8 +39,9 @@ class LoggerDataCollector extends DataCollector
{
if (null !== $this->logger) {
$this->data = array(
- 'error_count' => $this->logger->countErrors(),
- 'logs' => $this->sanitizeLogs($this->logger->getLogs()),
+ 'error_count' => $this->logger->countErrors(),
+ 'logs' => $this->sanitizeLogs($this->logger->getLogs()),
+ 'deprecation_count' => $this->computeDeprecationCount()
);
}
}
@@ -66,6 +68,11 @@ class LoggerDataCollector extends DataCollector
return isset($this->data['logs']) ? $this->data['logs'] : array();
}
+ public function countDeprecations()
+ {
+ return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0;
+ }
+
/**
* {@inheritdoc}
*/
@@ -103,4 +110,16 @@ class LoggerDataCollector extends DataCollector
return $context;
}
+
+ private function computeDeprecationCount()
+ {
+ $count = 0;
+ foreach ($this->logger->getLogs() as $log) {
+ if (isset($log['context']['type']) && ErrorHandler::TYPE_DEPRECATION === $log['context']['type']) {
+ $count++;
+ }
+ }
+
+ return $count;
+ }
}
diff --git a/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php b/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php
index 605c08626c..50eadfe0a5 100644
--- a/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php
+++ b/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php
@@ -11,6 +11,9 @@
namespace Symfony\Component\HttpKernel\Debug;
+use Symfony\Component\HttpKernel\Exception\FatalErrorException;
+use Symfony\Component\HttpKernel\Log\LoggerInterface;
+
/**
* ErrorHandler.
*
@@ -18,6 +21,8 @@ namespace Symfony\Component\HttpKernel\Debug;
*/
class ErrorHandler
{
+ const TYPE_DEPRECATION = -100;
+
private $levels = array(
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
@@ -28,10 +33,19 @@ class ErrorHandler
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated',
+ E_ERROR => 'Error',
+ E_CORE_ERROR => 'Core Error',
+ E_COMPILE_ERROR => 'Compile Error',
+ E_PARSE => 'Parse',
);
private $level;
+ private $reservedMemory;
+
+ /** @var LoggerInterface */
+ private static $logger;
+
/**
* Register the error handler.
*
@@ -45,6 +59,8 @@ class ErrorHandler
$handler->setLevel($level);
set_error_handler(array($handler, 'handle'));
+ register_shutdown_function(array($handler, 'handleFatal'));
+ $handler->reservedMemory = str_repeat('x', 10240);
return $handler;
}
@@ -54,6 +70,11 @@ class ErrorHandler
$this->level = null === $level ? error_reporting() : $level;
}
+ public static function setLogger(LoggerInterface $logger)
+ {
+ self::$logger = $logger;
+ }
+
/**
* @throws \ErrorException When error_reporting returns error
*/
@@ -63,10 +84,51 @@ class ErrorHandler
return false;
}
+ if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) {
+ if (null !== self::$logger) {
+ $deprecation = array(
+ 'type' => self::TYPE_DEPRECATION,
+ 'file' => $file,
+ 'line' => $line,
+ 'stack' => version_compare(PHP_VERSION, '5.4', '<')
+ ? array_slice(debug_backtrace(false), 0, 10)
+ : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10)
+ );
+
+ self::$logger->warn($message, $deprecation);
+ }
+
+ return true;
+ }
+
if (error_reporting() & $level && $this->level & $level) {
throw new \ErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line);
}
return false;
}
+
+ public function handleFatal()
+ {
+ if (null === $error = error_get_last()) {
+ return;
+ }
+
+ unset($this->reservedMemory);
+ $type = $error['type'];
+ if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
+ return;
+ }
+
+ // get current exception handler
+ $exceptionHandler = set_exception_handler(function() {});
+ restore_exception_handler();
+
+ if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) {
+ $level = isset($this->levels[$type]) ? $this->levels[$type] : $type;
+ $message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']);
+ $exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']);
+ $exceptionHandler[0]->handle($exception);
+ }
+ }
}
diff --git a/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php b/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php
index 88ef3d7615..cfa624387e 100644
--- a/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php
+++ b/src/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php
@@ -91,7 +91,6 @@ class ExceptionHandler
*/
public function getContent(FlattenException $exception)
{
- $title = '';
switch ($exception->getStatusCode()) {
case 404:
$title = 'Sorry, the page you are looking for could not be found.';
@@ -103,13 +102,10 @@ class ExceptionHandler
$content = '';
if ($this->debug) {
try {
- $message = nl2br($exception->getMessage());
- $class = $this->abbrClass($exception->getClass());
$count = count($exception->getAllPrevious());
- $content = '';
+ $total = $count + 1;
foreach ($exception->toArray() as $position => $e) {
$ind = $count - $position + 1;
- $total = $count + 1;
$class = $this->abbrClass($e['class']);
$message = nl2br($e['message']);
$content .= sprintf(<< $trace) {
+ foreach ($e['trace'] as $trace) {
$content .= ' ';
if ($trace['function']) {
$content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php
index 5c8d5e69ad..2ca0f13284 100644
--- a/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php
+++ b/src/Symfony/Component/HttpKernel/DependencyInjection/Extension.php
@@ -11,19 +11,14 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
-use Symfony\Component\Config\Definition\Processor;
-use Symfony\Component\Config\Definition\ConfigurationInterface;
-use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
-use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension;
/**
- * Provides useful features shared by many extensions.
+ * Allow adding classes to the class cache.
*
* @author Fabien Potencier
*/
-abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface
+abstract class Extension extends BaseExtension
{
private $classes = array();
@@ -46,80 +41,4 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn
{
$this->classes = array_merge($this->classes, $classes);
}
-
- /**
- * Returns the base path for the XSD files.
- *
- * @return string The XSD base path
- */
- public function getXsdValidationBasePath()
- {
- return false;
- }
-
- /**
- * Returns the namespace to be used for this extension (XML namespace).
- *
- * @return string The XML namespace
- */
- public function getNamespace()
- {
- return 'http://example.org/schema/dic/'.$this->getAlias();
- }
-
- /**
- * Returns the recommended alias to use in XML.
- *
- * This alias is also the mandatory prefix to use when using YAML.
- *
- * This convention is to remove the "Extension" postfix from the class
- * name and then lowercase and underscore the result. So:
- *
- * AcmeHelloExtension
- *
- * becomes
- *
- * acme_hello
- *
- * This can be overridden in a sub-class to specify the alias manually.
- *
- * @return string The alias
- */
- public function getAlias()
- {
- $className = get_class($this);
- if (substr($className, -9) != 'Extension') {
- throw new \BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.');
- }
- $classBaseName = substr(strrchr($className, '\\'), 1, -9);
-
- return Container::underscore($classBaseName);
- }
-
- final protected function processConfiguration(ConfigurationInterface $configuration, array $configs)
- {
- $processor = new Processor();
-
- return $processor->processConfiguration($configuration, $configs);
- }
-
- /**
- * {@inheritDoc}
- */
- public function getConfiguration(array $config, ContainerBuilder $container)
- {
- $reflected = new \ReflectionClass($this);
- $namespace = $reflected->getNamespaceName();
-
- $class = $namespace . '\\Configuration';
- if (class_exists($class)) {
- if (!method_exists($class, '__construct')) {
- $configuration = new $class();
-
- return $configuration;
- }
- }
-
- return null;
- }
}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/DeprecationLoggerListener.php b/src/Symfony/Component/HttpKernel/EventListener/DeprecationLoggerListener.php
new file mode 100644
index 0000000000..a40e866155
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/EventListener/DeprecationLoggerListener.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\EventListener;
+
+use Symfony\Component\HttpKernel\Log\LoggerInterface;
+use Symfony\Component\HttpKernel\Debug\ErrorHandler;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Injects the logger into the ErrorHandler, so that it can log deprecation errors.
+ *
+ * @author Colin Frei
+ */
+class DeprecationLoggerListener implements EventSubscriberInterface
+{
+ private $logger;
+
+ public function __construct(LoggerInterface $logger = null)
+ {
+ $this->logger = $logger;
+ }
+
+ public function injectLogger()
+ {
+ if (null !== $this->logger) {
+ ErrorHandler::setLogger($this->logger);
+ }
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(KernelEvents::REQUEST => 'injectLogger');
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php b/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php
new file mode 100644
index 0000000000..a082f80dc0
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Exception/FatalErrorException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Exception;
+
+/**
+ * Fatal Error Exception.
+ *
+ * @author Konstanton Myakshin
+ */
+class FatalErrorException extends \ErrorException
+{
+
+}
diff --git a/src/Symfony/Component/HttpKernel/Exception/FlattenException.php b/src/Symfony/Component/HttpKernel/Exception/FlattenException.php
index eb5e5715fa..367b549440 100644
--- a/src/Symfony/Component/HttpKernel/Exception/FlattenException.php
+++ b/src/Symfony/Component/HttpKernel/Exception/FlattenException.php
@@ -47,7 +47,7 @@ class FlattenException
$e->setStatusCode($statusCode);
$e->setHeaders($headers);
- $e->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine());
+ $e->setTraceFromException($exception);
$e->setClass(get_class($exception));
$e->setFile($exception->getFile());
$e->setLine($exception->getLine());
@@ -168,6 +168,40 @@ class FlattenException
return $this->trace;
}
+ public function setTraceFromException(\Exception $exception)
+ {
+ $trace = $exception->getTrace();
+
+ if ($exception instanceof FatalErrorException) {
+ if (function_exists('xdebug_get_function_stack')) {
+ $trace = array_slice(array_reverse(xdebug_get_function_stack()), 4);
+
+ foreach ($trace as $i => $frame) {
+ // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
+ if (!isset($frame['type'])) {
+ $trace[$i]['type'] = '??';
+ }
+
+ if ('dynamic' === $trace[$i]['type']) {
+ $trace[$i]['type'] = '->';
+ } elseif ('static' === $trace[$i]['type']) {
+ $trace[$i]['type'] = '::';
+ }
+
+ // XDebug also has a different name for the parameters array
+ if (isset($frame['params']) && !isset($frame['args'])) {
+ $trace[$i]['args'] = $frame['params'];
+ unset($trace[$i]['params']);
+ }
+ }
+ } else {
+ $trace = array_slice(array_reverse($trace), 1);
+ }
+ }
+
+ $this->setTrace($trace, $exception->getFile(), $exception->getLine());
+ }
+
public function setTrace($trace, $file, $line)
{
$this->trace = array();
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
index e1a0e6a271..0ab9ea0a25 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Esi.php
@@ -34,9 +34,9 @@ class Esi
* Constructor.
*
* @param array $contentTypes An array of content-type that should be parsed for ESI information.
- * (default: text/html, text/xml, and application/xml)
+ * (default: text/html, text/xml, application/xhtml+xml, and application/xml)
*/
- public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xml'))
+ public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml'))
{
$this->contentTypes = $contentTypes;
}
diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php
index b0662db99d..2d1df30a7c 100644
--- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php
+++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php
@@ -84,6 +84,7 @@ class Store implements StoreInterface
return true;
}
+
return !file_exists($path) ?: $path;
}
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 046d0c8a6a..73473cf3f8 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -95,17 +95,18 @@ abstract class Kernel implements KernelInterface, TerminableInterface
public function init()
{
+ ini_set('display_errors', 0);
+
if ($this->debug) {
- ini_set('display_errors', 1);
error_reporting(-1);
DebugClassLoader::enable();
ErrorHandler::register($this->errorReportingLevel);
if ('cli' !== php_sapi_name()) {
ExceptionHandler::register();
+ } else {
+ ini_set('display_errors', 1);
}
- } else {
- ini_set('display_errors', 0);
}
}
@@ -207,7 +208,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
/**
* {@inheritdoc}
- *
+ *
* @api
*/
public function getBundles()
@@ -729,17 +730,26 @@ abstract class Kernel implements KernelInterface, TerminableInterface
return $source;
}
+ $rawChunk = '';
$output = '';
- foreach (token_get_all($source) as $token) {
+ $tokens = token_get_all($source);
+ for (reset($tokens); false !== $token = current($tokens); next($tokens)) {
if (is_string($token)) {
- $output .= $token;
+ $rawChunk .= $token;
+ } elseif (T_START_HEREDOC === $token[0]) {
+ $output .= preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $rawChunk) . $token[1];
+ do {
+ $token = next($tokens);
+ $output .= $token[1];
+ } while ($token[0] !== T_END_HEREDOC);
+ $rawChunk = '';
} elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
- $output .= $token[1];
+ $rawChunk .= $token[1];
}
}
// replace multiple new lines with a single newline
- $output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output);
+ $output .= preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $rawChunk);
return $output;
}
diff --git a/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php
index 90db3dfbb1..9ca529e910 100644
--- a/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php
+++ b/src/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php
@@ -85,7 +85,9 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
'time' => $profile->getTime()
);
- return $this->getMongo()->update(array('_id' => $profile->getToken()), array_filter($record, function ($v) { return !empty($v); }), array('upsert' => true));
+ $result = $this->getMongo()->update(array('_id' => $profile->getToken()), array_filter($record, function ($v) { return !empty($v); }), array('upsert' => true));
+
+ return (boolean) (isset($result['ok']) ? $result['ok'] : $result);
}
/**
@@ -97,12 +99,15 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
{
if ($this->mongo === null) {
if (preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $this->dsn, $matches)) {
- $mongo = new \Mongo($matches[1] . (!empty($matches[2]) ? '/' . $matches[2] : ''));
+ $server = $matches[1] . (!empty($matches[2]) ? '/' . $matches[2] : '');
$database = $matches[2];
$collection = $matches[3];
+
+ $mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? '\Mongo' : '\MongoClient';
+ $mongo = new $mongoClass($server);
$this->mongo = $mongo->selectCollection($database, $collection);
} else {
- throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://user:pass@location/database/collection"', $this->dsn));
+ throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn));
}
}
@@ -111,7 +116,7 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
/**
* @param array $data
- *
+ *
* @return Profile
*/
protected function createProfileFromData(array $data)
@@ -132,7 +137,7 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
/**
* @param string $token
- *
+ *
* @return Profile[] An array of Profile instances
*/
protected function readChildren($token)
@@ -156,7 +161,7 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
* @param string $ip
* @param string $url
* @param string $method
- *
+ *
* @return array
*/
private function buildQuery($ip, $url, $method)
@@ -180,7 +185,7 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
/**
* @param array $data
- *
+ *
* @return array
*/
private function getData(array $data)
@@ -198,7 +203,7 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
/**
* @param array $data
- *
+ *
* @return Profile
*/
private function getProfile(array $data)
diff --git a/src/Symfony/Component/HttpKernel/Profiler/Profile.php b/src/Symfony/Component/HttpKernel/Profiler/Profile.php
index e00ba71d36..e24c725144 100644
--- a/src/Symfony/Component/HttpKernel/Profiler/Profile.php
+++ b/src/Symfony/Component/HttpKernel/Profiler/Profile.php
@@ -114,7 +114,7 @@ class Profile
/**
* Sets the IP.
- *
+ *
* @param string $ip
*/
public function setIp($ip)
@@ -179,7 +179,7 @@ class Profile
/**
* Sets children profiler.
- *
+ *
* @param Profile[] $children An array of Profile
*/
public function setChildren(array $children)
@@ -221,7 +221,7 @@ class Profile
/**
* Gets the Collectors associated with this profile.
- *
+ *
* @return DataCollectorInterface[]
*/
public function getCollectors()
@@ -231,7 +231,7 @@ class Profile
/**
* Sets the Collectors associated with this profile.
- *
+ *
* @param DataCollectorInterface[] $collectors
*/
public function setCollectors(array $collectors)
diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
index a4b853c3ed..ea82d9d315 100644
--- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector;
+use Symfony\Component\HttpKernel\Debug\ErrorHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -27,18 +28,19 @@ class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider getCollectTestData
*/
- public function testCollect($nb, $logs, $expected)
+ public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount)
{
$logger = $this->getMock('Symfony\Component\HttpKernel\Log\DebugLoggerInterface');
$logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb));
- $logger->expects($this->once())->method('getLogs')->will($this->returnValue($logs));
+ $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue($logs));
$c = new LoggerDataCollector($logger);
$c->collect(new Request(), new Response());
$this->assertSame('logger', $c->getName());
$this->assertSame($nb, $c->countErrors());
- $this->assertSame($expected ? $expected : $logs, $c->getLogs());
+ $this->assertSame($expectedLogs ? $expectedLogs : $logs, $c->getLogs());
+ $this->assertSame($expectedDeprecationCount, $c->countDeprecations());
}
public function getCollectTestData()
@@ -48,16 +50,28 @@ class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase
1,
array(array('message' => 'foo', 'context' => array())),
null,
+ 0
),
array(
1,
array(array('message' => 'foo', 'context' => array('foo' => fopen(__FILE__, 'r')))),
array(array('message' => 'foo', 'context' => array('foo' => 'Resource(stream)'))),
+ 0
),
array(
1,
array(array('message' => 'foo', 'context' => array('foo' => new \stdClass()))),
array(array('message' => 'foo', 'context' => array('foo' => 'Object(stdClass)'))),
+ 0
+ ),
+ array(
+ 1,
+ array(
+ array('message' => 'foo', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION)),
+ array('message' => 'foo2', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION))
+ ),
+ null,
+ 2
),
);
}
diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php
index 0eb2d79d4c..fdd02eaaf5 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Debug/ErrorHandlerTest.php
@@ -12,7 +12,6 @@
namespace Symfony\Component\HttpKernel\Tests\Debug;
use Symfony\Component\HttpKernel\Debug\ErrorHandler;
-use Symfony\Component\HttpKernel\Debug\ErrorException;
/**
* ErrorHandlerTest
@@ -48,14 +47,51 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
$handler = ErrorHandler::register(3);
try {
- $handler->handle(1, 'foo', 'foo.php', 12, 'foo');
+ $handler->handle(111, 'foo', 'foo.php', 12, 'foo');
} catch (\ErrorException $e) {
- $this->assertSame('1: foo in foo.php line 12', $e->getMessage());
- $this->assertSame(1, $e->getSeverity());
+ $this->assertSame('111: foo in foo.php line 12', $e->getMessage());
+ $this->assertSame(111, $e->getSeverity());
$this->assertSame('foo.php', $e->getFile());
$this->assertSame(12, $e->getLine());
}
restore_error_handler();
+
+ $handler = ErrorHandler::register(E_USER_DEPRECATED);
+ $this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo'));
+
+ restore_error_handler();
+
+ $handler = ErrorHandler::register(E_DEPRECATED);
+ $this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, 'foo'));
+
+ restore_error_handler();
+
+ $logger = $this->getMock('Symfony\Component\HttpKernel\Log\LoggerInterface');
+
+ $that = $this;
+ $warnArgCheck = function($message, $context) use ($that) {
+ $that->assertEquals('foo', $message);
+ $that->assertArrayHasKey('file', $context);
+ $that->assertEquals($context['file'], 'foo.php');
+ $that->assertArrayHasKey('line', $context);
+ $that->assertEquals($context['line'], 12);
+ $that->assertArrayHasKey('type', $context);
+ $that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION);
+ $that->assertArrayHasKey('stack', $context);
+ $that->assertInternalType('array', $context['stack']);
+ };
+
+ $logger
+ ->expects($this->once())
+ ->method('warn')
+ ->will($this->returnCallback($warnArgCheck))
+ ;
+
+ $handler = ErrorHandler::register(E_USER_DEPRECATED);
+ $handler->setLogger($logger);
+ $handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo');
+
+ restore_error_handler();
}
}
diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php
index 88318cc2e9..07faa8a688 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php
@@ -194,7 +194,7 @@ class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
class EventSubscriber implements EventSubscriberInterface
{
- static public function getSubscribedEvents()
+ public static function getSubscribedEvents()
{
return array('foo' => 'call');
}
diff --git a/src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php
index 934b44b461..d51ab6aa07 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Exception/FlattenExceptionTest.php
@@ -156,14 +156,14 @@ class FlattenExceptionTest extends \PHPUnit_Framework_TestCase
public function testToArray(\Exception $exception, $statusCode)
{
$flattened = FlattenException::create($exception);
- $flattened->setTrace(array(),'foo.php',123);
+ $flattened->setTrace(array(), 'foo.php', 123);
$this->assertEquals(array(
array(
'message'=> 'test',
'class'=>'Exception',
'trace'=>array(array(
- 'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php','line' => 123,
+ 'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php', 'line' => 123,
'args' => array()
)),
)
diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
index 46361f6f5a..d4295f5ab8 100644
--- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
@@ -223,9 +223,28 @@ class KernelTest extends \PHPUnit_Framework_TestCase
return;
}
- $source = <<[0-9]\.[0-9]|[0-9]{2,})/', $version, $matches);
return $matches['version'];
}
-function download_icu_data($version) {
+function download_icu_data($version)
+{
$icu = parse_ini_file(__DIR__.'/icu.ini');
if (!isset($icu[$version])) {
@@ -624,4 +627,4 @@ create_stub_datafile($defaultLocale, $stubRegionDir, $countries);
// Clean up
clear_directory($currDir);
-rmdir($currDir);
\ No newline at end of file
+rmdir($currDir);
diff --git a/src/Symfony/Component/Locale/Resources/data/icu.ini b/src/Symfony/Component/Locale/Resources/data/icu.ini
index 8db4652b99..9c31dd4bce 100644
--- a/src/Symfony/Component/Locale/Resources/data/icu.ini
+++ b/src/Symfony/Component/Locale/Resources/data/icu.ini
@@ -6,3 +6,4 @@
4.6 = http://source.icu-project.org/repos/icu/icu/tags/release-4-6-1/source/data
4.8 = http://source.icu-project.org/repos/icu/icu/tags/release-4-8-1-1/source/data
49 = http://source.icu-project.org/repos/icu/icu/tags/release-49-1-2/source/data
+50 = http://source.icu-project.org/repos/icu/icu/tags/release-50-1/source/data
diff --git a/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php b/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php
index 628de046a4..f3411b63d4 100644
--- a/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php
+++ b/src/Symfony/Component/Locale/Tests/Stub/StubIntlDateFormatterTest.php
@@ -875,6 +875,13 @@ class StubIntlDateFormatterTest extends LocaleTestCase
array('y-LLLLL-d', '1970-J-1'),
array('y-LLLLL-d', '1970-S-1'),
);
+
+ if (!$this->isIntlExtensionLoaded() || $this->isLowerThanIcuVersion('4.8')) {
+ $data[] = array('y-M-d', '1970/1/1');
+ $data[] = array('yy-M-d', '70/1/1');
+ }
+
+ return $data;
}
/*
diff --git a/src/Symfony/Component/Locale/Tests/Stub/StubLocaleTest.php b/src/Symfony/Component/Locale/Tests/Stub/StubLocaleTest.php
index 568ee1ead3..ed08aac272 100644
--- a/src/Symfony/Component/Locale/Tests/Stub/StubLocaleTest.php
+++ b/src/Symfony/Component/Locale/Tests/Stub/StubLocaleTest.php
@@ -11,7 +11,6 @@
namespace Symfony\Component\Locale\Tests\Stub;
-use Symfony\Component\Locale\Locale;
use Symfony\Component\Locale\Stub\StubLocale;
use Symfony\Component\Locale\Tests\TestCase as LocaleTestCase;
@@ -22,7 +21,7 @@ class StubLocaleTest extends LocaleTestCase
*/
public function testGetDisplayCountriesWithUnsupportedLocale()
{
- $countries = StubLocale::getDisplayCountries('pt_BR');
+ StubLocale::getDisplayCountries('pt_BR');
}
public function testGetDisplayCountries()
@@ -42,7 +41,7 @@ class StubLocaleTest extends LocaleTestCase
*/
public function testGetDisplayLanguagesWithUnsupportedLocale()
{
- $countries = StubLocale::getDisplayLanguages('pt_BR');
+ StubLocale::getDisplayLanguages('pt_BR');
}
public function testGetDisplayLanguages()
@@ -62,7 +61,7 @@ class StubLocaleTest extends LocaleTestCase
*/
public function testGetCurrenciesDataWithUnsupportedLocale()
{
- $currencies = StubLocale::getCurrenciesData('pt_BR');
+ StubLocale::getCurrenciesData('pt_BR');
}
public function testGetCurrenciesData()
@@ -97,7 +96,7 @@ class StubLocaleTest extends LocaleTestCase
*/
public function testGetDisplayLocalesWithUnsupportedLocale()
{
- $locales = StubLocale::getDisplayLocales('pt');
+ StubLocale::getDisplayLocales('pt');
}
public function testGetDisplayLocales()
diff --git a/src/Symfony/Component/Locale/Tests/Stub/StubNumberFormatterTest.php b/src/Symfony/Component/Locale/Tests/Stub/StubNumberFormatterTest.php
index 9471f820d1..7f033a31a9 100644
--- a/src/Symfony/Component/Locale/Tests/Stub/StubNumberFormatterTest.php
+++ b/src/Symfony/Component/Locale/Tests/Stub/StubNumberFormatterTest.php
@@ -210,7 +210,7 @@ class StubNumberFormatterTest extends LocaleTestCase
public function formatCurrencyWithCurrencyStyleBrazilianRealRoundingProvider()
{
- $brl = $this->isIntlExtensionLoaded() && $this->isSameAsIcuVersion('4.8') ? 'BR' : 'R';
+ $brl = $this->isIntlExtensionLoaded() && $this->isGreaterOrEqualThanIcuVersion('4.8') ? 'BR' : 'R';
return array(
array(100, 'BRL', $brl, '%s$100.00'),
diff --git a/src/Symfony/Component/Locale/Tests/TestCase.php b/src/Symfony/Component/Locale/Tests/TestCase.php
index b6483285a2..aa772f25e3 100644
--- a/src/Symfony/Component/Locale/Tests/TestCase.php
+++ b/src/Symfony/Component/Locale/Tests/TestCase.php
@@ -89,7 +89,16 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
protected function normalizeIcuVersion($version)
{
- return ((float) $version) * 100;
+ $versionIds = explode('.', $version);
+
+ $multi = 1000;
+ $intVersion = 0;
+ foreach ($versionIds as $id) {
+ $intVersion += $id * $multi;
+ $multi = $multi / 10;
+ }
+
+ return (int) $intVersion;
}
protected function getIntlExtensionIcuVersion()
diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php
index f739c911a1..916a9ac7af 100644
--- a/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php
+++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsTest.php
@@ -521,6 +521,7 @@ class OptionsTest extends \PHPUnit_Framework_TestCase
$this->options->setNormalizer('foo', function (Options $options, $previousValue) use ($test) {
$test->assertNull($previousValue);
+
return '';
});
$this->assertEquals(array('foo' => ''), $this->options->all());
diff --git a/src/Symfony/Component/Process/CHANGELOG.md b/src/Symfony/Component/Process/CHANGELOG.md
index f0752ec8e8..7fa5b72d50 100644
--- a/src/Symfony/Component/Process/CHANGELOG.md
+++ b/src/Symfony/Component/Process/CHANGELOG.md
@@ -4,6 +4,7 @@ CHANGELOG
2.2.0
-----
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
* added a way to retrieve the standard and error output incrementally
* added Process:restart()
diff --git a/src/Symfony/Component/Process/ProcessBuilder.php b/src/Symfony/Component/Process/ProcessBuilder.php
index 2a88ea00d4..ce3a041f16 100644
--- a/src/Symfony/Component/Process/ProcessBuilder.php
+++ b/src/Symfony/Component/Process/ProcessBuilder.php
@@ -56,6 +56,18 @@ class ProcessBuilder
return $this;
}
+ /**
+ * @param array $arguments
+ *
+ * @return ProcessBuilder
+ */
+ public function setArguments(array $arguments)
+ {
+ $this->arguments = $arguments;
+
+ return $this;
+ }
+
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php
index 66ccc91189..8f29cf8163 100644
--- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php
+++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php
@@ -18,7 +18,7 @@ use Symfony\Component\Process\Process;
*/
abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{
- protected abstract function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array());
+ abstract protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array());
/**
* @expectedException Symfony\Component\Process\Exception\InvalidArgumentException
@@ -115,7 +115,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetErrorOutput()
{
- $p = new Process(sprintf('php -r %s', escapeshellarg('ini_set(\'display_errors\',\'on\');$n=0;while($n<3){echo $a;$n++;}')));
+ $p = new Process(sprintf('php -r %s', escapeshellarg('ini_set(\'display_errors\',\'on\'); $n = 0; while ($n < 3) { echo $a; $n++; }')));
$p->run();
$this->assertEquals(3, preg_match_all('/PHP Notice/', $p->getErrorOutput(), $matches));
@@ -123,7 +123,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetIncrementalErrorOutput()
{
- $p = new Process(sprintf('php -r %s', escapeshellarg('ini_set(\'display_errors\',\'on\');usleep(50000);$n=0;while($n<3){echo $a;$n++;}')));
+ $p = new Process(sprintf('php -r %s', escapeshellarg('ini_set(\'display_errors\',\'on\'); usleep(50000); $n = 0; while ($n < 3) { echo $a; $n++; }')));
$p->start();
while ($p->isRunning()) {
@@ -134,7 +134,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetOutput()
{
- $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while($n<3){echo \' foo \';$n++;}')));
+ $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
$p->run();
$this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
@@ -142,7 +142,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetIncrementalOutput()
{
- $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while($n<3){echo \' foo \';usleep(50000);$n++;}')));
+ $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) { echo \' foo \'; usleep(50000); $n++; }')));
$p->start();
while ($p->isRunning()) {
@@ -282,7 +282,6 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals');
}
-
$process = $this->getProcess('php -r "while (true) {}"');
$process->start();
$process->stop();
diff --git a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php
index 4f6157e46e..469208c99f 100644
--- a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php
+++ b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php
@@ -105,4 +105,14 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
$this->assertNull($p->getValue($pb));
}
+
+ public function testShouldSetArguments()
+ {
+ $pb = new ProcessBuilder(array('initial'));
+ $pb->setArguments(array('second'));
+
+ $proc = $pb->getProcess();
+
+ $this->assertContains("second", $proc->getCommandLine());
+ }
}
diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md
index 8c5665b2fc..fba7d41a40 100644
--- a/src/Symfony/Component/Routing/CHANGELOG.md
+++ b/src/Symfony/Component/Routing/CHANGELOG.md
@@ -4,6 +4,63 @@ CHANGELOG
2.2.0
-----
+ * [BC BREAK] 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);
+ ```
+
+ * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()`
+ have been deprecated and will be removed in Symfony 2.3.
+ * [BC BREAK] 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.
+ * [DEPRECATION] 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()`.
+ * [DEPRECATION] 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. Don't worry about performance, prefix optimization for matching is still done
+ in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities.
+ * [DEPRECATION] `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);
+ ```
* added support for the method default argument values when defining a @Route
* Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`.
* Characters that function as separator between placeholders are now whitelisted
diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php
index 2c510a5e32..aa0a3ee8f2 100644
--- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php
+++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php
@@ -29,7 +29,7 @@ use Symfony\Component\HttpKernel\Log\LoggerInterface;
class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface
{
/**
- * @var RouteCollection
+ * @var RouteCollection
*/
protected $routes;
diff --git a/src/Symfony/Component/Routing/Loader/ClosureLoader.php b/src/Symfony/Component/Routing/Loader/ClosureLoader.php
index 0ec442d87d..8212c2916d 100644
--- a/src/Symfony/Component/Routing/Loader/ClosureLoader.php
+++ b/src/Symfony/Component/Routing/Loader/ClosureLoader.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Routing\Loader;
use Symfony\Component\Config\Loader\Loader;
+use Symfony\Component\Routing\RouteCollection;
/**
* ClosureLoader loads routes from a PHP closure.
@@ -30,6 +31,8 @@ class ClosureLoader extends Loader
* @param \Closure $closure A Closure
* @param string|null $type The resource type
*
+ * @return RouteCollection A RouteCollection instance
+ *
* @api
*/
public function load($closure, $type = null)
diff --git a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php
index b93dd255b9..98b7efbf47 100644
--- a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php
@@ -11,8 +11,9 @@
namespace Symfony\Component\Routing\Loader;
-use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Routing\RouteCollection;
/**
* PhpFileLoader loads routes from a PHP file.
@@ -31,6 +32,8 @@ class PhpFileLoader extends FileLoader
* @param string $file A PHP file path
* @param string|null $type The resource type
*
+ * @return RouteCollection A RouteCollection instance
+ *
* @api
*/
public function load($file, $type = null)
diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
index 1b3eb0c15f..00a6012719 100644
--- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php
@@ -15,16 +15,21 @@ use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Loader\FileLoader;
+use Symfony\Component\Config\Util\XmlUtils;
/**
* XmlFileLoader loads XML routing files.
*
* @author Fabien Potencier
+ * @author Tobias Schultze
*
* @api
*/
class XmlFileLoader extends FileLoader
{
+ const NAMESPACE_URI = 'http://symfony.com/schema/routing';
+ const SCHEME_PATH = '/schema/routing/routing-1.0.xsd';
+
/**
* Loads an XML file.
*
@@ -33,7 +38,8 @@ class XmlFileLoader extends FileLoader
*
* @return RouteCollection A RouteCollection instance
*
- * @throws \InvalidArgumentException When a tag can't be parsed
+ * @throws \InvalidArgumentException When the file cannot be loaded or when the XML cannot be
+ * parsed because it does not validate against the scheme.
*
* @api
*/
@@ -61,54 +67,28 @@ class XmlFileLoader extends FileLoader
/**
* Parses a node from a loaded XML file.
*
- * @param RouteCollection $collection the collection to associate with the node
- * @param \DOMElement $node the node to parse
- * @param string $path the path of the XML file being processed
- * @param string $file
+ * @param RouteCollection $collection Collection to associate with the node
+ * @param \DOMElement $node Element to parse
+ * @param string $path Full path of the XML file being processed
+ * @param string $file Loaded file name
*
- * @throws \InvalidArgumentException When a tag can't be parsed
+ * @throws \InvalidArgumentException When the XML is invalid
*/
protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file)
{
- switch ($node->tagName) {
+ if (self::NAMESPACE_URI !== $node->namespaceURI) {
+ return;
+ }
+
+ switch ($node->localName) {
case 'route':
$this->parseRoute($collection, $node, $path);
break;
case 'import':
- $resource = $node->getAttribute('resource');
- $type = $node->getAttribute('type');
- $prefix = $node->getAttribute('prefix');
- $hostnamePattern = $node->getAttribute('hostname-pattern');
-
- $defaults = array();
- $requirements = array();
- $options = array();
-
- foreach ($node->childNodes as $n) {
- if (!$n instanceof \DOMElement) {
- continue;
- }
-
- switch ($n->tagName) {
- case 'default':
- $defaults[$n->getAttribute('key')] = trim($n->nodeValue);
- break;
- case 'requirement':
- $requirements[$n->getAttribute('key')] = trim($n->nodeValue);
- break;
- case 'option':
- $options[$n->getAttribute('key')] = trim($n->nodeValue);
- break;
- default:
- throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $n->tagName));
- }
- }
-
- $this->setCurrentDir(dirname($path));
- $collection->addCollection($this->import($resource, ('' !== $type ? $type : null), false, $file), $prefix, $defaults, $requirements, $options, $hostnamePattern);
+ $this->parseImport($collection, $node, $path, $file);
break;
default:
- throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
+ throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path));
}
}
@@ -125,41 +105,59 @@ class XmlFileLoader extends FileLoader
/**
* Parses a route and adds it to the RouteCollection.
*
- * @param RouteCollection $collection A RouteCollection instance
- * @param \DOMElement $definition Route definition
- * @param string $file An XML file path
+ * @param RouteCollection $collection RouteCollection instance
+ * @param \DOMElement $node Element to parse that represents a Route
+ * @param string $path Full path of the XML file being processed
*
- * @throws \InvalidArgumentException When the definition cannot be parsed
+ * @throws \InvalidArgumentException When the XML is invalid
*/
- protected function parseRoute(RouteCollection $collection, \DOMElement $definition, $file)
+ protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path)
{
- $defaults = array();
- $requirements = array();
- $options = array();
-
- foreach ($definition->childNodes as $node) {
- if (!$node instanceof \DOMElement) {
- continue;
- }
-
- switch ($node->tagName) {
- case 'default':
- $defaults[$node->getAttribute('key')] = trim((string) $node->nodeValue);
- break;
- case 'option':
- $options[$node->getAttribute('key')] = trim((string) $node->nodeValue);
- break;
- case 'requirement':
- $requirements[$node->getAttribute('key')] = trim((string) $node->nodeValue);
- break;
- default:
- throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName));
- }
+ if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('pattern')) {
+ throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "pattern" attribute.', $path));
}
- $route = new Route($definition->getAttribute('pattern'), $defaults, $requirements, $options, $definition->getAttribute('hostname-pattern'));
+ list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);
- $collection->add($definition->getAttribute('id'), $route);
+ $route = new Route($node->getAttribute('pattern'), $defaults, $requirements, $options, $node->getAttribute('hostname-pattern'));
+ $collection->add($id, $route);
+ }
+
+ /**
+ * Parses an import and adds the routes in the resource to the RouteCollection.
+ *
+ * @param RouteCollection $collection RouteCollection instance
+ * @param \DOMElement $node Element to parse that represents a Route
+ * @param string $path Full path of the XML file being processed
+ * @param string $file Loaded file name
+ *
+ * @throws \InvalidArgumentException When the XML is invalid
+ */
+ protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file)
+ {
+ if ('' === $resource = $node->getAttribute('resource')) {
+ throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path));
+ }
+
+ $type = $node->getAttribute('type');
+ $prefix = $node->getAttribute('prefix');
+ $hostnamePattern = $node->hasAttribute('hostname-pattern') ? $node->getAttribute('hostname-pattern') : null;
+
+ list($defaults, $requirements, $options) = $this->parseConfigs($node, $path);
+
+ $this->setCurrentDir(dirname($path));
+
+ $subCollection = $this->import($resource, ('' !== $type ? $type : null), false, $file);
+ /* @var $subCollection RouteCollection */
+ $subCollection->addPrefix($prefix);
+ if (null !== $hostnamePattern) {
+ $subCollection->setHostnamePattern($hostnamePattern);
+ }
+ $subCollection->addDefaults($defaults);
+ $subCollection->addRequirements($requirements);
+ $subCollection->addOptions($options);
+
+ $collection->addCollection($subCollection);
}
/**
@@ -169,79 +167,47 @@ class XmlFileLoader extends FileLoader
*
* @return \DOMDocument
*
- * @throws \InvalidArgumentException When loading of XML file returns error
+ * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors
+ * or when the XML structure is not as expected by the scheme -
+ * see validate()
*/
protected function loadFile($file)
{
- $internalErrors = libxml_use_internal_errors(true);
- $disableEntities = libxml_disable_entity_loader(true);
- libxml_clear_errors();
+ return XmlUtils::loadFile($file, __DIR__ . static::SCHEME_PATH);
+ }
- $dom = new \DOMDocument();
- $dom->validateOnParse = true;
- if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
- libxml_disable_entity_loader($disableEntities);
+ /**
+ * Parses the config elements (default, requirement, option).
+ *
+ * @param \DOMElement $node Element to parse that contains the configs
+ * @param string $path Full path of the XML file being processed
+ *
+ * @return array An array with the defaults as first item, requirements as second and options as third.
+ *
+ * @throws \InvalidArgumentException When the XML is invalid
+ */
+ private function parseConfigs(\DOMElement $node, $path)
+ {
+ $defaults = array();
+ $requirements = array();
+ $options = array();
- throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($internalErrors)));
- }
- $dom->normalizeDocument();
-
- libxml_use_internal_errors($internalErrors);
- libxml_disable_entity_loader($disableEntities);
-
- foreach ($dom->childNodes as $child) {
- if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
- throw new \InvalidArgumentException('Document types are not allowed.');
+ foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) {
+ switch ($n->localName) {
+ case 'default':
+ $defaults[$n->getAttribute('key')] = trim($n->textContent);
+ break;
+ case 'requirement':
+ $requirements[$n->getAttribute('key')] = trim($n->textContent);
+ break;
+ case 'option':
+ $options[$n->getAttribute('key')] = trim($n->textContent);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path));
}
}
- $this->validate($dom);
-
- return $dom;
- }
-
- /**
- * Validates a loaded XML file.
- *
- * @param \DOMDocument $dom A loaded XML file
- *
- * @throws \InvalidArgumentException When XML doesn't validate its XSD schema
- */
- protected function validate(\DOMDocument $dom)
- {
- $location = __DIR__.'/schema/routing/routing-1.0.xsd';
-
- $current = libxml_use_internal_errors(true);
- libxml_clear_errors();
-
- if (!$dom->schemaValidate($location)) {
- throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($current)));
- }
- libxml_use_internal_errors($current);
- }
-
- /**
- * Retrieves libxml errors and clears them.
- *
- * @return array An array of libxml error strings
- */
- private function getXmlErrors($internalErrors)
- {
- $errors = array();
- foreach (libxml_get_errors() as $error) {
- $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
- LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
- $error->code,
- trim($error->message),
- $error->file ? $error->file : 'n/a',
- $error->line,
- $error->column
- );
- }
-
- libxml_clear_errors();
- libxml_use_internal_errors($internalErrors);
-
- return $errors;
+ return array($defaults, $requirements, $options);
}
}
diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
index 4a530fbb6a..7a5e268b27 100644
--- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php
@@ -21,13 +21,14 @@ use Symfony\Component\Config\Loader\FileLoader;
* YamlFileLoader loads Yaml routing files.
*
* @author Fabien Potencier
+ * @author Tobias Schultze
*
* @api
*/
class YamlFileLoader extends FileLoader
{
private static $availableKeys = array(
- 'type', 'resource', 'prefix', 'pattern', 'options', 'defaults', 'requirements', 'hostname_pattern',
+ 'resource', 'type', 'prefix', 'pattern', 'hostname_pattern', 'defaults', 'requirements', 'options',
);
/**
@@ -38,7 +39,7 @@ class YamlFileLoader extends FileLoader
*
* @return RouteCollection A RouteCollection instance
*
- * @throws \InvalidArgumentException When route can't be parsed
+ * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid
*
* @api
*/
@@ -53,27 +54,19 @@ class YamlFileLoader extends FileLoader
// empty file
if (null === $config) {
- $config = array();
+ return $collection;
}
// not an array
if (!is_array($config)) {
- throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $file));
+ throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path));
}
foreach ($config as $name => $config) {
- $config = $this->normalizeRouteConfig($config);
+ $this->validate($config, $name, $path);
if (isset($config['resource'])) {
- $type = isset($config['type']) ? $config['type'] : null;
- $prefix = isset($config['prefix']) ? $config['prefix'] : null;
- $defaults = isset($config['defaults']) ? $config['defaults'] : array();
- $requirements = isset($config['requirements']) ? $config['requirements'] : array();
- $options = isset($config['options']) ? $config['options'] : array();
- $hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : '';
-
- $this->setCurrentDir(dirname($path));
- $collection->addCollection($this->import($config['resource'], $type, false, $file), $prefix, $defaults, $requirements, $options, $hostnamePattern);
+ $this->parseImport($collection, $config, $path, $file);
} else {
$this->parseRoute($collection, $name, $config, $path);
}
@@ -98,46 +91,90 @@ class YamlFileLoader extends FileLoader
* @param RouteCollection $collection A RouteCollection instance
* @param string $name Route name
* @param array $config Route definition
- * @param string $file A Yaml file path
- *
- * @throws \InvalidArgumentException When config pattern is not defined for the given route
+ * @param string $path Full path of the YAML file being processed
*/
- protected function parseRoute(RouteCollection $collection, $name, $config, $file)
+ protected function parseRoute(RouteCollection $collection, $name, array $config, $path)
{
$defaults = isset($config['defaults']) ? $config['defaults'] : array();
$requirements = isset($config['requirements']) ? $config['requirements'] : array();
$options = isset($config['options']) ? $config['options'] : array();
$hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : null;
- if (!isset($config['pattern'])) {
- throw new \InvalidArgumentException(sprintf('You must define a "pattern" for the "%s" route.', $name));
- }
-
$route = new Route($config['pattern'], $defaults, $requirements, $options, $hostnamePattern);
$collection->add($name, $route);
}
/**
- * Normalize route configuration.
+ * Parses an import and adds the routes in the resource to the RouteCollection.
*
- * @param array $config A resource config
- *
- * @return array
- *
- * @throws \InvalidArgumentException if one of the provided config keys is not supported
+ * @param RouteCollection $collection A RouteCollection instance
+ * @param array $config Route definition
+ * @param string $path Full path of the YAML file being processed
+ * @param string $file Loaded file name
*/
- private function normalizeRouteConfig(array $config)
+ protected function parseImport(RouteCollection $collection, array $config, $path, $file)
{
- foreach ($config as $key => $value) {
- if (!in_array($key, self::$availableKeys)) {
- throw new \InvalidArgumentException(sprintf(
- 'Yaml routing loader does not support given key: "%s". Expected one of the (%s).',
- $key, implode(', ', self::$availableKeys)
- ));
- }
- }
+ $type = isset($config['type']) ? $config['type'] : null;
+ $prefix = isset($config['prefix']) ? $config['prefix'] : '';
+ $defaults = isset($config['defaults']) ? $config['defaults'] : array();
+ $requirements = isset($config['requirements']) ? $config['requirements'] : array();
+ $options = isset($config['options']) ? $config['options'] : array();
+ $hostnamePattern = isset($config['hostname_pattern']) ? $config['hostname_pattern'] : null;
- return $config;
+ $this->setCurrentDir(dirname($path));
+
+ $subCollection = $this->import($config['resource'], $type, false, $file);
+ /* @var $subCollection RouteCollection */
+ $subCollection->addPrefix($prefix);
+ if (null !== $hostnamePattern) {
+ $subCollection->setHostnamePattern($hostnamePattern);
+ }
+ $subCollection->addDefaults($defaults);
+ $subCollection->addRequirements($requirements);
+ $subCollection->addOptions($options);
+
+ $collection->addCollection($subCollection);
+ }
+
+ /**
+ * Validates the route configuration.
+ *
+ * @param array $config A resource config
+ * @param string $name The config key
+ * @param string $path The loaded file path
+ *
+ * @throws \InvalidArgumentException If one of the provided config keys is not supported,
+ * something is missing or the combination is nonesense
+ */
+ protected function validate($config, $name, $path)
+ {
+ if (!is_array($config)) {
+ throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path));
+ }
+ if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The routing file "%s" contains unsupport keys for "%s": "%s". Expected one of: "%s".',
+ $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys)
+ ));
+ }
+ if (isset($config['resource']) && isset($config['pattern'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The routing file "%s" must not specify both the "resource" key and the "pattern" key for "%s". Choose between an import and a route definition.',
+ $path, $name
+ ));
+ }
+ if (!isset($config['resource']) && isset($config['type'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.',
+ $name, $path
+ ));
+ }
+ if (!isset($config['resource']) && !isset($config['pattern'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'You must define a "pattern" for the route "%s" in file "%s".',
+ $name, $path
+ ));
+ }
}
}
diff --git a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
index 5346237edb..3143b3ac72 100644
--- a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
+++ b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
@@ -36,14 +36,14 @@
-
+
-
+
@@ -52,7 +52,7 @@
-
+
diff --git a/src/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php
index e513f04e3b..76612e6184 100644
--- a/src/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php
+++ b/src/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php
@@ -83,6 +83,7 @@ class ApacheUrlMatcher extends UrlMatcher
if (null !== $route) {
$parameters['_route'] = $route;
+
return $this->mergeDefaults($parameters, $defaults);
} elseif (0 < count($allow)) {
throw new MethodNotAllowedException($allow);
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
index 454e26ca7b..586191439f 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
@@ -111,7 +111,6 @@ EOF;
{
$fetchedHostname = false;
- $routes = $this->flattenRouteCollection($routes);
$groups = $this->groupRoutesByHostnameRegex($routes);
$code = '';
@@ -146,7 +145,7 @@ EOF;
*
* @param DumperPrefixCollection $routes A DumperPrefixCollection instance
* @param Boolean $supportsRedirections Whether redirections are supported by the base class
- * @parma string $prefix Prefix of the parent collection
+ * @param string $prefix Prefix of the parent collection
*
* @return string PHP code
*/
@@ -321,31 +320,6 @@ EOF;
return $code;
}
- /**
- * Flattens a tree of routes to a single collection.
- *
- * @param RouteCollection $routes Collection of routes
- * @param DumperCollection|null $to A DumperCollection to add routes to
- *
- * @return DumperCollection
- */
- private function flattenRouteCollection(RouteCollection $routes, DumperCollection $to = null)
- {
- if (null === $to) {
- $to = new DumperCollection();
- }
-
- foreach ($routes as $name => $route) {
- if ($route instanceof RouteCollection) {
- $this->flattenRouteCollection($route, $to);
- } else {
- $to->add(new DumperRoute($name, $route));
- }
- }
-
- return $to;
- }
-
/**
* Groups consecutive routes having the same hostname regex.
*
@@ -355,7 +329,7 @@ EOF;
*
* @return DumperCollection A collection with routes grouped by hostname regex in sub-collections
*/
- private function groupRoutesByHostnameRegex(DumperCollection $routes)
+ private function groupRoutesByHostnameRegex(RouteCollection $routes)
{
$groups = new DumperCollection();
@@ -363,14 +337,14 @@ EOF;
$currentGroup->setAttribute('hostname_regex', null);
$groups->add($currentGroup);
- foreach ($routes as $route) {
- $hostnameRegex = $route->getRoute()->compile()->getHostnameRegex();
+ foreach ($routes as $name => $route) {
+ $hostnameRegex = $route->compile()->getHostnameRegex();
if ($currentGroup->getAttribute('hostname_regex') !== $hostnameRegex) {
$currentGroup = new DumperCollection();
$currentGroup->setAttribute('hostname_regex', $hostnameRegex);
$groups->add($currentGroup);
}
- $currentGroup->add($route);
+ $currentGroup->add(new DumperRoute($name, $route));
}
return $groups;
diff --git a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php
index 1e2491c488..6ef846dd69 100644
--- a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php
+++ b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php
@@ -44,14 +44,6 @@ class TraceableUrlMatcher extends UrlMatcher
protected function matchCollection($pathinfo, RouteCollection $routes)
{
foreach ($routes as $name => $route) {
- if ($route instanceof RouteCollection) {
- if (!$ret = $this->matchCollection($pathinfo, $route)) {
- continue;
- }
-
- return true;
- }
-
$compiledRoute = $route->compile();
if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
index 5c6985447c..c0fea2d7e7 100644
--- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
+++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
@@ -43,7 +43,7 @@ class UrlMatcher implements UrlMatcherInterface
/**
* @var RouteCollection
*/
- private $routes;
+ protected $routes;
/**
* Constructor.
@@ -105,18 +105,6 @@ class UrlMatcher implements UrlMatcherInterface
protected function matchCollection($pathinfo, RouteCollection $routes)
{
foreach ($routes as $name => $route) {
- if ($route instanceof RouteCollection) {
- if (false === strpos($route->getPrefix(), '{') && $route->getPrefix() !== substr($pathinfo, 0, strlen($route->getPrefix()))) {
- continue;
- }
-
- if (!$ret = $this->matchCollection($pathinfo, $route)) {
- continue;
- }
-
- return $ret;
- }
-
$compiledRoute = $route->compile();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
@@ -157,10 +145,30 @@ class UrlMatcher implements UrlMatcherInterface
continue;
}
- return $this->mergeDefaults(array_replace($matches, $hostnameMatches, array('_route' => $name)), $route->getDefaults());
+ return $this->getAttributes($route, $name, array_replace($matches, $hostnameMatches));
}
}
+ /**
+ * Returns an array of values to use as request attributes.
+ *
+ * As this method requires the Route object, it is not available
+ * in matchers that do not have access to the matched Route instance
+ * (like the PHP and Apache matcher dumpers).
+ *
+ * @param Route $route The route we are matching against
+ * @param string $name The name of the route
+ * @param array $attributes An array of attributes from the matcher
+ *
+ * @return array An array of parameters
+ */
+ protected function getAttributes(Route $route, $name, array $attributes)
+ {
+ $attributes['_route'] = $name;
+
+ return $this->mergeDefaults($attributes, $route->getDefaults());
+ }
+
/**
* Handles specific route requirements.
*
diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php
index 38fc3f494a..59f203139c 100644
--- a/src/Symfony/Component/Routing/Route.php
+++ b/src/Symfony/Component/Routing/Route.php
@@ -76,11 +76,6 @@ class Route implements \Serializable
$this->setHostnamePattern($hostnamePattern);
}
- public function __clone()
- {
- $this->compiled = null;
- }
-
public function serialize()
{
return serialize(array(
@@ -145,6 +140,8 @@ class Route implements \Serializable
* Sets the hostname pattern.
*
* @param string $pattern The pattern
+ *
+ * @return Route The current Route instance
*/
public function setHostnamePattern($pattern)
{
diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php
index 9e5f4a1d6f..4df6f7d145 100644
--- a/src/Symfony/Component/Routing/RouteCollection.php
+++ b/src/Symfony/Component/Routing/RouteCollection.php
@@ -14,10 +14,11 @@ namespace Symfony\Component\Routing;
use Symfony\Component\Config\Resource\ResourceInterface;
/**
- * A RouteCollection represents a set of Route instances as a tree structure.
+ * A RouteCollection represents a set of Route instances.
*
- * When adding a route, it overrides existing routes with the
- * same name defined in the instance or its children and parents.
+ * When adding a route at the end of the collection, an existing route
+ * with the same name is removed first. So there can only be one route
+ * with a given name.
*
* @author Fabien Potencier
* @author Tobias Schultze
@@ -27,7 +28,7 @@ use Symfony\Component\Config\Resource\ResourceInterface;
class RouteCollection implements \IteratorAggregate, \Countable
{
/**
- * @var (RouteCollection|Route)[]
+ * @var Route[]
*/
private $routes = array();
@@ -38,11 +39,13 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* @var string
+ * @deprecated since version 2.2, will be removed in 2.3
*/
private $prefix = '';
/**
* @var RouteCollection|null
+ * @deprecated since version 2.2, will be removed in 2.3
*/
private $parent;
@@ -50,9 +53,6 @@ class RouteCollection implements \IteratorAggregate, \Countable
{
foreach ($this->routes as $name => $route) {
$this->routes[$name] = clone $route;
- if ($route instanceof RouteCollection) {
- $this->routes[$name]->setParent($this);
- }
}
}
@@ -60,6 +60,8 @@ class RouteCollection implements \IteratorAggregate, \Countable
* Gets the parent RouteCollection.
*
* @return RouteCollection|null The parent RouteCollection or null when it's the root
+ *
+ * @deprecated since version 2.2, will be removed in 2.3
*/
public function getParent()
{
@@ -67,9 +69,11 @@ class RouteCollection implements \IteratorAggregate, \Countable
}
/**
- * Gets the root RouteCollection of the tree.
+ * Gets the root RouteCollection.
*
* @return RouteCollection The root RouteCollection
+ *
+ * @deprecated since version 2.2, will be removed in 2.3
*/
public function getRoot()
{
@@ -82,9 +86,13 @@ class RouteCollection implements \IteratorAggregate, \Countable
}
/**
- * Gets the current RouteCollection as an Iterator that includes all routes and child route collections.
+ * Gets the current RouteCollection as an Iterator that includes all routes.
*
- * @return \ArrayIterator An \ArrayIterator interface
+ * It implements \IteratorAggregate.
+ *
+ * @see all()
+ *
+ * @return \ArrayIterator An \ArrayIterator object for iterating over routes
*/
public function getIterator()
{
@@ -94,16 +102,11 @@ class RouteCollection implements \IteratorAggregate, \Countable
/**
* Gets the number of Routes in this collection.
*
- * @return int The number of routes in this collection, including nested collections
+ * @return int The number of routes
*/
public function count()
{
- $count = 0;
- foreach ($this->routes as $route) {
- $count += $route instanceof RouteCollection ? count($route) : 1;
- }
-
- return $count;
+ return count($this->routes);
}
/**
@@ -116,32 +119,23 @@ class RouteCollection implements \IteratorAggregate, \Countable
*/
public function add($name, Route $route)
{
- $this->remove($name);
+ unset($this->routes[$name]);
$this->routes[$name] = $route;
}
/**
- * Returns all routes in this collection and its children.
+ * Returns all routes in this collection.
*
* @return Route[] An array of routes
*/
public function all()
{
- $routes = array();
- foreach ($this->routes as $name => $route) {
- if ($route instanceof RouteCollection) {
- $routes = array_merge($routes, $route->all());
- } else {
- $routes[$name] = $route;
- }
- }
-
- return $routes;
+ return $this->routes;
}
/**
- * Gets a route by name defined in this collection or its children.
+ * Gets a route by name.
*
* @param string $name The route name
*
@@ -149,106 +143,102 @@ class RouteCollection implements \IteratorAggregate, \Countable
*/
public function get($name)
{
- if (isset($this->routes[$name])) {
- return $this->routes[$name] instanceof RouteCollection ? null : $this->routes[$name];
- }
-
- foreach ($this->routes as $routes) {
- if ($routes instanceof RouteCollection && null !== $route = $routes->get($name)) {
- return $route;
- }
- }
-
- return null;
+ return isset($this->routes[$name]) ? $this->routes[$name] : null;
}
/**
- * Removes a route or an array of routes by name from all connected
- * collections (this instance and all parents and children).
+ * Removes a route or an array of routes by name from the collection
+ *
+ * For BC it's also removed from the root, which will not be the case in 2.3
+ * as the RouteCollection won't be a tree structure.
*
* @param string|array $name The route name or an array of route names
*/
public function remove($name)
{
+ // just for BC
$root = $this->getRoot();
foreach ((array) $name as $n) {
- $root->removeRecursively($n);
+ unset($root->routes[$n]);
+ unset($this->routes[$n]);
}
}
/**
- * Adds a route collection to the current set of routes (at the end of the current set).
+ * Adds a route collection at the end of the current set by appending all
+ * routes of the added collection.
*
* @param RouteCollection $collection A RouteCollection instance
- * @param string $prefix An optional prefix to add before each pattern of the route collection
- * @param array $defaults An array of default values
- * @param array $requirements An array of requirements
- * @param array $options An array of options
- * @param string $hostnamePattern Hostname pattern
- *
- * @throws \InvalidArgumentException When the RouteCollection already exists in the tree
*
* @api
*/
- public function addCollection(RouteCollection $collection, $prefix = '', $defaults = array(), $requirements = array(), $options = array(), $hostnamePattern = '')
+ public function addCollection(RouteCollection $collection)
{
- // prevent infinite loops by recursive referencing
- $root = $this->getRoot();
- if ($root === $collection || $root->hasCollection($collection)) {
- throw new \InvalidArgumentException('The RouteCollection already exists in the tree.');
+ // This is to keep BC for getParent() and getRoot(). It does not prevent
+ // infinite loops by recursive referencing. But we don't need that logic
+ // anymore as the tree logic has been deprecated and we are just widening
+ // the accepted range.
+ $collection->parent = $this;
+
+ // this is to keep BC
+ $numargs = func_num_args();
+ if ($numargs > 1) {
+ $collection->addPrefix($this->prefix . func_get_arg(1));
+ if ($numargs > 2) {
+ $collection->addDefaults(func_get_arg(2));
+ if ($numargs > 3) {
+ $collection->addRequirements(func_get_arg(3));
+ if ($numargs > 4) {
+ $collection->addOptions(func_get_arg(4));
+ }
+ }
+ }
+ } else {
+ // the sub-collection must have the prefix of the parent (current instance) prepended because it does not
+ // necessarily already have it applied (depending on the order RouteCollections are added to each other)
+ // this will be removed when the BC layer for getPrefix() is removed
+ $collection->addPrefix($this->prefix);
}
- // remove all routes with the same names in all existing collections
- $this->remove(array_keys($collection->all()));
-
- $collection->setParent($this);
- // the sub-collection must have the prefix of the parent (current instance) prepended because it does not
- // necessarily already have it applied (depending on the order RouteCollections are added to each other)
- $collection->addPrefix($this->getPrefix() . $prefix, $defaults, $requirements, $options);
-
- if ('' !== $hostnamePattern) {
- $collection->setHostnamePattern($hostnamePattern);
+ // we need to remove all routes with the same names first because just replacing them
+ // would not place the new route at the end of the merged array
+ foreach ($collection->all() as $name => $route) {
+ unset($this->routes[$name]);
+ $this->routes[$name] = $route;
}
- $this->routes[] = $collection;
+ $this->resources = array_merge($this->resources, $collection->getResources());
}
/**
- * Adds a prefix to all routes in the current set.
+ * Adds a prefix to the path of all child routes.
*
* @param string $prefix An optional prefix to add before each pattern of the route collection
* @param array $defaults An array of default values
* @param array $requirements An array of requirements
- * @param array $options An array of options
*
* @api
*/
- public function addPrefix($prefix, $defaults = array(), $requirements = array(), $options = array())
+ public function addPrefix($prefix, array $defaults = array(), array $requirements = array())
{
$prefix = trim(trim($prefix), '/');
- if ('' === $prefix && empty($defaults) && empty($requirements) && empty($options)) {
+ if ('' === $prefix) {
return;
}
// a prefix must start with a single slash and must not end with a slash
- if ('' !== $prefix) {
- $this->prefix = '/' . $prefix . $this->prefix;
- }
+ $this->prefix = '/' . $prefix . $this->prefix;
+
+ // this is to keep BC
+ $options = func_num_args() > 3 ? func_get_arg(3) : array();
foreach ($this->routes as $route) {
- if ($route instanceof RouteCollection) {
- // we add the slashes so the prefix is not lost by trimming in the sub-collection
- $route->addPrefix('/' . $prefix . '/', $defaults, $requirements, $options);
- } else {
- if ('' !== $prefix) {
- $route->setPattern('/' . $prefix . $route->getPattern());
- }
- $route->addDefaults($defaults);
- $route->addRequirements($requirements);
- $route->addOptions($options);
- }
+ $route->setPattern('/' . $prefix . $route->getPattern());
+ $route->addDefaults($defaults);
+ $route->addRequirements($requirements);
+ $route->addOptions($options);
}
}
@@ -256,6 +246,8 @@ class RouteCollection implements \IteratorAggregate, \Countable
* Returns the prefix that may contain placeholders.
*
* @return string The prefix
+ *
+ * @deprecated since version 2.2, will be removed in 2.3
*/
public function getPrefix()
{
@@ -263,14 +255,66 @@ class RouteCollection implements \IteratorAggregate, \Countable
}
/**
- * Sets the hostname pattern on all child routes.
+ * Sets the hostname pattern on all routes.
*
- * @param string $pattern The pattern
+ * @param string $pattern The pattern
+ * @param array $defaults An array of default values
+ * @param array $requirements An array of requirements
*/
- public function setHostnamePattern($pattern)
+ public function setHostnamePattern($pattern, array $defaults = array(), array $requirements = array())
{
foreach ($this->routes as $route) {
$route->setHostnamePattern($pattern);
+ $route->addDefaults($defaults);
+ $route->addRequirements($requirements);
+ }
+ }
+
+ /**
+ * Adds defaults to all routes.
+ *
+ * An existing default value under the same name in a route will be overridden.
+ *
+ * @param array $defaults An array of default values
+ */
+ public function addDefaults(array $defaults)
+ {
+ if ($defaults) {
+ foreach ($this->routes as $route) {
+ $route->addDefaults($defaults);
+ }
+ }
+ }
+
+ /**
+ * Adds requirements to all routes.
+ *
+ * An existing requirement under the same name in a route will be overridden.
+ *
+ * @param array $requirements An array of requirements
+ */
+ public function addRequirements(array $requirements)
+ {
+ if ($requirements) {
+ foreach ($this->routes as $route) {
+ $route->addRequirements($requirements);
+ }
+ }
+ }
+
+ /**
+ * Adds options to all routes.
+ *
+ * An existing option value under the same name in a route will be overridden.
+ *
+ * @param array $options An array of options
+ */
+ public function addOptions(array $options)
+ {
+ if ($options) {
+ foreach ($this->routes as $route) {
+ $route->addOptions($options);
+ }
}
}
@@ -281,14 +325,7 @@ class RouteCollection implements \IteratorAggregate, \Countable
*/
public function getResources()
{
- $resources = $this->resources;
- foreach ($this->routes as $routes) {
- if ($routes instanceof RouteCollection) {
- $resources = array_merge($resources, $routes->getResources());
- }
- }
-
- return array_unique($resources);
+ return array_unique($this->resources);
}
/**
@@ -300,60 +337,4 @@ class RouteCollection implements \IteratorAggregate, \Countable
{
$this->resources[] = $resource;
}
-
- /**
- * Sets the parent RouteCollection. It's only used internally from one RouteCollection
- * to another. It makes no sense to be available as part of the public API.
- *
- * @param RouteCollection $parent The parent RouteCollection
- */
- private function setParent(RouteCollection $parent)
- {
- $this->parent = $parent;
- }
-
- /**
- * Removes a route by name from this collection and its children recursively.
- *
- * @param string $name The route name
- *
- * @return Boolean true when found
- */
- private function removeRecursively($name)
- {
- // It is ensured by the adders (->add and ->addCollection) that there can
- // only be one route per name in all connected collections. So we can stop
- // iterating recursively on the first hit.
- if (isset($this->routes[$name])) {
- unset($this->routes[$name]);
-
- return true;
- }
-
- foreach ($this->routes as $routes) {
- if ($routes instanceof RouteCollection && $routes->removeRecursively($name)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Checks whether the given RouteCollection is already set in any child of the current instance.
- *
- * @param RouteCollection $collection A RouteCollection instance
- *
- * @return Boolean
- */
- private function hasCollection(RouteCollection $collection)
- {
- foreach ($this->routes as $routes) {
- if ($routes === $collection || $routes instanceof RouteCollection && $routes->hasCollection($collection)) {
- return true;
- }
- }
-
- return false;
- }
}
diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php
index 4b19271666..991ce0985b 100644
--- a/src/Symfony/Component/Routing/RouteCompiler.php
+++ b/src/Symfony/Component/Routing/RouteCompiler.php
@@ -131,7 +131,7 @@ class RouteCompiler implements RouteCompilerInterface
if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
// When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
// quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
- // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
+ // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
// after it. This optimization cannot be applied when the next char is no real separator or when the next variable is
// directly adjacent, e.g. '/{x}{y}'.
$regexp .= '+';
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/missing_id.xml b/src/Symfony/Component/Routing/Tests/Fixtures/missing_id.xml
new file mode 100644
index 0000000000..52719be167
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/missing_id.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/missing_path.xml b/src/Symfony/Component/Routing/Tests/Fixtures/missing_path.xml
new file mode 100644
index 0000000000..a7a8e7b39b
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/missing_path.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/namespaceprefix.xml b/src/Symfony/Component/Routing/Tests/Fixtures/namespaceprefix.xml
new file mode 100644
index 0000000000..efc4da9b37
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/namespaceprefix.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ MyBundle:Blog:show
+ \w+
+ en|fr|de
+ RouteCompiler
+
+
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/nonesense_resource_plus_path.yml b/src/Symfony/Component/Routing/Tests/Fixtures/nonesense_resource_plus_path.yml
new file mode 100644
index 0000000000..eba64e56bb
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/nonesense_resource_plus_path.yml
@@ -0,0 +1,3 @@
+blog_show:
+ resource: validpattern.yml
+ pattern: /test
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/nonesense_type_without_resource.yml b/src/Symfony/Component/Routing/Tests/Fixtures/nonesense_type_without_resource.yml
new file mode 100644
index 0000000000..3b4c76898b
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/nonesense_type_without_resource.yml
@@ -0,0 +1,3 @@
+blog_show:
+ pattern: /blog/{slug}
+ type: custom
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/nonvalid2.yml b/src/Symfony/Component/Routing/Tests/Fixtures/nonvalid2.yml
new file mode 100644
index 0000000000..cfa9992bbc
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/nonvalid2.yml
@@ -0,0 +1 @@
+route: string
diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
index 002d7449b8..42bad2df32 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php
@@ -11,8 +11,6 @@
namespace Symfony\Component\Routing\Tests\Loader;
-use Symfony\Component\Routing\Loader\AnnotationClassLoader;
-
class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
{
protected $loader;
@@ -42,7 +40,6 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
}
/**
- * @covers Symfony\Component\Routing\Loader\AnnotationClassLoader::supports
* @dataProvider provideTestSupportsChecksResource
*/
public function testSupportsChecksResource($resource, $expectedSupports)
@@ -63,9 +60,6 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
);
}
- /**
- * @covers Symfony\Component\Routing\Loader\AnnotationClassLoader::supports
- */
public function testSupportsChecksTypeIfSpecified()
{
$this->assertTrue($this->loader->supports('class', 'annotation'), '->supports() checks the resource type if specified');
diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php
index a8ac733120..29126ba4f2 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php
@@ -40,9 +40,6 @@ class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest
$this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses');
}
- /**
- * @covers Symfony\Component\Routing\Loader\AnnotationDirectoryLoader::supports
- */
public function testSupports()
{
$fixturesDir = __DIR__.'/../Fixtures';
diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php
index 02d0576dbb..f0a8a0e329 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php
@@ -34,9 +34,6 @@ class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest
$this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php');
}
- /**
- * @covers Symfony\Component\Routing\Loader\AnnotationFileLoader::supports
- */
public function testSupports()
{
$fixture = __DIR__.'/../Fixtures/annotated.php';
diff --git a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php
index d9e3ff50d7..64d1b086ef 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php
@@ -24,9 +24,6 @@ class ClosureLoaderTest extends \PHPUnit_Framework_TestCase
}
}
- /**
- * @covers Symfony\Component\Routing\Loader\ClosureLoader::supports
- */
public function testSupports()
{
$loader = new ClosureLoader();
@@ -40,9 +37,6 @@ class ClosureLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($loader->supports($closure, 'foo'), '->supports() checks the resource type if specified');
}
- /**
- * @covers Symfony\Component\Routing\Loader\ClosureLoader::load
- */
public function testLoad()
{
$loader = new ClosureLoader();
diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
index 0ea7b32e17..3ff425d981 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php
@@ -13,7 +13,6 @@ namespace Symfony\Component\Routing\Tests\Loader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\PhpFileLoader;
-use Symfony\Component\Routing\Route;
class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase
{
@@ -24,9 +23,6 @@ class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase
}
}
- /**
- * @covers Symfony\Component\Routing\Loader\PhpFileLoader::supports
- */
public function testSupports()
{
$loader = new PhpFileLoader($this->getMock('Symfony\Component\Config\FileLocator'));
diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
index 235a9d13af..17e33ef297 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php
@@ -13,7 +13,6 @@ namespace Symfony\Component\Routing\Tests\Loader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\XmlFileLoader;
-use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader;
class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
@@ -25,9 +24,6 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
}
}
- /**
- * @covers Symfony\Component\Routing\Loader\XmlFileLoader::supports
- */
public function testSupports()
{
$loader = new XmlFileLoader($this->getMock('Symfony\Component\Config\FileLocator'));
@@ -56,6 +52,21 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('RouteCompiler', $route->getOption('compiler_class'));
}
+ public function testLoadWithNamespacePrefix()
+ {
+ $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
+ $routeCollection = $loader->load('namespaceprefix.xml');
+
+ $this->assertCount(1, $routeCollection, 'One route is loaded');
+ $route = $routeCollection->get('blog_show');
+ $this->assertEquals('/blog/{slug}', $route->getPattern());
+ $this->assertEquals('MyBundle:Blog:show', $route->getDefault('_controller'));
+ $this->assertEquals('\w+', $route->getRequirement('slug'));
+ $this->assertEquals('en|fr|de', $route->getRequirement('_locale'));
+ $this->assertEquals('{_locale}.example.com', $route->getHostnamePattern());
+ $this->assertEquals('RouteCompiler', $route->getOption('compiler_class'));
+ }
+
public function testLoadWithImport()
{
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
@@ -94,7 +105,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
public function getPathsToInvalidFiles()
{
- return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'));
+ return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'), array('missing_id.xml'), array('missing_path.xml'));
}
/**
diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
index e2e0f98eeb..f99e789486 100644
--- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php
@@ -13,7 +13,6 @@ namespace Symfony\Component\Routing\Tests\Loader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
-use Symfony\Component\Routing\Route;
use Symfony\Component\Config\Resource\FileResource;
class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
@@ -29,9 +28,6 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
}
}
- /**
- * @covers Symfony\Component\Routing\Loader\YamlFileLoader::supports
- */
public function testSupports()
{
$loader = new YamlFileLoader($this->getMock('Symfony\Component\Config\FileLocator'));
@@ -54,29 +50,17 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \InvalidArgumentException
+ * @dataProvider getPathsToInvalidFiles
*/
- public function testLoadThrowsExceptionIfNotAnArray()
+ public function testLoadThrowsExceptionWithInvalidFile($filePath)
{
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
- $loader->load('nonvalid.yml');
+ $loader->load($filePath);
}
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testLoadThrowsExceptionIfArrayHasUnsupportedKeys()
+ public function getPathsToInvalidFiles()
{
- $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
- $loader->load('nonvalidkeys.yml');
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testLoadThrowsExceptionWhenIncomplete()
- {
- $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
- $loader->load('incomplete.yml');
+ return array(array('nonvalid.yml'), array('nonvalid2.yml'), array('incomplete.yml'), array('nonvalidkeys.yml'), array('nonesense_resource_plus_path.yml'), array('nonesense_type_without_resource.yml'));
}
public function testLoadSpecialRouteName()
@@ -121,13 +105,4 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('bar', $routes['blog_show']->getOption('foo'));
$this->assertEquals('{locale}.example.com', $routes['blog_show']->getHostnamePattern());
}
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testParseRouteThrowsExceptionWithMissingPattern()
- {
- $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures')));
- $loader->load('incomplete.yml');
- }
}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
index 9c15be5642..e04310cc9a 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php
@@ -135,8 +135,8 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
$collection3 = new RouteCollection();
$collection3->add('overridden2', new Route('/new'));
$collection3->add('hey', new Route('/hey/'));
- $collection1->addCollection($collection2);
$collection2->addCollection($collection3);
+ $collection1->addCollection($collection2);
$collection->addCollection($collection1, '/multi');
// "dynamic" prefix
@@ -216,8 +216,8 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
$collection3 = new RouteCollection();
$collection3->add('c', new Route('/{var}'));
- $collection1->addCollection($collection2, '/b');
$collection2->addCollection($collection3, '/c');
+ $collection1->addCollection($collection2, '/b');
$collection->addCollection($collection1, '/a');
/* test case 2 */
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
index 64ef2d339b..18e11ebcfb 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
@@ -338,6 +338,22 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523'));
}
+ public function testCannotRelyOnPrefix()
+ {
+ $coll = new RouteCollection();
+
+ $subColl = new RouteCollection();
+ $subColl->add('bar', new Route('/bar'));
+ $subColl->addPrefix('/prefix');
+ // overwrite the pattern, so the prefix is not valid anymore for this route in the collection
+ $subColl->get('bar')->setPattern('/new');
+
+ $coll->addCollection($subColl);
+
+ $matcher = new UrlMatcher($coll, new RequestContext());
+ $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new'));
+ }
+
public function testWithHostname()
{
$coll = new RouteCollection();
diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php
index 08cd7a9a26..de1d9b1f39 100644
--- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php
+++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php
@@ -60,8 +60,8 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase
$collection->add('foo', new Route('/foo'));
$collection1 = new RouteCollection();
- $collection->addCollection($collection1);
$collection1->add('foo', new Route('/foo1'));
+ $collection->addCollection($collection1);
$this->assertEquals('/foo1', $this->getFirstNamedRoute($collection, 'foo')->getPattern());
}
@@ -72,8 +72,8 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase
$collection->add('foo', new Route('/foo'));
$collection1 = new RouteCollection();
- $collection->addCollection($collection1);
$collection1->add('foo1', new Route('/foo1'));
+ $collection->addCollection($collection1);
$this->assertCount(2, $collection);
}
@@ -202,19 +202,12 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase
$collection3 = new RouteCollection();
$collection3->add('foo', $new = new Route('/new'));
- $collection1->addCollection($collection2);
$collection2->addCollection($collection3);
-
- $collection1->add('stay', new Route('/stay'));
-
- $iterator = $collection1->getIterator();
+ $collection1->addCollection($collection2);
$this->assertSame($new, $collection1->get('foo'), '->get() returns new route that overrode previous one');
- // size of 2 because collection1 contains collection2 and /stay but not /old anymore
- $this->assertCount(2, $iterator, '->addCollection() removes previous routes when adding new routes with the same name');
- $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $iterator->current(), '->getIterator returns both Routes and RouteCollections');
- $iterator->next();
- $this->assertInstanceOf('Symfony\Component\Routing\Route', $iterator->current(), '->getIterator returns both Routes and RouteCollections');
+ // size of 1 because collection1 contains /new but not /old anymore
+ $this->assertCount(1, $collection1->getIterator(), '->addCollection() removes previous routes when adding new routes with the same name');
}
public function testGet()
@@ -231,63 +224,6 @@ class RouteCollectionTest extends \PHPUnit_Framework_TestCase
$this->assertNull($collection1->get(0), '->get() does not disclose internal child RouteCollection');
}
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testCannotSelfJoinCollection()
- {
- $collection = new RouteCollection();
-
- $collection->addCollection($collection);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testCannotAddExistingCollectionToTree()
- {
- $collection1 = new RouteCollection();
- $collection2 = new RouteCollection();
- $collection3 = new RouteCollection();
-
- $collection1->addCollection($collection2);
- $collection1->addCollection($collection3);
- $collection2->addCollection($collection3);
- }
-
- public function testPatternDoesNotChangeWhenDefinitionOrderChanges()
- {
- $collection1 = new RouteCollection();
- $collection1->add('a', new Route('/a...'));
- $collection2 = new RouteCollection();
- $collection2->add('b', new Route('/b...'));
- $collection3 = new RouteCollection();
- $collection3->add('c', new Route('/c...'));
-
- $rootCollection_A = new RouteCollection();
- $collection2->addCollection($collection3, '/c');
- $collection1->addCollection($collection2, '/b');
- $rootCollection_A->addCollection($collection1, '/a');
-
- // above should mean the same as below
-
- $collection1 = new RouteCollection();
- $collection1->add('a', new Route('/a...'));
- $collection2 = new RouteCollection();
- $collection2->add('b', new Route('/b...'));
- $collection3 = new RouteCollection();
- $collection3->add('c', new Route('/c...'));
-
- $rootCollection_B = new RouteCollection();
- $collection1->addCollection($collection2, '/b');
- $collection2->addCollection($collection3, '/c');
- $rootCollection_B->addCollection($collection1, '/a');
-
- // test it now
-
- $this->assertEquals($rootCollection_A, $rootCollection_B);
- }
-
public function testSetHostnamePattern()
{
$collection = new RouteCollection();
diff --git a/src/Symfony/Component/Security/Core/Util/SecureRandomInterface.php b/src/Symfony/Component/Security/Core/Util/SecureRandomInterface.php
index bb70a7ebcc..2c35a7219e 100644
--- a/src/Symfony/Component/Security/Core/Util/SecureRandomInterface.php
+++ b/src/Symfony/Component/Security/Core/Util/SecureRandomInterface.php
@@ -11,7 +11,6 @@
namespace Symfony\Component\Security\Core\Util;
-
/**
* Interface that needs to be implemented by all secure random number generators.
*
diff --git a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php
index 61d77a8ba8..d54374557b 100644
--- a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php
+++ b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php
@@ -50,9 +50,10 @@ class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandle
$this->logger = $logger;
$this->options = array_merge(array(
- 'failure_path' => null,
- 'failure_forward' => false,
- 'login_path' => '/login',
+ 'failure_path' => null,
+ 'failure_forward' => false,
+ 'login_path' => '/login',
+ 'failure_path_parameter' => '_failure_path'
), $options);
}
@@ -61,6 +62,10 @@ class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandle
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
+ if ($failureUrl = $request->get($this->options['failure_path_parameter'], null, true)) {
+ $this->options['failure_path'] = $failureUrl;
+ }
+
if (null === $this->options['failure_path']) {
$this->options['failure_path'] = $this->options['login_path'];
}
diff --git a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php
index dc7cbe549b..dd7a7d5547 100644
--- a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php
+++ b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php
@@ -58,7 +58,6 @@ class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandle
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
}
-
/**
* Get the provider key.
*
diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
index b1ba24b83f..fddd3c7275 100644
--- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
@@ -75,6 +75,7 @@ class ContextListener implements ListenerInterface
if (null === $session || null === $token = $session->get('_security_'.$this->contextKey)) {
$this->context->setToken(null);
+
return;
}
diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php
index 1c87e770a1..76cfc6af09 100644
--- a/src/Symfony/Component/Security/Http/HttpUtils.php
+++ b/src/Symfony/Component/Security/Http/HttpUtils.php
@@ -106,7 +106,7 @@ class HttpUtils
}
}
- return $path === $request->getPathInfo();
+ return $path === rawurldecode($request->getPathInfo());
}
/**
diff --git a/tests/Symfony/Tests/Component/Security/Http/Firewall/DigestDataTest.php b/src/Symfony/Component/Security/Tests/Http/Firewall/DigestDataTest.php
similarity index 100%
rename from tests/Symfony/Tests/Component/Security/Http/Firewall/DigestDataTest.php
rename to src/Symfony/Component/Security/Tests/Http/Firewall/DigestDataTest.php
diff --git a/src/Symfony/Component/Security/Tests/Http/HttpUtilsTest.php b/src/Symfony/Component/Security/Tests/Http/HttpUtilsTest.php
index a30051fdd0..fc1b754db9 100644
--- a/src/Symfony/Component/Security/Tests/Http/HttpUtilsTest.php
+++ b/src/Symfony/Component/Security/Tests/Http/HttpUtilsTest.php
@@ -97,6 +97,11 @@ class HttpUtilsTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($utils->checkRequestPath($this->getRequest(), '/'));
$this->assertFalse($utils->checkRequestPath($this->getRequest(), '/foo'));
+ $this->assertTrue($utils->checkRequestPath($this->getRequest('/foo%20bar'), '/foo bar'));
+ // Plus must not decoded to space
+ $this->assertTrue($utils->checkRequestPath($this->getRequest('/foo+bar'), '/foo+bar'));
+ // Checking unicode
+ $this->assertTrue($utils->checkRequestPath($this->getRequest(urlencode('/вход')), '/вход'));
$urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
$urlMatcher
diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
index 6c0e2b5b38..27c9005931 100644
--- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
+++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
@@ -243,11 +243,15 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
if ($key === 'item') {
if (isset($value['@key'])) {
- $data[(string) $value['@key']] = $value['#'];
+ if (isset($value['#'])) {
+ $data[(string) $value['@key']] = $value['#'];
+ } else {
+ $data[(string) $value['@key']] = $value;
+ }
} else {
$data['item'][] = $value;
}
- } elseif (array_key_exists($key, $data)) {
+ } elseif (array_key_exists($key, $data) || $key == "entry") {
if ((false === is_array($data[$key])) || (false === isset($data[$key][0]))) {
$data[$key] = array($data[$key]);
}
diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
index 4986f791c9..23cb0217a6 100644
--- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
@@ -251,6 +251,39 @@ class XmlEncoderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
}
+ public function testDecodeWithoutItemHash()
+ {
+ $obj = new ScalarDummy;
+ $obj->xmlFoo = array(
+ 'foo-bar' => array(
+ '@key' => "value",
+ 'item' => array("@key" => 'key', "key-val" => 'val')
+ ),
+ 'Foo' => array(
+ 'Bar' => "Test",
+ '@Type' => 'test'
+ ),
+ 'föo_bär' => 'a',
+ "Bar" => array(1,2,3),
+ 'a' => 'b',
+ );
+ $expected = array(
+ 'foo-bar' => array(
+ '@key' => "value",
+ 'key' => array('@key' => 'key', "key-val" => 'val')
+ ),
+ 'Foo' => array(
+ 'Bar' => "Test",
+ '@Type' => 'test'
+ ),
+ 'föo_bär' => 'a',
+ "Bar" => array(1,2,3),
+ 'a' => 'b',
+ );
+ $xml = $this->encoder->encode($obj, 'xml');
+ $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
+ }
+
public function testPreventsComplexExternalEntities()
{
$oldCwd = getcwd();
diff --git a/src/Symfony/Component/Stopwatch/Stopwatch.php b/src/Symfony/Component/Stopwatch/Stopwatch.php
index 6b65db4ca6..2debd2dfdd 100644
--- a/src/Symfony/Component/Stopwatch/Stopwatch.php
+++ b/src/Symfony/Component/Stopwatch/Stopwatch.php
@@ -279,4 +279,3 @@ class Section
return $this->events;
}
}
-
diff --git a/src/Symfony/Component/Templating/Loader/FilesystemLoader.php b/src/Symfony/Component/Templating/Loader/FilesystemLoader.php
index 9d40b62ec9..563010c6a6 100644
--- a/src/Symfony/Component/Templating/Loader/FilesystemLoader.php
+++ b/src/Symfony/Component/Templating/Loader/FilesystemLoader.php
@@ -89,7 +89,7 @@ class FilesystemLoader extends Loader
*
* @param TemplateReferenceInterface $template A template
* @param integer $time The last modification time of the cached template (timestamp)
- *
+ *
* @return Boolean true if the template is still fresh, false otherwise
*
* @api
diff --git a/src/Symfony/Component/Templating/PhpEngine.php b/src/Symfony/Component/Templating/PhpEngine.php
index 7fbd0a4768..e978e868c6 100644
--- a/src/Symfony/Component/Templating/PhpEngine.php
+++ b/src/Symfony/Component/Templating/PhpEngine.php
@@ -236,7 +236,7 @@ class PhpEngine implements EngineInterface, \ArrayAccess
/**
* Adds some helpers.
- *
+ *
* @param HelperInterface[] $helpers An array of helper
*
* @api
diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md
index 72affba57b..81402f557a 100644
--- a/src/Symfony/Component/Translation/CHANGELOG.md
+++ b/src/Symfony/Component/Translation/CHANGELOG.md
@@ -1,6 +1,16 @@
CHANGELOG
=========
+2.2.0
+-----
+
+ * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3.
+ * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now
+ throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found
+ and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid.
+ * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException
+ (IcuDatFileLoader, IcuResFileLoader and QtFileLoader)
+
2.1.0
-----
diff --git a/src/Symfony/Component/Translation/Exception/ExceptionInterface.php b/src/Symfony/Component/Translation/Exception/ExceptionInterface.php
new file mode 100644
index 0000000000..7757e669a0
--- /dev/null
+++ b/src/Symfony/Component/Translation/Exception/ExceptionInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Exception interface for all exceptions thrown by the component.
+ *
+ * @author Fabien Potencier
+ *
+ * @api
+ */
+interface ExceptionInterface
+{
+}
diff --git a/src/Symfony/Component/Translation/Exception/InvalidResourceException.php b/src/Symfony/Component/Translation/Exception/InvalidResourceException.php
new file mode 100644
index 0000000000..6413f1ae79
--- /dev/null
+++ b/src/Symfony/Component/Translation/Exception/InvalidResourceException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Thrown when a resource cannot be loaded.
+ *
+ * @author Fabien Potencier
+ *
+ * @api
+ */
+class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/src/Symfony/Component/Translation/Exception/NotFoundResourceException.php b/src/Symfony/Component/Translation/Exception/NotFoundResourceException.php
new file mode 100644
index 0000000000..7826e5ce51
--- /dev/null
+++ b/src/Symfony/Component/Translation/Exception/NotFoundResourceException.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Thrown when a resource does not exist.
+ *
+ * @author Fabien Potencier
+ *
+ * @api
+ */
+class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/src/Symfony/Component/Translation/Loader/CsvFileLoader.php b/src/Symfony/Component/Translation/Loader/CsvFileLoader.php
index ce8930f355..bc10188c4c 100644
--- a/src/Symfony/Component/Translation/Loader/CsvFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/CsvFileLoader.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Loader;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -33,16 +35,20 @@ class CsvFileLoader extends ArrayLoader implements LoaderInterface
*/
public function load($resource, $locale, $domain = 'messages')
{
- $messages = array();
-
if (!stream_is_local($resource)) {
- throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $resource));
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ $messages = array();
+
try {
$file = new \SplFileObject($resource, 'rb');
} catch (\RuntimeException $e) {
- throw new \InvalidArgumentException(sprintf('Error opening file "%s".', $resource));
+ throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e);
}
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
diff --git a/src/Symfony/Component/Translation/Loader/IcuDatFileLoader.php b/src/Symfony/Component/Translation/Loader/IcuDatFileLoader.php
index 83c8abaebe..104883b0c5 100644
--- a/src/Symfony/Component/Translation/Loader/IcuDatFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/IcuDatFileLoader.php
@@ -12,6 +12,8 @@
namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -26,12 +28,20 @@ class IcuDatFileLoader extends IcuResFileLoader
*/
public function load($resource, $locale, $domain = 'messages')
{
+ if (!stream_is_local($resource.'.dat')) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource.'.dat')) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
$rb = new \ResourceBundle($locale, $resource);
if (!$rb) {
- throw new \RuntimeException("cannot load this resource : $resource");
+ throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource));
} elseif (intl_is_failure($rb->getErrorCode())) {
- throw new \RuntimeException($rb->getErrorMessage(), $rb->getErrorCode());
+ throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
}
$messages = $this->flatten($rb);
diff --git a/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php b/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php
index 9387596ae6..81b8e0fcec 100644
--- a/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/IcuResFileLoader.php
@@ -12,6 +12,8 @@
namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\DirectoryResource;
/**
@@ -26,12 +28,20 @@ class IcuResFileLoader implements LoaderInterface
*/
public function load($resource, $locale, $domain = 'messages')
{
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!is_dir($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
$rb = new \ResourceBundle($locale, $resource);
if (!$rb) {
- throw new \RuntimeException("cannot load this resource : $resource");
+ throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource));
} elseif (intl_is_failure($rb->getErrorCode())) {
- throw new \RuntimeException($rb->getErrorMessage(), $rb->getErrorCode());
+ throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
}
$messages = $this->flatten($rb);
diff --git a/src/Symfony/Component/Translation/Loader/IniFileLoader.php b/src/Symfony/Component/Translation/Loader/IniFileLoader.php
index cd181709eb..616fa7e0e7 100644
--- a/src/Symfony/Component/Translation/Loader/IniFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/IniFileLoader.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Loader;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -25,8 +27,12 @@ class IniFileLoader extends ArrayLoader implements LoaderInterface
*/
public function load($resource, $locale, $domain = 'messages')
{
- if (!is_file($resource)) {
- throw new \InvalidArgumentException(sprintf('Error opening file "%s".', $resource));
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
$messages = parse_ini_file($resource, true);
diff --git a/src/Symfony/Component/Translation/Loader/LoaderInterface.php b/src/Symfony/Component/Translation/Loader/LoaderInterface.php
index d16705d522..4d9965ce99 100644
--- a/src/Symfony/Component/Translation/Loader/LoaderInterface.php
+++ b/src/Symfony/Component/Translation/Loader/LoaderInterface.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
/**
* LoaderInterface is the interface implemented by all translation loaders.
@@ -32,6 +33,9 @@ interface LoaderInterface
* @return MessageCatalogue A MessageCatalogue instance
*
* @api
+ *
+ * @throws NotFoundResourceException when the resource cannot be found
+ * @throws InvalidResourceException when the resource cannot be loaded
*/
public function load($resource, $locale, $domain = 'messages');
}
diff --git a/src/Symfony/Component/Translation/Loader/MoFileLoader.php b/src/Symfony/Component/Translation/Loader/MoFileLoader.php
index 181915073f..9d1cacb36e 100644
--- a/src/Symfony/Component/Translation/Loader/MoFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/MoFileLoader.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Loader;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -43,6 +45,14 @@ class MoFileLoader extends ArrayLoader implements LoaderInterface
public function load($resource, $locale, $domain = 'messages')
{
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
$messages = $this->parse($resource);
// empty file
@@ -52,7 +62,7 @@ class MoFileLoader extends ArrayLoader implements LoaderInterface
// not an array
if (!is_array($messages)) {
- throw new \InvalidArgumentException(sprintf('The file "%s" must contain a valid mo file.', $resource));
+ throw new InvalidResourceException(sprintf('The file "%s" must contain a valid mo file.', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
@@ -68,7 +78,7 @@ class MoFileLoader extends ArrayLoader implements LoaderInterface
* @param resource $resource
*
* @return array
- * @throws \InvalidArgumentException If stream content has an invalid format.
+ * @throws InvalidResourceException If stream content has an invalid format.
*/
private function parse($resource)
{
@@ -77,7 +87,7 @@ class MoFileLoader extends ArrayLoader implements LoaderInterface
$stat = fstat($stream);
if ($stat['size'] < self::MO_HEADER_SIZE) {
- throw new \InvalidArgumentException("MO stream content has an invalid format.");
+ throw new InvalidResourceException("MO stream content has an invalid format.");
}
$magic = unpack('V1', fread($stream, 4));
$magic = hexdec(substr(dechex(current($magic)), -8));
@@ -87,7 +97,7 @@ class MoFileLoader extends ArrayLoader implements LoaderInterface
} elseif ($magic == self::MO_BIG_ENDIAN_MAGIC) {
$isBigEndian = true;
} else {
- throw new \InvalidArgumentException("MO stream content has an invalid format.");
+ throw new InvalidResourceException("MO stream content has an invalid format.");
}
$formatRevision = $this->readLong($stream, $isBigEndian);
@@ -155,8 +165,8 @@ class MoFileLoader extends ArrayLoader implements LoaderInterface
/**
* Reads an unsigned long from stream respecting endianess.
*
- * @param resource $stream
- * @param boolean $isBigEndian
+ * @param resource $stream
+ * @param boolean $isBigEndian
* @return integer
*/
private function readLong($stream, $isBigEndian)
diff --git a/src/Symfony/Component/Translation/Loader/PhpFileLoader.php b/src/Symfony/Component/Translation/Loader/PhpFileLoader.php
index c80d938a8a..4737d1bf96 100644
--- a/src/Symfony/Component/Translation/Loader/PhpFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/PhpFileLoader.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Loader;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -30,7 +32,11 @@ class PhpFileLoader extends ArrayLoader implements LoaderInterface
public function load($resource, $locale, $domain = 'messages')
{
if (!stream_is_local($resource)) {
- throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $resource));
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
$messages = require($resource);
diff --git a/src/Symfony/Component/Translation/Loader/PoFileLoader.php b/src/Symfony/Component/Translation/Loader/PoFileLoader.php
index c783cb1ecc..58ec6c7a66 100644
--- a/src/Symfony/Component/Translation/Loader/PoFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/PoFileLoader.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Loader;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -21,6 +23,14 @@ class PoFileLoader extends ArrayLoader implements LoaderInterface
{
public function load($resource, $locale, $domain = 'messages')
{
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
$messages = $this->parse($resource);
// empty file
@@ -30,7 +40,7 @@ class PoFileLoader extends ArrayLoader implements LoaderInterface
// not an array
if (!is_array($messages)) {
- throw new \InvalidArgumentException(sprintf('The file "%s" must contain a valid po file.', $resource));
+ throw new InvalidResourceException(sprintf('The file "%s" must contain a valid po file.', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
diff --git a/src/Symfony/Component/Translation/Loader/QtFileLoader.php b/src/Symfony/Component/Translation/Loader/QtFileLoader.php
new file mode 100644
index 0000000000..d64494b81c
--- /dev/null
+++ b/src/Symfony/Component/Translation/Loader/QtFileLoader.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Config\Resource\FileResource;
+
+/**
+ * QtFileLoader loads translations from QT Translations XML files.
+ *
+ * @author Benjamin Eberlei
+ *
+ * @api
+ */
+class QtFileLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @api
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ $dom = new \DOMDocument();
+ $current = libxml_use_internal_errors(true);
+ if (!@$dom->load($resource, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) {
+ throw new InvalidResourceException(implode("\n", $this->getXmlErrors()));
+ }
+
+ $xpath = new \DOMXPath($dom);
+ $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
+
+ $catalogue = new MessageCatalogue($locale);
+ if ($nodes->length == 1) {
+ $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message');
+ foreach ($translations as $translation) {
+ $catalogue->set(
+ (string) $translation->getElementsByTagName('source')->item(0)->nodeValue,
+ (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue,
+ $domain
+ );
+ $translation = $translation->nextSibling;
+ }
+ $catalogue->addResource(new FileResource($resource));
+ }
+
+ libxml_use_internal_errors($current);
+
+ return $catalogue;
+ }
+
+ /**
+ * Returns the XML errors of the internal XML parser
+ *
+ * @return array An array of errors
+ */
+ private function getXmlErrors()
+ {
+ $errors = array();
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
+ LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ $error->code,
+ trim($error->message),
+ $error->file ? $error->file : 'n/a',
+ $error->line,
+ $error->column
+ );
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors(false);
+
+ return $errors;
+ }
+}
diff --git a/src/Symfony/Component/Translation/Loader/QtTranslationsLoader.php b/src/Symfony/Component/Translation/Loader/QtTranslationsLoader.php
index b31169d1a8..5c3dbba817 100644
--- a/src/Symfony/Component/Translation/Loader/QtTranslationsLoader.php
+++ b/src/Symfony/Component/Translation/Loader/QtTranslationsLoader.php
@@ -11,75 +11,16 @@
namespace Symfony\Component\Translation\Loader;
-use Symfony\Component\Config\Resource\FileResource;
-use Symfony\Component\Translation\MessageCatalogue;
-
/**
- * QtTranslationsLoader loads translations from QT Translations XML files.
+ * QtFileLoader loads translations from QT Translations XML files.
*
- * @author Benjamin Eberlei
+ * @author Саша Стаменковић
*
* @api
+ *
+ * @deprecated Deprecated since version 2.2, to be removed in 2.3.
+ * Use QtFileLoader instead.
*/
-class QtTranslationsLoader implements LoaderInterface
-{
- /**
- * {@inheritdoc}
- *
- * @api
- */
- public function load($resource, $locale, $domain = 'messages')
- {
- $dom = new \DOMDocument();
- $current = libxml_use_internal_errors(true);
- if (!@$dom->load($resource, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) {
- throw new \RuntimeException(implode("\n", $this->getXmlErrors()));
- }
-
- $xpath = new \DOMXPath($dom);
- $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
-
- $catalogue = new MessageCatalogue($locale);
- if ($nodes->length == 1) {
- $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message');
- foreach ($translations as $translation) {
- $catalogue->set(
- (string) $translation->getElementsByTagName('source')->item(0)->nodeValue,
- (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue,
- $domain
- );
- $translation = $translation->nextSibling;
- }
- $catalogue->addResource(new FileResource($resource));
- }
-
- libxml_use_internal_errors($current);
-
- return $catalogue;
- }
-
- /**
- * Returns the XML errors of the internal XML parser
- *
- * @return array An array of errors
- */
- private function getXmlErrors()
- {
- $errors = array();
- foreach (libxml_get_errors() as $error) {
- $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
- LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
- $error->code,
- trim($error->message),
- $error->file ? $error->file : 'n/a',
- $error->line,
- $error->column
- );
- }
-
- libxml_clear_errors();
- libxml_use_internal_errors(false);
-
- return $errors;
- }
+class QtTranslationsLoader extends QtFileLoader
+{
}
diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php
index e8bc40d47a..c1e9f1de3a 100644
--- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php
@@ -12,6 +12,8 @@
namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
@@ -31,7 +33,11 @@ class XliffFileLoader implements LoaderInterface
public function load($resource, $locale, $domain = 'messages')
{
if (!stream_is_local($resource)) {
- throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $resource));
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
$xml = $this->parseFile($resource);
@@ -67,7 +73,7 @@ class XliffFileLoader implements LoaderInterface
if (!@$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
libxml_disable_entity_loader($disableEntities);
- throw new \RuntimeException(implode("\n", $this->getXmlErrors($internalErrors)));
+ throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors)));
}
libxml_disable_entity_loader($disableEntities);
@@ -76,7 +82,7 @@ class XliffFileLoader implements LoaderInterface
if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
libxml_use_internal_errors($internalErrors);
- throw new \RuntimeException('Document types are not allowed.');
+ throw new InvalidResourceException('Document types are not allowed.');
}
}
@@ -96,7 +102,7 @@ class XliffFileLoader implements LoaderInterface
$source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source);
if (!@$dom->schemaValidateSource($source)) {
- throw new \RuntimeException(implode("\n", $this->getXmlErrors($internalErrors)));
+ throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors)));
}
$dom->normalizeDocument();
diff --git a/src/Symfony/Component/Translation/Loader/YamlFileLoader.php b/src/Symfony/Component/Translation/Loader/YamlFileLoader.php
index 4ac885b0a2..232ee2bca3 100644
--- a/src/Symfony/Component/Translation/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/Translation/Loader/YamlFileLoader.php
@@ -11,8 +11,11 @@
namespace Symfony\Component\Translation\Loader;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Yaml\Yaml;
+use Symfony\Component\Yaml\Exception\ParseException;
/**
* YamlFileLoader loads translations from Yaml files.
@@ -30,7 +33,19 @@ class YamlFileLoader extends ArrayLoader implements LoaderInterface
*/
public function load($resource, $locale, $domain = 'messages')
{
- $messages = Yaml::parse($resource);
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $messages = Yaml::parse($resource);
+ } catch (ParseException $e) {
+ throw new InvalidResourceException('Error parsing YAML.', 0, $e);
+ }
// empty file
if (null === $messages) {
@@ -39,7 +54,7 @@ class YamlFileLoader extends ArrayLoader implements LoaderInterface
// not an array
if (!is_array($messages)) {
- throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $resource));
+ throw new InvalidResourceException(sprintf('The file "%s" must contain a YAML array.', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
diff --git a/src/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php
index 303e22868f..88570a6024 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php
@@ -46,9 +46,9 @@ class CsvFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
*/
- public function testLoadThrowsAnExceptionIfFileNotExists()
+ public function testLoadNonExistingResource()
{
$loader = new CsvFileLoader();
$resource = __DIR__.'/../fixtures/not-exists.csv';
@@ -56,9 +56,9 @@ class CsvFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
- public function testLoadThrowsAnExceptionIfFileNotLocal()
+ public function testLoadNonLocalResource()
{
$loader = new CsvFileLoader();
$resource = 'http://example.com/resources.csv';
diff --git a/src/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php
index 5dc85f1b56..e4f0a36daa 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php
@@ -25,12 +25,20 @@ class IcuDatFileLoaderTest extends LocalizedTestCase
if (!extension_loaded('intl')) {
$this->markTestSkipped('This test requires intl extension to work.');
}
+ }
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new IcuDatFileLoader();
+ $loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted/resources', 'es', 'domain2');
}
public function testDatEnglishLoad()
{
- // bundled resource is build using pkgdata command which at leas in ICU 4.2 comes in extremely! buggy form
+ // bundled resource is build using pkgdata command which at least in ICU 4.2 comes in extremely! buggy form
// you must specify an temporary build directory which is not the same as current directory and
// MUST reside on the same partition. pkgdata -p resources -T /srv -d . packagelist.txt
$loader = new IcuDatFileLoader();
@@ -54,11 +62,11 @@ class IcuDatFileLoaderTest extends LocalizedTestCase
}
/**
- * @expectedException \RuntimeException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
*/
- public function testLoadInvalidResource()
+ public function testLoadNonExistingResource()
{
$loader = new IcuDatFileLoader();
- $catalogue = $loader->load(__DIR__.'/../fixtures/resourcebundle/res/en.txt', 'en', 'domain1');
+ $loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1');
}
}
diff --git a/src/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php
index eeb84bc0e7..89020e72ae 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php
@@ -25,7 +25,6 @@ class IcuResFileLoaderTest extends LocalizedTestCase
if (!extension_loaded('intl')) {
$this->markTestSkipped('This test requires intl extension to work.');
}
-
}
public function testLoad()
@@ -41,11 +40,20 @@ class IcuResFileLoaderTest extends LocalizedTestCase
}
/**
- * @expectedException \RuntimeException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new IcuResFileLoader();
+ $loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new IcuResFileLoader();
- $catalogue = $loader->load(__DIR__.'/../fixtures/resourcebundle/res/en.txt', 'en', 'domain1');
+ $loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted', 'en', 'domain1');
}
}
diff --git a/src/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php
index 30dd3595ed..d95263f775 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php
@@ -46,12 +46,12 @@ class IniFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
*/
- public function testLoadThrowsAnExceptionIfFileNotExists()
+ public function testLoadNonExistingResource()
{
$loader = new IniFileLoader();
- $resource = __DIR__.'/../fixtures/not-exists.ini';
+ $resource = __DIR__.'/../fixtures/non-existing.ini';
$loader->load($resource, 'en', 'domain1');
}
}
diff --git a/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php
index b90d07cb3a..b309ba5c17 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php
@@ -46,12 +46,22 @@ class MoFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new MoFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.mo';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new MoFileLoader();
$resource = __DIR__.'/../fixtures/empty.mo';
- $catalogue = $loader->load($resource, 'en', 'domain1');
+ $loader->load($resource, 'en', 'domain1');
}
}
diff --git a/src/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php
index e90aa99539..3272c08569 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php
@@ -35,7 +35,17 @@ class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new PhpFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.php';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfFileNotLocal()
{
diff --git a/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php
index 39039120d2..e31a3f8683 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php
@@ -56,6 +56,16 @@ class PoFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new PoFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.po';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
public function testLoadEmptyTranslation()
{
$loader = new PoFileLoader();
diff --git a/src/Symfony/Component/Translation/Tests/Loader/QtTranslationsLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/QtFileLoaderTest.php
similarity index 66%
rename from src/Symfony/Component/Translation/Tests/Loader/QtTranslationsLoaderTest.php
rename to src/Symfony/Component/Translation/Tests/Loader/QtFileLoaderTest.php
index 16c2f78f6f..1dea7c91a7 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/QtTranslationsLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/QtFileLoaderTest.php
@@ -11,10 +11,10 @@
namespace Symfony\Component\Translation\Tests\Loader;
-use Symfony\Component\Translation\Loader\QtTranslationsLoader;
+use Symfony\Component\Translation\Loader\QtFileLoader;
use Symfony\Component\Config\Resource\FileResource;
-class QtTranslationsLoaderTest extends \PHPUnit_Framework_TestCase
+class QtFileLoaderTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
@@ -25,7 +25,7 @@ class QtTranslationsLoaderTest extends \PHPUnit_Framework_TestCase
public function testLoad()
{
- $loader = new QtTranslationsLoader();
+ $loader = new QtFileLoader();
$resource = __DIR__.'/../fixtures/resources.ts';
$catalogue = $loader->load($resource, 'en', 'resources');
@@ -33,4 +33,14 @@ class QtTranslationsLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new QtFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.ts';
+ $loader->load($resource, 'en', 'domain1');
+ }
}
diff --git a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php
index 748c13483f..5f65231f8e 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php
@@ -43,25 +43,35 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \RuntimeException
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new XliffFileLoader();
- $catalogue = $loader->load(__DIR__.'/../fixtures/resources.php', 'en', 'domain1');
+ $loader->load(__DIR__.'/../fixtures/resources.php', 'en', 'domain1');
}
/**
- * @expectedException \RuntimeException
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadResourceDoesNotValidate()
{
$loader = new XliffFileLoader();
- $catalogue = $loader->load(__DIR__.'/../fixtures/non-valid.xlf', 'en', 'domain1');
+ $loader->load(__DIR__.'/../fixtures/non-valid.xlf', 'en', 'domain1');
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new XliffFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.xlf';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfFileNotLocal()
{
@@ -71,7 +81,7 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \RuntimeException
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
* @expectedExceptionMessage Document types are not allowed.
*/
public function testDocTypeIsNotAllowed()
diff --git a/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php
index d954dd3f5e..35fba47dae 100644
--- a/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php
@@ -50,7 +50,27 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
- * @expectedException \InvalidArgumentException
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testLoadNonExistingResource()
+ {
+ $loader = new YamlFileLoader();
+ $resource = __DIR__.'/../fixtures/non-existing.yml';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
+ */
+ public function testLoadThrowsAnExceptionIfFileNotLocal()
+ {
+ $loader = new YamlFileLoader();
+ $resource = 'http://example.com/resources.yml';
+ $loader->load($resource, 'en', 'domain1');
+ }
+
+ /**
+ * @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfNotAnArray()
{
diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php
index 82aedfeeac..9d55cac88b 100644
--- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php
+++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php
@@ -67,6 +67,36 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobar', $translator->trans('bar'));
}
+ /**
+ * @dataProvider getTransFileTests
+ * @expectedException Symfony\Component\Translation\Exception\NotFoundResourceException
+ */
+ public function testTransWithoutFallbackLocaleFile($format, $loader)
+ {
+ $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
+ $translator = new Translator('en', new MessageSelector());
+ $translator->addLoader($format, new $loaderClass());
+ $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en');
+ $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en');
+
+ // force catalogue loading
+ $translator->trans('foo');
+ }
+
+ /**
+ * @dataProvider getTransFileTests
+ */
+ public function testTransWithFallbackLocaleFile($format, $loader)
+ {
+ $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
+ $translator = new Translator('en_GB', new MessageSelector());
+ $translator->addLoader($format, new $loaderClass());
+ $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB');
+ $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources');
+
+ $this->assertEquals('bar', $translator->trans('foo', array(), 'resources'));
+ }
+
public function testTransWithFallbackLocaleBis()
{
$translator = new Translator('en_US', new MessageSelector());
@@ -144,6 +174,20 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
}
+ public function getTransFileTests()
+ {
+ return array(
+ array('csv', 'CsvFileLoader'),
+ array('ini', 'IniFileLoader'),
+ array('mo', 'MoFileLoader'),
+ array('po', 'PoFileLoader'),
+ array('php', 'PhpFileLoader'),
+ array('ts', 'QtFileLoader'),
+ array('xlf', 'XliffFileLoader'),
+ array('yml', 'YamlFileLoader'),
+ );
+ }
+
public function getTransTests()
{
return array(
diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resourcebundle/corrupted/resources.dat b/src/Symfony/Component/Translation/Tests/fixtures/resourcebundle/corrupted/resources.dat
new file mode 100644
index 0000000000..391250caa0
--- /dev/null
+++ b/src/Symfony/Component/Translation/Tests/fixtures/resourcebundle/corrupted/resources.dat
@@ -0,0 +1 @@
+XXX
\ No newline at end of file
diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resourcebundle/res/en.txt b/src/Symfony/Component/Translation/Tests/fixtures/resourcebundle/res/en.txt
deleted file mode 100644
index a8a87e5f44..0000000000
--- a/src/Symfony/Component/Translation/Tests/fixtures/resourcebundle/res/en.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-en {
- foo { "bar" }
-}
\ No newline at end of file
diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php
index ec043a18f6..439a62d34b 100644
--- a/src/Symfony/Component/Translation/Translator.php
+++ b/src/Symfony/Component/Translation/Translator.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Translation;
use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
/**
* Translator.
@@ -181,7 +182,13 @@ class Translator implements TranslatorInterface
protected function loadCatalogue($locale)
{
- $this->doLoadCatalogue($locale);
+ try {
+ $this->doLoadCatalogue($locale);
+ } catch (NotFoundResourceException $e) {
+ if (!$this->computeFallbackLocales($locale)) {
+ throw $e;
+ }
+ }
$this->loadFallbackCatalogues($locale);
}
diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md
index c56f7ba57e..20d499f855 100644
--- a/src/Symfony/Component/Validator/CHANGELOG.md
+++ b/src/Symfony/Component/Validator/CHANGELOG.md
@@ -26,6 +26,8 @@ CHANGELOG
* changed `ValidatorInterface::getMetadataFactory` to return a `MetadataFactoryInterface` instead of a `ClassMetadataFactoryInterface`
* removed `ClassMetadataFactoryInterface` type hint from `ValidatorBuilderInterface::setMetadataFactory`.
As of Symfony 2.3, this method will be typed against `MetadataFactoryInterface` instead.
+ * [BC BREAK] the switches `traverse` and `deep` in the `Valid` constraint and in `GraphWalker::walkReference`
+ are ignored for arrays now. Arrays are always traversed recursively.
2.1.0
-----
diff --git a/src/Symfony/Component/Validator/Constraints/CardScheme.php b/src/Symfony/Component/Validator/Constraints/CardScheme.php
index 356cad930c..0bc1c93afc 100644
--- a/src/Symfony/Component/Validator/Constraints/CardScheme.php
+++ b/src/Symfony/Component/Validator/Constraints/CardScheme.php
@@ -20,7 +20,7 @@ use Symfony\Component\Validator\Constraint;
*/
class CardScheme extends Constraint
{
- public $message = 'Unsupported card type or invalid card number';
+ public $message = 'Unsupported card type or invalid card number.';
public $schemes;
public function getDefaultOption()
diff --git a/src/Symfony/Component/Validator/Constraints/Luhn.php b/src/Symfony/Component/Validator/Constraints/Luhn.php
index f376045fc7..f8fd35c6c8 100644
--- a/src/Symfony/Component/Validator/Constraints/Luhn.php
+++ b/src/Symfony/Component/Validator/Constraints/Luhn.php
@@ -20,5 +20,5 @@ use Symfony\Component\Validator\Constraint;
*/
class Luhn extends Constraint
{
- public $message = 'Invalid card number';
+ public $message = 'Invalid card number.';
}
diff --git a/src/Symfony/Component/Validator/Constraints/Regex.php b/src/Symfony/Component/Validator/Constraints/Regex.php
index f26186cb13..fb5dcc1a73 100644
--- a/src/Symfony/Component/Validator/Constraints/Regex.php
+++ b/src/Symfony/Component/Validator/Constraints/Regex.php
@@ -62,7 +62,7 @@ class Regex extends Constraint
* Convert the htmlPattern to a suitable format for HTML5 pattern.
* Example: /^[a-z]+$/ would be converted to [a-z]+
* However, if options are specified, it cannot be converted
- *
+ *
* Pattern is also ignored if match=false since the pattern should
* then be reversed before application.
*
@@ -78,7 +78,7 @@ class Regex extends Constraint
if (!$this->match) {
return null;
}
-
+
if (preg_match('/^(.)(\^?)(.*?)(\$?)\1$/', $this->pattern, $matches)) {
$delimiter = $matches[1];
$start = empty($matches[2]) ? '.*' : '';
diff --git a/src/Symfony/Component/Validator/ExecutionContext.php b/src/Symfony/Component/Validator/ExecutionContext.php
index 9f23082485..bfcaba891f 100644
--- a/src/Symfony/Component/Validator/ExecutionContext.php
+++ b/src/Symfony/Component/Validator/ExecutionContext.php
@@ -176,9 +176,9 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
- public function getPropertyPath($subPath = null)
+ public function getPropertyPath($subPath = '')
{
- if (null !== $subPath && '' !== $this->propertyPath && '' !== $subPath && '[' !== $subPath[0]) {
+ if ('' != $subPath && '' !== $this->propertyPath && '[' !== $subPath[0]) {
return $this->propertyPath . '.' . $subPath;
}
diff --git a/src/Symfony/Component/Validator/ExecutionContextInterface.php b/src/Symfony/Component/Validator/ExecutionContextInterface.php
index cae91a99b4..0b6c86633d 100644
--- a/src/Symfony/Component/Validator/ExecutionContextInterface.php
+++ b/src/Symfony/Component/Validator/ExecutionContextInterface.php
@@ -142,15 +142,15 @@ interface ExecutionContextInterface
* Any violations generated during the validation will be added to the
* violation list that you can access with {@link getViolations}.
*
- * @param mixed $value The value to validate.
- * @param string $subPath The path to append to the context's property path.
- * @param null $groups The groups to validate in. If you don't pass any
- * groups here, the current group of the context
- * will be used.
- * @param bool $traverse Whether to traverse the value if it is an array
- * or an instance of \Traversable .
- * @param bool $deep Whether to traverse the value recursively if
- * it is a collection of collections.
+ * @param mixed $value The value to validate.
+ * @param string $subPath The path to append to the context's property path.
+ * @param null|string|string[] $groups The groups to validate in. If you don't pass any
+ * groups here, the current group of the context
+ * will be used.
+ * @param Boolean $traverse Whether to traverse the value if it is an array
+ * or an instance of \Traversable .
+ * @param Boolean $deep Whether to traverse the value recursively if
+ * it is a collection of collections.
*/
public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false);
@@ -177,7 +177,7 @@ interface ExecutionContextInterface
* @param mixed $value The value to validate.
* @param Constraint|Constraint[] $constraints The constraint(s) to validate against.
* @param string $subPath The path to append to the context's property path.
- * @param null $groups The groups to validate in. If you don't pass any
+ * @param null|string|string[] $groups The groups to validate in. If you don't pass any
* groups here, the current group of the context
* will be used.
*/
@@ -300,5 +300,5 @@ interface ExecutionContextInterface
* string if the validator is currently validating the
* root value of the validation graph.
*/
- public function getPropertyPath($subPath = null);
+ public function getPropertyPath($subPath = '');
}
diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
index 40dc41ad19..0d2cecbc01 100644
--- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
@@ -82,7 +82,7 @@ class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassB
$this->defaultGroup = $class;
}
}
-
+
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath, $propagatedGroup = null)
{
if (null === $propagatedGroup && Constraint::DEFAULT_GROUP === $group
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
index 4925abb44d..6b25f18817 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
@@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Config\Util\XmlUtils;
class XmlFileLoader extends FileLoader
{
@@ -185,54 +186,12 @@ class XmlFileLoader extends FileLoader
*/
protected function parseFile($file)
{
- $internalErrors = libxml_use_internal_errors(true);
- $disableEntities = libxml_disable_entity_loader(true);
- libxml_clear_errors();
-
- $dom = new \DOMDocument();
- $dom->validateOnParse = true;
- if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
- libxml_disable_entity_loader($disableEntities);
-
- throw new MappingException(implode("\n", $this->getXmlErrors($internalErrors)));
- }
-
- libxml_disable_entity_loader($disableEntities);
-
- if (!$dom->schemaValidate(__DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd')) {
- throw new MappingException(implode("\n", $this->getXmlErrors($internalErrors)));
- }
-
- $dom->normalizeDocument();
-
- libxml_use_internal_errors($internalErrors);
-
- foreach ($dom->childNodes as $child) {
- if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
- throw new MappingException('Document types are not allowed.');
- }
+ try {
+ $dom = XmlUtils::loadFile($file, __DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd');
+ } catch (\Exception $e) {
+ throw new MappingException($e->getMessage(), $e->getCode(), $e);
}
return simplexml_import_dom($dom);
}
-
- protected function getXmlErrors($internalErrors)
- {
- $errors = array();
- foreach (libxml_get_errors() as $error) {
- $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
- LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
- $error->code,
- trim($error->message),
- $error->file ? $error->file : 'n/a',
- $error->line,
- $error->column
- );
- }
-
- libxml_clear_errors();
- libxml_use_internal_errors($internalErrors);
-
- return $errors;
- }
}
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf
new file mode 100644
index 0000000000..e6a29653b1
--- /dev/null
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf
@@ -0,0 +1,227 @@
+
+
+
+
+
+ This value should be false.
+ Hierdie waarde moet vals wees.
+
+
+ This value should be true.
+ Hierdie waarde moet waar wees.
+
+
+ This value should be of type {{ type }}.
+ Hierdie waarde moet van die soort {{type}} wees.
+
+
+ This value should be blank.
+ Hierdie waarde moet leeg wees.
+
+
+ The value you selected is not a valid choice.
+ Die waarde wat jy gekies het is nie 'n geldige keuse nie.
+
+
+ You must select at least {{ limit }} choices.
+ Jy moet ten minste {{ limit }} kies.|Jy moet ten minste {{ limit }} keuses kies.
+
+
+ You must select at most {{ limit }} choices.
+ Jy moet by die meeste {{ limit }} keuse kies.|Jy moet by die meeste {{ limit }} keuses kies.
+
+
+ One or more of the given values is invalid.
+ Een of meer van die gegewe waardes is ongeldig.
+
+
+ The fields {{ fields }} were not expected.
+ Die velde {{ fields }} is nie verwag nie.
+
+
+ The fields {{ fields }} are missing.
+ Die velde {{ fields }} ontbreek.
+
+
+ This value is not a valid date.
+ Hierdie waarde is nie 'n geldige datum nie.
+
+
+ This value is not a valid datetime.
+ Hierdie waarde is nie 'n geldige datum en tyd nie.
+
+
+ This value is not a valid email address.
+ Hierdie waarde is nie 'n geldige e-pos adres nie.
+
+
+ The file could not be found.
+ Die lêer kon nie gevind word nie.
+
+
+ The file is not readable.
+ Die lêer kan nie gelees word nie.
+
+
+ The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.
+ Die lêer is te groot ({{ size }} {{ suffix }}). Toegelaat maksimum grootte is {{ limit }} {{ suffix }}.
+
+
+ The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.
+ Die MIME-tipe van die lêer is ongeldig ({{ type }}). Toegelaat MIME-tipes is {{ types }}.
+
+
+ This value should be {{ limit }} or less.
+ Hierdie waarde moet {{ limit }} of minder wees.
+
+
+ This value is too long. It should have {{ limit }} characters or less.
+ Hierdie waarde is te lank. Dit moet {{ limit }} karakter of minder wees.|Hierdie waarde is te lank. Dit moet {{ limit }} karakters of minder wees.
+
+
+ This value should be {{ limit }} or more.
+ Hierdie waarde moet {{ limit }} of meer wees.
+
+
+ This value is too short. It should have {{ limit }} characters or more.
+ Hierdie waarde is te kort. Dit moet {{ limit }} karakter of meer wees.|Hierdie waarde is te kort. Dit moet {{ limit }} karakters of meer wees.
+
+
+ This value should not be blank.
+ Hierdie waarde moet nie leeg wees nie.
+
+
+ This value should not be null.
+ Hierdie waarde moet nie nul wees nie.
+
+
+ This value should be null.
+ Hierdie waarde moet nul wees.
+
+
+ This value is not valid.
+ Hierdie waarde is nie geldig nie.
+
+
+ This value is not a valid time.
+ Hierdie waarde is nie 'n geldige tyd nie.
+
+
+ This value is not a valid URL.
+ Hierdie waarde is nie 'n geldige URL nie.
+
+
+ The two values should be equal.
+ Die twee waardes moet gelyk wees.
+
+
+ The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.
+ Die lêer is te groot. Toegelaat maksimum grootte is {{ limit }} {{ suffix }}.
+
+
+ The file is too large.
+ Die lêer is te groot.
+
+
+ The file could not be uploaded.
+ Die lêer kan nie opgelaai word nie.
+
+
+ This value should be a valid number.
+ Hierdie waarde moet 'n geldige nommer wees.
+
+
+ This file is not a valid image.
+ Hierdie lêer is nie 'n geldige beeld nie.
+
+
+ This is not a valid IP address.
+ Hierdie is nie 'n geldige IP-adres nie.
+
+
+ This value is not a valid language.
+ Hierdie waarde is nie 'n geldige taal nie.
+
+
+ This value is not a valid locale.
+ Hierdie waarde is nie 'n geldige land instelling nie.
+
+
+ This value is not a valid country.
+ Hierdie waarde is nie 'n geldige land nie.
+
+
+ This value is already used.
+ Hierdie waarde word reeds gebruik.
+
+
+ The size of the image could not be detected.
+ Die grootte van die beeld kon nie opgespoor word nie.
+
+
+ The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.
+ Die beeld breedte is te groot ({{ width }}px). Toegelaat maksimum breedte is {{ max_width }}px.
+
+
+ The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.
+ Die beeld breedte is te klein ({{ width }}px). Minimum breedte verwag is {{ min_width }}px.
+
+
+ The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.
+ Die beeld hoogte is te groot ({{ height }}px). Toegelaat maksimum hoogte is {{ max_height }}px.
+
+
+ The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.
+ Die beeld hoogte is te klein ({{ height }}px). Minimum hoogte verwag is {{ min_height }}px.
+
+
+ This value should be the user current password.
+ Hierdie waarde moet die huidige wagwoord van die gebruiker wees.
+
+
+ This value should have exactly {{ limit }} characters.
+ Hierdie waarde moet presies {{ limit }} karakter wees.|Hierdie waarde moet presies {{ limit }} karakters wees.
+
+
+ The file was only partially uploaded.
+ Die lêer is slegs gedeeltelik opgelaai.
+
+
+ No file was uploaded.
+ Geen lêer is opgelaai nie.
+
+
+ No temporary folder was configured in php.ini.
+ Geen tydelike lêer is ingestel in php.ini nie.
+
+
+ Cannot write temporary file to disk.
+ Kan nie tydelike lêer skryf op skyf nie.
+
+
+ A PHP extension caused the upload to fail.
+ 'n PHP-uitbreiding veroorsaak die oplaai van die lêer om te misluk.
+
+
+ This collection should contain {{ limit }} elements or more.
+ Hierdie versameling moet {{ limit }} element of meer bevat.|Hierdie versameling moet {{ limit }} elemente of meer bevat.
+
+
+ This collection should contain {{ limit }} elements or less.
+ Hierdie versameling moet {{ limit }} element of minder bevat.|Hierdie versameling moet {{ limit }} elemente of meer bevat.
+
+
+ This collection should contain exactly {{ limit }} elements.
+ Hierdie versameling moet presies {{ limit }} element bevat.|Hierdie versameling moet presies {{ limit }} elemente bevat.
+
+
+ Invalid card number.
+ Ongeldige kredietkaart nommer.
+
+
+ Unsupported card type or invalid card number.
+ Nie-ondersteunde tipe kaart of ongeldige kredietkaart nommer.
+
+
+
+
diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf
index 6e78a47e5a..687598d825 100644
--- a/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf
+++ b/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf
@@ -214,6 +214,14 @@
This collection should contain exactly {{ limit }} elements.
Aquesta col·lecció ha de contenir exactament {{ limit }} element.|Aquesta col·lecció ha de contenir exactament {{ limit }} elements.
+
+ Invalid card number.
+ Número de targeta invàlid.
+
+
+ Unsupported card type or invalid card number.
+ Tipus de targeta no suportada o número de targeta invàlid.
+
This value should be false.
- Nilai ini harus salah.
+ Nilai ini harus bernilai salah.
This value should be true.
- Nilai ini harus benar.
+ Nilai ini harus bernilai benar.
This value should be of type {{ type }}.
@@ -20,7 +20,7 @@
The value you selected is not a valid choice.
- Nilai ini harus salah satu dari pilihan yang diberikan.
+ Nilai yang dipilih tidak tepat.
You must select at least {{ limit }} choices.
@@ -64,11 +64,11 @@
The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.
- Ukuran berkas terlalu besar ({{ size }} {{ suffix }}). Ukuran maksimum yang diijinkan adalah {{ limit }} {{ suffix }}.
+ Ukuran berkas terlalu besar ({{ size }} {{ suffix }}). Ukuran maksimum yang diizinkan adalah {{ limit }} {{ suffix }}.
The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.
- Jenis berkas tidak sah ({{ type }}). Jenis berkas yang diijinkan adalah {{ types }}.
+ Jenis berkas ({{ type }}) tidak sah. Jenis berkas yang diijinkan adalah {{ types }}.
This value should be {{ limit }} or less.
@@ -182,6 +182,46 @@
This value should have exactly {{ limit }} characters.
Nilai ini harus memiliki tepat {{ limit }} karakter.
+
+ The file was only partially uploaded.
+ Berkas hanya terunggah sebagian.
+
+
+ No file was uploaded.
+ Tidak ada berkas terunggah.
+
+
+ No temporary folder was configured in php.ini.
+ Direktori sementara tidak dikonfiguasi pada php.ini
+
+
+ Cannot write temporary file to disk.
+ Tidak dapat menuliskan berkas sementara ke dalam media penyimpanan.
+
+
+ A PHP extension caused the upload to fail.
+ Sebuah ekstensi PHP menyebabkan kegagalan unggah.
+
+
+ This collection should contain {{ limit }} elements or more.
+ Kumpulan ini harus memiliki {{ limit }} elemen atau lebih.
+
+
+ This collection should contain {{ limit }} elements or less.
+ Kumpulan ini harus memiliki kurang dari {{ limit }} elemen.
+
+
+ This collection should contain exactly {{ limit }} elements.
+ Kumpulan ini harus memiliki tepat {{ limit }} elemen.
+
+
+ Invalid card number.
+ Nomor kartu tidak sah.
+
+
+ Unsupported card type or invalid card number.
+ Jenis kartu tidak didukung atau nomor kartu tidak sah.
+