Merge branch 'master' of github.com:symfony/symfony into deprecationErrors
This commit is contained in:
commit
6b105504f4
|
@ -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)
|
||||
|
|
|
@ -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?
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
-----
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -86,4 +86,4 @@ class DoctrineOrmTypeGuesserTest extends \PHPUnit_Framework_TestCase
|
|||
return new DoctrineOrmTypeGuesser($registry);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
namespace Symfony\Bridge\Doctrine\Tests\Logger;
|
||||
|
||||
|
||||
class DbalLoggerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -161,7 +161,7 @@ class PropelLogger
|
|||
/**
|
||||
* Returns queries.
|
||||
*
|
||||
* @return array Queries
|
||||
* @return array Queries
|
||||
*/
|
||||
public function getQueries()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label>
|
||||
{% 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{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label>
|
||||
{% endspaceless %}
|
||||
{% endblock form_label %}
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
|
@ -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
|
||||
-----
|
||||
|
|
|
@ -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[] = '<comment>'.$tagName.'</comment>';
|
||||
}
|
||||
$output->writeln(vsprintf($format1, $this->buildArgumentsArray('<comment>Service Id</comment>', '<comment>Scope</comment>', '<comment>Class Name</comment>', $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;
|
||||
|
|
|
@ -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('<comment>Name</comment> %s', $name));
|
||||
$output->writeln(sprintf('<comment>Pattern</comment> %s', $route->getPattern()));
|
||||
$output->writeln(sprintf('<comment>HostnamePattern</comment> %s', $hostname));
|
||||
$output->writeln(sprintf('<comment>Class</comment> %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('<comment>Defaults</comment> %s', $defaults));
|
||||
$output->writeln(sprintf('<comment>Defaults</comment> %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('<comment>Requirements</comment> %s', $requirements));
|
||||
$requirements = '' !== $requirements ? $requirements : 'NONE';
|
||||
$output->writeln(sprintf('<comment>Requirements</comment> %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('<comment>Options</comment> %s', $options));
|
||||
$output->write('<comment>Regex</comment> ');
|
||||
$output->writeln(sprintf('<comment>Options</comment> %s', $options));
|
||||
$output->write('<comment>Regex</comment> ');
|
||||
$output->writeln(preg_replace('/^ /', '', preg_replace('/^/m', ' ', $route->compile()->getRegex())), OutputInterface::OUTPUT_RAW);
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<parameter key="debug.stopwatch.class">Symfony\Component\Stopwatch\Stopwatch</parameter>
|
||||
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
|
||||
<parameter key="debug.controller_resolver.class">Symfony\Component\HttpKernel\Controller\TraceableControllerResolver</parameter>
|
||||
<parameter key="debug.deprecation_logger_listener.class">Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
|
@ -26,5 +27,11 @@
|
|||
<argument type="service" id="controller_resolver" />
|
||||
<argument type="service" id="debug.stopwatch" />
|
||||
</service>
|
||||
|
||||
<service id="debug.deprecation_logger_listener" class="%debug.deprecation_logger_listener.class%">
|
||||
<tag name="kernel.event_subscriber" />
|
||||
<tag name="monolog.logger" channel="deprecation" />
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<parameter key="translation.loader.xliff.class">Symfony\Component\Translation\Loader\XliffFileLoader</parameter>
|
||||
<parameter key="translation.loader.po.class">Symfony\Component\Translation\Loader\PoFileLoader</parameter>
|
||||
<parameter key="translation.loader.mo.class">Symfony\Component\Translation\Loader\MoFileLoader</parameter>
|
||||
<parameter key="translation.loader.qt.class">Symfony\Component\Translation\Loader\QtTranslationsLoader</parameter>
|
||||
<parameter key="translation.loader.qt.class">Symfony\Component\Translation\Loader\QtFileLoader</parameter>
|
||||
<parameter key="translation.loader.csv.class">Symfony\Component\Translation\Loader\CsvFileLoader</parameter>
|
||||
<parameter key="translation.loader.res.class">Symfony\Component\Translation\Loader\IcuResFileLoader</parameter>
|
||||
<parameter key="translation.loader.dat.class">Symfony\Component\Translation\Loader\IcuDatFileLoader</parameter>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<?php if (false !== $label): ?>
|
||||
<?php if ($required) { $label_attr['class'] = trim((isset($label_attr['class']) ? $label_attr['class'] : '').' required'); } ?>
|
||||
<?php if (!$compound) { $label_attr['for'] = $id; } ?>
|
||||
<?php if (!$label) { $label = $view['form']->humanize($name); } ?>
|
||||
<label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label>
|
||||
<?php endif ?>
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 <fabien@symfony.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <daniel@dantleech.com>
|
||||
*/
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
;
|
||||
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
<parameters>
|
||||
<parameter key="twig.class">Twig_Environment</parameter>
|
||||
<parameter key="twig.loader.filesystem.class">Symfony\Bundle\TwigBundle\Loader\FilesystemLoader</parameter>
|
||||
<parameter key="twig.loader.chain.class">Twig_Loader_Chain</parameter>
|
||||
<parameter key="templating.engine.twig.class">Symfony\Bundle\TwigBundle\TwigEngine</parameter>
|
||||
<parameter key="twig.cache_warmer.class">Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer</parameter>
|
||||
<parameter key="twig.extension.trans.class">Symfony\Bridge\Twig\Extension\TranslationExtension</parameter>
|
||||
<parameter key="twig.extension.assets.class">Symfony\Bundle\TwigBundle\Extension\AssetsExtension</parameter>
|
||||
<parameter key="twig.extension.actions.class">Symfony\Bundle\TwigBundle\Extension\ActionsExtension</parameter>
|
||||
<parameter key="twig.extension.code.class">Symfony\Bundle\TwigBundle\Extension\CodeExtension</parameter>
|
||||
<parameter key="twig.extension.code.class">Symfony\Bridge\Twig\Extension\CodeExtension</parameter>
|
||||
<parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter>
|
||||
<parameter key="twig.extension.yaml.class">Symfony\Bridge\Twig\Extension\YamlExtension</parameter>
|
||||
<parameter key="twig.extension.form.class">Symfony\Bridge\Twig\Extension\FormExtension</parameter>
|
||||
|
@ -20,6 +21,7 @@
|
|||
<parameter key="twig.form.renderer.class">Symfony\Bridge\Twig\Form\TwigRenderer</parameter>
|
||||
<parameter key="twig.translation.extractor.class">Symfony\Bridge\Twig\Translation\TwigExtractor</parameter>
|
||||
<parameter key="twig.exception_listener.class">Symfony\Component\HttpKernel\EventListener\ExceptionListener</parameter>
|
||||
<parameter key="twig.controller.exception.class">Symfony\Bundle\TwigBundle\Controller\ExceptionController</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
|
@ -41,8 +43,11 @@
|
|||
<service id="twig.loader.filesystem" class="%twig.loader.filesystem.class%" public="false">
|
||||
<argument type="service" id="templating.locator" />
|
||||
<argument type="service" id="templating.name_parser" />
|
||||
<tag name="twig.loader"/>
|
||||
</service>
|
||||
|
||||
<service id="twig.loader.chain" class="%twig.loader.chain.class%" public="false"/>
|
||||
|
||||
<service id="twig.loader" alias="twig.loader.filesystem" />
|
||||
|
||||
<service id="templating.engine.twig" class="%templating.engine.twig.class%" public="false">
|
||||
|
@ -107,5 +112,10 @@
|
|||
<argument>%twig.exception_listener.controller%</argument>
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<service id="twig.controller.exception" class="%twig.controller.exception.class%">
|
||||
<argument type="service" id="twig" />
|
||||
<argument>%kernel.debug%</argument>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace symfony\src\Symfony\Bundle\TwigBundle\Tests\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass;
|
||||
|
||||
class TwigLoaderPassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
|
|
@ -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 <fabien@symfony.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<parameters>
|
||||
<parameter key="web_profiler.controller.profiler.class">Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController</parameter>
|
||||
<parameter key="web_profiler.controller.router.class">Symfony\Bundle\WebProfilerBundle\Controller\RouterController</parameter>
|
||||
<parameter key="web_profiler.controller.exception.class">Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
|
@ -23,5 +24,11 @@
|
|||
<argument type="service" id="twig" />
|
||||
<argument type="service" id="router" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<service id="web_profiler.controller.exception" class="%web_profiler.controller.exception.class%">
|
||||
<argument type="service" id="profiler" />
|
||||
<argument type="service" id="twig" />
|
||||
<argument>%kernel.debug%</argument>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% block head %}
|
||||
<style type="text/css">
|
||||
{% include '@WebProfiler/Collector/exception.css.twig' %}
|
||||
{% render 'web_profiler.controller.exception:cssAction' with { 'token': token } %}
|
||||
</style>
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
@ -28,7 +28,7 @@
|
|||
</p>
|
||||
{% else %}
|
||||
<div class="sf-reset">
|
||||
{% render 'WebProfilerBundle:Exception:show' with { 'exception': collector.exception, 'format': 'html' } %}
|
||||
{% render 'web_profiler.controller.exception:showAction' with { 'token': token } %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 %}
|
||||
<img width="15" height="28" alt="Logs" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAAA4klEQVQ4y2P4//8/AyWYYXgYwOPp6Xnc3t7+P7EYpB6k7+zZs2ADNEjRjIwDAgKWgAywIUfz8+fPVzg7O/8AGeCATQEQnAfi/SAah/wcV1dXvAYUgORANA75ehcXl+/4DHAABRIe+ZrhbgAhTHsDiEgHBA0glA6GfSDiw5mZma+A+sphBlhVVFQ88vHx+Xfu3Ll7QP5haOjjwtuAuGHv3r3NIMNABqh8+/atsaur666vr+9XUlwSHx//AGQANxCbAnEWyGQicRMQ9wBxIQM0qjiBWAFqkB00/glhayBWHwb1AgB38EJsUtxtWwAAAABJRU5ErkJggg=="/>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-yellow">{{ collector.counterrors }}</span>
|
||||
{% if collector.counterrors %}
|
||||
{% set status_color = "red" %}
|
||||
{% else %}
|
||||
{% set status_color = "yellow" %}
|
||||
{% endif %}
|
||||
{% set error_count = collector.counterrors + collector.countdeprecations %}
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ status_color }}">{{ error_count }}</span>
|
||||
{% endset %}
|
||||
{% set text %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Exception</b>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-yellow">{{ collector.counterrors }}</span>
|
||||
</div>
|
||||
{% if collector.counterrors %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Exception</b>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-red">{{ collector.counterrors }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if collector.countdeprecations %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Deprecated Calls</b>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-yellow">{{ collector.countdeprecations }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endset %}
|
||||
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
|
||||
{% endif %}
|
||||
|
@ -20,9 +36,10 @@
|
|||
<span class="label">
|
||||
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAgCAYAAAAMq2gFAAABjElEQVRIx2MIDw+vd3R0/GFvb/+fGtjFxeVJSUmJ1f///5nv37/PAMMMzs7OVLMEhoODgy/k5+cHJCYmagAtZAJbRG1L0DEwxCYALeOgiUXbt2+/X1NT8xTEdnd3/wi0SI4mFgHBDCBeCLXoF5BtwkCEpvNAvB8JnydCTwgQR0It+g1kWxNjUQEQOyDhAiL0gNUiWWRDjEUOyMkUZsCoRaMWjVpEvEVkFkGjFmEUqgc+fvx4hVYWIReqzi9evKileaoDslnu3LkTNLQtGk3edLPIycnpL9Bge5pb1NXVdQNosDmGRcAm7F+QgKur6783b95cBQoeRGv1kII3QPOdAoZF8+fPP4PUqnx55syZVKCEI1rLh1hsAbWEZ8aMGaUoFoFcMG3atKdIjfSPISEhawICAlaQgwMDA1f6+/sfB5rzE2Sej4/PD3C7DkjoAHHVoUOHLpSVlX3w8vL6Sa34Alr6Z8WKFaCoMARZxAHEoFZ/HBD3A/FyIF4BxMvIxCC964F4G6hZDMTxQCwJAGWE8pur5kFDAAAAAElFTkSuQmCC" alt="Logger" /></span>
|
||||
<strong>Logs</strong>
|
||||
{% if collector.counterrors %}
|
||||
{% if collector.counterrors or collector.countdeprecations %}
|
||||
{% set error_count = collector.counterrors + collector.countdeprecations %}
|
||||
<span class="count">
|
||||
<span>{{ collector.counterrors }}</span>
|
||||
<span>{{ error_count }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
@ -41,8 +58,9 @@
|
|||
<input type="hidden" name="panel" value="logger" />
|
||||
<label for="priority">Priority</label>
|
||||
<select id="priority" name="priority" onchange="document.getElementById('priority-form').submit(); ">
|
||||
{% for value, text in { 100: 'DEBUG', 200: 'INFO', 250: 'NOTICE', 300: 'WARNING', 400: 'ERROR', 500: 'CRITICAL', 550: 'ALERT', 600: 'EMERGENCY' } %}
|
||||
<option value="{{ value }}"{{ value == priority ? ' selected' : '' }}>{{ text }}</option>
|
||||
{# 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' } %}
|
||||
<option value="{{ value }}"{{ value == priority ? ' selected' : '' }}>{{ text }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<noscript>
|
||||
|
@ -55,15 +73,9 @@
|
|||
|
||||
{% if collector.logs %}
|
||||
<ul class="alt">
|
||||
{% 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 %}
|
||||
<li class="{{ cycle(['odd', 'even'], loop.index) }}{% if log.priority >= 400 %} error{% elseif log.priority >= 300 %} warning{% endif %}">
|
||||
{{ log.priorityName }} - {{ log.message }}
|
||||
{% if log.context is defined and log.context is not empty %}
|
||||
<br />
|
||||
<small>
|
||||
<strong>Context</strong>: {{ log.context|yaml_encode }}
|
||||
</small>
|
||||
{% endif %}
|
||||
{{ logger.display_message(loop.index, log) }}
|
||||
</li>
|
||||
{% else %}
|
||||
<li><em>No logs available for this priority.</em></li>
|
||||
|
@ -75,3 +87,35 @@
|
|||
</p>
|
||||
{% 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 %}
|
||||
<a href="#" onclick="Sfjs.toggle('{{ id }}', document.getElementById('{{ id }}-on'), document.getElementById('{{ id }}-off')); return false;">
|
||||
<img class="toggle" id="{{ id }}-off" alt="-" src="data:image/gif;base64,R0lGODlhEgASAMQSANft94TG57Hb8GS44ez1+mC24IvK6ePx+Wa44dXs92+942e54o3L6W2844/M6dnu+P/+/l614P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABIALAAAAAASABIAQAVCoCQBTBOd6Kk4gJhGBCTPxysJb44K0qD/ER/wlxjmisZkMqBEBW5NHrMZmVKvv9hMVsO+hE0EoNAstEYGxG9heIhCADs=" style="display:none" />
|
||||
<img class="toggle" id="{{ id }}-on" alt="+" src="data:image/gif;base64,R0lGODlhEgASAMQTANft99/v+Ga44bHb8ITG52S44dXs9+z1+uPx+YvK6WC24G+944/M6W28443L6dnu+Ge54v/+/l614P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABMALAAAAAASABIAQAVS4DQBTiOd6LkwgJgeUSzHSDoNaZ4PU6FLgYBA5/vFID/DbylRGiNIZu74I0h1hNsVxbNuUV4d9SsZM2EzWe1qThVzwWFOAFCQFa1RQq6DJB4iIQA7" style="display:inline" />
|
||||
</a>
|
||||
{% for index, call in log.context.stack if index > 0 %}
|
||||
{% if index == 1 %}
|
||||
<ul class="sf-call-stack" id="{{ id }}" style="display: none">
|
||||
{% 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 %}
|
||||
|
||||
<li>Called from {{ call.file is defined and call.line is defined ? call.file|format_file(call.line, from) : from|raw }}</li>
|
||||
|
||||
{{ index == log.context.stack|length - 1 ? '</ul>' : '' }}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{ log.priorityName }} - {{ log.message }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div id="collector-wrapper">
|
||||
{% if profile %}
|
||||
<div id="resume">
|
||||
<a id="resume-view-all" href="{{ path('_profiler_search', {limit: 10}) }}">View all</a>
|
||||
<a id="resume-view-all" href="{{ path('_profiler_search', {limit: 10}) }}">View last 10</a>
|
||||
<strong>Profile for:</strong>
|
||||
{{ profile.method|upper }}
|
||||
{% if profile.method|upper in ['GET', 'HEAD'] %}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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\\": "" }
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony WebProfilerBundle Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./Tests</directory>
|
||||
<directory>./Resources</directory>
|
||||
<directory>./vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
-----
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE scan [<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource={{ resource }}">]>
|
||||
<scan></scan>
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root2 xmlns="http://example.com/schema" />
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xsd:schema xmlns="http://example.com/schema"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://example.com/schema"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xsd:element name="root" />
|
||||
</xsd:schema>
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root xmlns="http://example.com/schema">
|
||||
</root>
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 : '<root>'.$xml.'</root>');
|
||||
|
||||
$this->assertSame($expected, XmlUtils::convertDomElementToArray($dom->documentElement, $checkPrefix));
|
||||
}
|
||||
|
||||
public function getDataForConvertDomToArray()
|
||||
{
|
||||
return array(
|
||||
array(null, ''),
|
||||
array('bar', 'bar'),
|
||||
array(array('bar' => 'foobar'), '<foo bar="foobar" />', true),
|
||||
array(array('foo' => null), '<foo />'),
|
||||
array(array('foo' => 'bar'), '<foo>bar</foo>'),
|
||||
array(array('foo' => array('foo' => 'bar')), '<foo foo="bar"/>'),
|
||||
array(array('foo' => array('foo' => 'bar')), '<foo><foo>bar</foo></foo>'),
|
||||
array(array('foo' => array('foo' => 'bar', 'value' => 'text')), '<foo foo="bar">text</foo>'),
|
||||
array(array('foo' => array('attr' => 'bar', 'foo' => 'text')), '<foo attr="bar"><foo>text</foo></foo>'),
|
||||
array(array('foo' => array('bar', 'text')), '<foo>bar</foo><foo>text</foo>'),
|
||||
array(array('foo' => array(array('foo' => 'bar'), array('foo' => 'text'))), '<foo foo="bar"/><foo foo="text" />'),
|
||||
array(array('foo' => array('foo' => array('bar', 'text'))), '<foo foo="bar"><foo>text</foo></foo>'),
|
||||
array(array('foo' => 'bar'), '<foo><!-- Comment -->bar</foo>'),
|
||||
array(array('foo' => 'text'), '<foo xmlns:h="http://www.example.org/bar" h:bar="bar">text</foo>'),
|
||||
array(array('foo' => array('bar' => 'bar', 'value' => 'text')), '<foo xmlns:h="http://www.example.org/bar" h:bar="bar">text</foo>', false, false),
|
||||
array(array('attr' => 1, 'b' => 'hello'), '<foo:a xmlns:foo="http://www.example.org/foo" xmlns:h="http://www.example.org/bar" attr="1" h:bar="bar"><foo:b>hello</foo:b><h:c>2</h:c></foo:a>', 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();
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <fabien@symfony.com>
|
||||
* @author Martin Hasoň <martin.hason@gmail.com>
|
||||
*/
|
||||
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 (<foo>bar</foo>)
|
||||
* if the tag also has some nested tags
|
||||
*
|
||||
* * The attributes are converted to keys (<foo foo="bar"/>)
|
||||
*
|
||||
* * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
|
@ -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 <width>x<height> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
-----
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(" [<info>%-${width}s</info>] %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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
|
||||
namespace Symfony\Component\Console\Tests\Formatter;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <fabien@symfony.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* added a delete option for the mirror() method
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue