Merge branch 'master' of github.com:symfony/symfony into deprecationErrors

This commit is contained in:
Colin Frei 2012-12-14 23:30:36 +01:00
commit 6b105504f4
305 changed files with 5575 additions and 2140 deletions

View File

@ -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)

View File

@ -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?
-----------------

View File

@ -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`,

View File

@ -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",

View File

@ -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
-----

View File

@ -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);

View File

@ -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(

View File

@ -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()

View File

@ -86,4 +86,4 @@ class DoctrineOrmTypeGuesserTest extends \PHPUnit_Framework_TestCase
return new DoctrineOrmTypeGuesser($registry);
}
}
}

View File

@ -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();
}
}
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Bridge\Doctrine\Tests\Logger;
class DbalLoggerTest extends \PHPUnit_Framework_TestCase
{
/**

View File

@ -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()
{

View File

@ -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;
}
}

View File

@ -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();

View File

@ -161,7 +161,7 @@ class PropelLogger
/**
* Returns queries.
*
* @return array Queries
* @return array Queries
*/
public function getQueries()
{

View File

@ -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);
}
/**

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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 %}

View File

@ -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
{

View File

@ -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
-----

View File

@ -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;

View File

@ -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);
}

View File

@ -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())
{

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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()

View File

@ -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.
*

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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 ?>

View File

@ -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()));
}
}

View File

@ -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)

View File

@ -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]"

View File

@ -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

View File

@ -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;
}
}

View File

@ -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');
}
}
}

View File

@ -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()
;

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,4 @@
vendor/
composer.lock
phpunit.xml

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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'] %}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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\\": "" }

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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
-----

View File

@ -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);
}
}
}

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<root2 xmlns="http://example.com/schema" />

View File

@ -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>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://example.com/schema">
</root>

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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];
}
}
}

View File

@ -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
-----

View File

@ -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;

View File

@ -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.
*

View File

@ -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);
}
/**

View File

@ -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

View File

@ -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();

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tests\Formatter;
use Symfony\Component\Console\Formatter\OutputFormatter;

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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));
}

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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.
*

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {}

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
2.2.0
-----
* added a delete option for the mirror() method
2.1.0
-----

View File

@ -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));

View 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