diff --git a/.travis.yml b/.travis.yml index 2bfd170c7f66..764897ff66da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,7 @@ before_install: - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (pecl install -f memcached-2.1.0 && echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without memcache extension"; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]] && [ "$deps" = "no" ]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi; - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi; + - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "extension = ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - ./phpunit install - export PHPUNIT="$(readlink -f ./phpunit)" @@ -48,5 +49,5 @@ install: script: - if [ "$deps" = "no" ]; then echo "$COMPONENTS" | parallel --gnu --keep-order 'echo -e "\\nRunning {} tests"; $PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi; - if [ "$deps" = "no" ]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi; - - if [ "$deps" = "high" ]; then echo "$COMPONENTS" | parallel --gnu --keep-order -j10% 'echo -e "\\nRunning {} tests"; cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; + - if [ "$deps" = "high" ]; then echo "$COMPONENTS" | parallel --gnu --keep-order -j10% 'echo -e "\\nRunning {} tests"; cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data,legacy'; fi; - if [ "$deps" = "low" ]; then echo "$COMPONENTS" | parallel --gnu --keep-order -j10% 'echo -e "\\nRunning {} tests"; cd {}; composer --prefer-source --prefer-lowest --prefer-stable update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md new file mode 100644 index 000000000000..174d941b4502 --- /dev/null +++ b/UPGRADE-2.8.md @@ -0,0 +1,475 @@ +UPGRADE FROM 2.7 to 2.8 +======================= + +Form +---- + + * The "cascade_validation" option was deprecated. Use the "constraints" + option together with the `Valid` constraint instead. Contrary to + "cascade_validation", "constraints" must be set on the respective child forms, + not the parent form. + + Before: + + ```php + $form = $this->createFormBuilder($article, array('cascade_validation' => true)) + ->add('author', new AuthorType()) + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Validator\Constraints\Valid; + + $form = $this->createFormBuilder($article) + ->add('author', new AuthorType(), array( + 'constraints' => new Valid(), + )) + ->getForm(); + ``` + + Alternatively, you can set the `Valid` constraint in the model itself: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + class Article + { + /** + * @Assert\Valid + */ + private $author; + } + ``` + + * Type names were deprecated and will be removed in Symfony 3.0. Instead of + referencing types by name, you should reference them by their + fully-qualified class name (FQCN) instead. With PHP 5.5 or later, you can + use the "class" constant for that: + + Before: + + ```php + $form = $this->createFormBuilder() + ->add('name', 'text') + ->add('age', 'integer') + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\IntegerType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + + $form = $this->createFormBuilder() + ->add('name', TextType::class) + ->add('age', IntegerType::class) + ->getForm(); + ``` + + As a further consequence, the method `FormTypeInterface::getName()` was + deprecated and will be removed in Symfony 3.0. You should remove this method + from your form types. + + If you want to customize the block prefix of a type in Twig, you should now + implement `FormTypeInterface::getBlockPrefix()` instead: + + Before: + + ```php + class UserProfileType extends AbstractType + { + public function getName() + { + return 'profile'; + } + } + ``` + + After: + + ```php + class UserProfileType extends AbstractType + { + public function getBlockPrefix() + { + return 'profile'; + } + } + ``` + + If you don't customize `getBlockPrefix()`, it defaults to the class name + without "Type" suffix in underscore notation (here: "user_profile"). + + If you want to create types that are compatible with Symfony 2.3 up to 2.8 + and don't trigger deprecation errors, implement *both* `getName()` and + `getBlockPrefix()`: + + ```php + class ProfileType extends AbstractType + { + public function getName() + { + return $this->getBlockPrefix(); + } + + public function getBlockPrefix() + { + return 'profile'; + } + } + ``` + + If you define your form types in the Dependency Injection configuration, you + should further remove the "alias" attribute: + + Before: + + ```xml + + + + ``` + + After: + + ```xml + + + + ``` + + Type extension should return the fully-qualified class name of the extended + type from `FormTypeExtensionInterface::getExtendedType()` now. + + Before: + + ```php + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'form'; + } + } + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + } + ``` + + If your extension has to be compatible with Symfony 2.3-2.8, use the + following statement: + + ```php + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return method_exists(AbstractType::class, 'getBlockPrefix') ? FormType::class : 'form'; + } + } + ``` + + * Returning type instances from `FormTypeInterface::getParent()` is deprecated + and will not be supported anymore in Symfony 3.0. Return the fully-qualified + class name of the parent type class instead. + + Before: + + ```php + class MyType + { + public function getParent() + { + return new ParentType(); + } + } + ``` + + After: + + ```php + class MyType + { + public function getParent() + { + return ParentType::class; + } + } + ``` + + * Passing type instances to `Form::add()`, `FormBuilder::add()` and the + `FormFactory::create*()` methods is deprecated and will not be supported + anymore in Symfony 3.0. Pass the fully-qualified class name of the type + instead. + + Before: + + ```php + $form = $this->createForm(new MyType()); + ``` + + After: + + ```php + $form = $this->createForm(MyType::class); + ``` + + * Registering type extensions as a service with an alias which does not + match the type returned by `getExtendedType` is now forbidden. Fix your + implementation to define the right type. + +Translator +---------- + + * The `getMessages()` method of the `Symfony\Component\Translation\Translator` was deprecated and will be removed in + Symfony 3.0. You should use the `getCatalogue()` method of the `Symfony\Component\Translation\TranslatorBagInterface`. + + Before: + + ```php + $messages = $translator->getMessages(); + ``` + + After: + + ```php + $catalogue = $translator->getCatalogue($locale); + $messages = $catalogue->all(); + + while ($catalogue = $catalogue->getFallbackCatalogue()) { + $messages = array_replace_recursive($catalogue->all(), $messages); + } + ``` + +DependencyInjection +------------------- + + * The concept of scopes were deprecated, the deprecated methods are: + + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopes()` + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopeChildren()` + - `Symfony\Component\DependencyInjection\ContainerInterface::enterScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::leaveScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::addScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::hasScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::isScopeActive()` + - `Symfony\Component\DependencyInjection\Definition::setScope()` + - `Symfony\Component\DependencyInjection\Definition::getScope()` + - `Symfony\Component\DependencyInjection\Reference::isStrict()` + + Also, the `$scope` and `$strict` parameters of `Symfony\Component\DependencyInjection\ContainerInterface::set()` and `Symfony\Component\DependencyInjection\Reference` respectively were deprecated. + + * A new `shared` flag has been added to the service definition + in replacement of the `prototype` scope. + + Before: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setScope(ContainerBuilder::SCOPE_PROTOTYPE) + ; + ``` + + ```yml + services: + foo: + class: stdClass + scope: prototype + ``` + + ```xml + + + + ``` + + After: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setShared(false) + ; + ``` + + ```yml + services: + foo: + class: stdClass + shared: false + ``` + + ```xml + + + + ``` + +WebProfiler +----------- + +The `profiler:import` and `profiler:export` commands have been deprecated and +will be removed in 3.0. + +The web development toolbar has been completely redesigned. This update has +introduced some changes in the HTML markup of the toolbar items. + +Before: + +Information was wrapped with simple `` elements: + +```twig +{% block toolbar %} + {% set icon %} + + + {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MB + + {% endset %} +{% endblock %} +``` + +After: + +Information is now semantically divided into values and labels according to +the `class` attribute of each `` element: + +```twig +{% block toolbar %} + {% set icon %} + + + {{ '%.1f'|format(collector.memory / 1024 / 1024) }} + + MB + {% endset %} +{% endblock %} +``` + +Most of the blocks designed for the previous toolbar will still be displayed +correctly. However, if you want to support both the old and the new toolbar, +it's better to make use of the new `profiler_markup_version` variable passed +to the toolbar templates: + +```twig +{% block toolbar %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% set icon %} + {% if profiler_markup_version == 1 %} + + {# code for the original toolbar #} + + {% else %} + + {# code for the new toolbar (Symfony 2.8+) #} + + {% endif %} + {% endset %} +{% endblock %} +``` + +FrameworkBundle +--------------- + + * The default value of the parameter `session`.`cookie_httponly` is now `true`. + It prevents scripting languages, such as JavaScript to access the cookie, + which help to reduce identity theft through XSS attacks. If your + application needs to access the session cookie, override this parameter: + + ```yaml + framework: + session: + cookie_httponly: false + ``` + +Security +-------- + + * The AbstractToken::isGranted() method was deprecated. Instead, + override the voteOnAttribute() method. This method has one small + difference: it's passed the TokenInterface instead of the user: + + Before: + + ```php + class MyCustomVoter extends AbstractVoter + { + // ... + + protected function isGranted($attribute, $object, $user = null) + { + // ... + } + } + ``` + + After: + + ```php + class MyCustomVoter extends AbstractVoter + { + // ... + + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + $user = $token->getUser(); + // ... + } + } + ``` + +Config +------ + + * The `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` method has been + deprecated and will be removed in Symfony 3.0 because it assumes that resource + implementations are able to check themselves for freshness. + + If you have custom resources that implement this method, change them to implement the + `\Symfony\Component\Config\Resource\SelfCheckingResourceInterface` sub-interface instead + of `\Symfony\Component\Config\Resource\ResourceInterface`. + + Before: + + ```php + use Symfony\Component\Config\Resource\ResourceInterface; + + class MyCustomResource implements ResourceInterface { ... } + ``` + + After: + + ```php + use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + + class MyCustomResource implements SelfCheckingResourceInterface { ... } + ``` + + Additionally, if you have implemented cache validation strategies *using* `isFresh()` + yourself, you should have a look at the new cache validation system based on + `ResourceChecker`s. diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 6b6d6cad8d68..855056b682a2 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -600,6 +600,123 @@ UPGRADE FROM 2.x to 3.0 * The `Resources/` directory was moved to `Core/Resources/` + * The `key` settings of `anonymous` and `remember_me` are renamed to `secret`. + + Before: + + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { key: "%secret%" } + remember_me: + key: "%secret%" + ``` + + ```xml + + + + + + + + + + + + ``` + + ```php + // ... + $container->loadFromExtension('security', array( + // ... + 'firewalls' => array( + // ... + 'anonymous' => array('key' => '%secret%'), + 'remember_me' => array('key' => '%secret%'), + ), + )); + ``` + + After: + + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { secret: "%secret%" } + remember_me: + secret: "%secret%" + ``` + + ```xml + + + + + + + + + + + + ``` + + ```php + // ... + $container->loadFromExtension('security', array( + // ... + 'firewalls' => array( + // ... + 'anonymous' => array('secret' => '%secret%'), + 'remember_me' => array('secret' => '%secret%'), + ), + )); + ``` + + * The `AbstractVoter::getSupportedAttributes()` and `AbstractVoter::getSupportedClasses()` + methods have been removed in favor of `AbstractVoter::supports()`. + + Before: + + ```php + class MyVoter extends AbstractVoter + { + protected function getSupportedAttributes() + { + return array('CREATE', 'EDIT'); + } + + protected function getSupportedClasses() + { + return array('AppBundle\Entity\Post'); + } + + // ... + } + ``` + + After: + + ```php + class MyVoter extends AbstractVoter + { + protected function supports($attribute, $class) + { + return $this->isClassInstanceOf($class, 'AppBundle\Entity\Post') + && in_array($attribute, array('CREATE', 'EDIT')); + } + + // ... + } + ``` + ### Translator * The `Translator::setFallbackLocale()` method has been removed in favor of @@ -1121,3 +1238,10 @@ UPGRADE FROM 2.x to 3.0 * `Process::setStdin()` and `Process::getStdin()` have been removed. Use `Process::setInput()` and `Process::getInput()` that works the same way. * `Process::setInput()` and `ProcessBuilder::setInput()` do not accept non-scalar types. + +### Config + + * `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` has been removed. Also, + cache validation through this method (which was still supported in 2.8 for BC) does no longer + work because the `\Symfony\Component\Config\Resource\BCResourceInterfaceChecker` helper class + has been removed as well. diff --git a/appveyor.yml b/appveyor.yml index 2e4d1cce4021..069bee46c523 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -45,6 +45,7 @@ install: - IF %PHP_EXT%==1 echo extension=php_mbstring.dll >> php.ini - IF %PHP_EXT%==1 echo extension=php_fileinfo.dll >> php.ini - IF %PHP_EXT%==1 echo extension=php_pdo_sqlite.dll >> php.ini + - IF %PHP_EXT%==1 echo extension=php_ldap.dll >> php.ini - cd c:\projects\symfony - php phpunit install - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) diff --git a/composer.json b/composer.json index 521e0307b029..3cfe44bcd9a9 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "php": ">=5.3.9", "doctrine/common": "~2.4", "twig/twig": "~1.20|~2.0", - "psr/log": "~1.0" + "psr/log": "~1.0", + "symfony/security-acl": "~2.7" }, "replace": { "symfony/asset": "self.version", @@ -42,17 +43,19 @@ "symfony/http-foundation": "self.version", "symfony/http-kernel": "self.version", "symfony/intl": "self.version", + "symfony/ldap": "self.version", "symfony/locale": "self.version", "symfony/monolog-bridge": "self.version", "symfony/options-resolver": "self.version", "symfony/process": "self.version", "symfony/property-access": "self.version", + "symfony/property-info": "self.version", "symfony/proxy-manager-bridge": "self.version", "symfony/routing": "self.version", "symfony/security": "self.version", - "symfony/security-acl": "self.version", "symfony/security-core": "self.version", "symfony/security-csrf": "self.version", + "symfony/security-guard": "self.version", "symfony/security-http": "self.version", "symfony/security-bundle": "self.version", "symfony/serializer": "self.version", @@ -76,7 +79,11 @@ "monolog/monolog": "~1.11", "ircmaxell/password-compat": "~1.0", "ocramius/proxy-manager": "~0.4|~1.0", - "egulias/email-validator": "~1.2" + "egulias/email-validator": "~1.2", + "phpdocumentor/reflection": "^1.0.7" + }, + "conflict": { + "phpdocumentor/reflection": "<1.0.7" }, "autoload": { "psr-4": { @@ -97,7 +104,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index ed8e7a5c3c85..223129097d3b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -94,12 +94,12 @@ public static function createChoiceName($choice, $key, $value) * Gets important parts from QueryBuilder that will allow to cache its results. * For instance in ORM two query builders with an equal SQL string and * equal parameters are considered to be equal. - * + * * @param object $queryBuilder - * + * * @return array|false Array with important QueryBuilder parts or false if * they can't be determined - * + * * @internal This method is public to be usable as callback. It should not * be used in user code. */ @@ -333,6 +333,6 @@ abstract public function getLoader(ObjectManager $manager, $queryBuilder, $class public function getParent() { - return 'choice'; + return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'; } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 486eca94abf7..7e0c6cb6cda7 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -30,7 +30,7 @@ public function configureOptions(OptionsResolver $resolver) if (is_callable($queryBuilder)) { $queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); - if (!$queryBuilder instanceof QueryBuilder) { + if (null !== $queryBuilder && !$queryBuilder instanceof QueryBuilder) { throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); } } @@ -56,7 +56,18 @@ public function getLoader(ObjectManager $manager, $queryBuilder, $class) return new ORMQueryBuilderLoader($queryBuilder, $manager, $class); } + /** + * {@inheritdoc} + */ public function getName() + { + return $this->getBlockPrefix(); + } + + /** + * {@inheritdoc} + */ + public function getBlockPrefix() { return 'entity'; } @@ -64,11 +75,11 @@ public function getName() /** * We consider two query builders with an equal SQL string and * equal parameters to be equal. - * + * * @param QueryBuilder $queryBuilder - * + * * @return array - * + * * @internal This method is public to be usable as callback. It should not * be used in user code. */ diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php new file mode 100644 index 000000000000..dc22e235959b --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\PropertyInfo; + +use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; +use Doctrine\Common\Persistence\Mapping\MappingException; +use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * Extracts data using Doctrine ORM and ODM metadata. + * + * @author Kévin Dunglas + */ +class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface +{ + /** + * @var ClassMetadataFactory + */ + private $classMetadataFactory; + + public function __construct(ClassMetadataFactory $classMetadataFactory) + { + $this->classMetadataFactory = $classMetadataFactory; + } + + /** + * {@inheritdoc} + */ + public function getProperties($class, array $context = array()) + { + try { + $metadata = $this->classMetadataFactory->getMetadataFor($class); + } catch (MappingException $exception) { + return; + } + + return array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); + } + + /** + * {@inheritdoc} + */ + public function getTypes($class, $property, array $context = array()) + { + try { + $metadata = $this->classMetadataFactory->getMetadataFor($class); + } catch (MappingException $exception) { + return; + } + + if ($metadata->hasAssociation($property)) { + $class = $metadata->getAssociationTargetClass($property); + + if ($metadata->isSingleValuedAssociation($property)) { + if ($metadata instanceof ClassMetadataInfo) { + $nullable = isset($metadata->discriminatorColumn['nullable']) ? $metadata->discriminatorColumn['nullable'] : false; + } else { + $nullable = false; + } + + return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class)); + } + + return array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_INT), + new Type(Type::BUILTIN_TYPE_OBJECT, false, $class) + )); + } + + if ($metadata->hasField($property)) { + $typeOfField = $metadata->getTypeOfField($property); + $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); + + switch ($typeOfField) { + case 'date': + case 'datetime': + case 'datetimetz': + case 'time': + return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')); + + case 'array': + return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)); + + case 'simple_array': + return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))); + + case 'json_array': + return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)); + + default: + return array(new Type($this->getPhpType($typeOfField), $nullable)); + } + } + } + + /** + * Gets the corresponding built-in PHP type. + * + * @param string $doctrineType + * + * @return string + */ + private function getPhpType($doctrineType) + { + switch ($doctrineType) { + case 'smallint': + // No break + case 'bigint': + // No break + case 'integer': + return Type::BUILTIN_TYPE_INT; + + case 'decimal': + return Type::BUILTIN_TYPE_FLOAT; + + case 'text': + // No break + case 'guid': + return Type::BUILTIN_TYPE_STRING; + + case 'boolean': + return Type::BUILTIN_TYPE_BOOL; + + case 'blob': + // No break + case 'binary': + return Type::BUILTIN_TYPE_RESOURCE; + + default: + return $doctrineType; + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 78f10848bccb..fb78d65ef181 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -108,12 +108,25 @@ protected function persist(array $entities) // be managed! } + /** + * @group legacy + */ + public function testLegacyName() + { + $field = $this->factory->createNamed('name', 'entity', null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )); + + $this->assertSame('entity', $field->getConfig()->getType()->getName()); + } + /** * @expectedException \Symfony\Component\OptionsResolver\Exception\MissingOptionsException */ public function testClassOptionIsRequired() { - $this->factory->createNamed('name', 'entity'); + $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType'); } /** @@ -121,7 +134,7 @@ public function testClassOptionIsRequired() */ public function testInvalidClassOption() { - $this->factory->createNamed('name', 'entity', null, array( + $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'class' => 'foo', )); } @@ -133,7 +146,7 @@ public function testSetDataToUninitializedEntityWithNonRequired() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, @@ -150,7 +163,7 @@ public function testSetDataToUninitializedEntityWithNonRequiredToString() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, @@ -167,7 +180,7 @@ public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() $this->persist(array($entity1, $entity2)); $qb = $this->em->createQueryBuilder()->select('e')->from(self::SINGLE_IDENT_CLASS, 'e'); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, @@ -183,7 +196,7 @@ public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() */ public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => new \stdClass(), @@ -195,7 +208,7 @@ public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() */ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function () { @@ -206,9 +219,22 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() $field->submit('2'); } + public function testConfigureQueryBuilderWithClosureReturningNull() + { + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => function () { + return; + }, + )); + + $this->assertEquals(array(), $field->createView()->vars['choices']); + } + public function testSetDataSingleNull() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, @@ -221,7 +247,7 @@ public function testSetDataSingleNull() public function testSetDataMultipleExpandedNull() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -235,7 +261,7 @@ public function testSetDataMultipleExpandedNull() public function testSetDataMultipleNonExpandedNull() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -249,7 +275,7 @@ public function testSetDataMultipleNonExpandedNull() public function testSubmitSingleExpandedNull() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => true, 'em' => 'default', @@ -263,7 +289,7 @@ public function testSubmitSingleExpandedNull() public function testSubmitSingleNonExpandedNull() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -277,7 +303,7 @@ public function testSubmitSingleNonExpandedNull() public function testSubmitMultipleNull() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, @@ -295,7 +321,7 @@ public function testSubmitSingleNonExpandedSingleIdentifier() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -320,7 +346,7 @@ public function testSubmitSingleNonExpandedSingleAssocIdentifier() $this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -342,7 +368,7 @@ public function testSubmitSingleNonExpandedCompositeIdentifier() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -366,7 +392,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifier() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -395,7 +421,7 @@ public function testSubmitMultipleNonExpandedSingleAssocIdentifier() $this->persist(array($innerEntity1, $innerEntity2, $innerEntity3, $entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -420,7 +446,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifierForExistingData() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -451,7 +477,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifier() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -477,7 +503,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifierExistingData() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -507,7 +533,7 @@ public function testSubmitSingleExpanded() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => true, 'em' => 'default', @@ -533,7 +559,7 @@ public function testSubmitMultipleExpanded() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -562,7 +588,7 @@ public function testSubmitMultipleExpandedWithNegativeIntegerId() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -588,7 +614,7 @@ public function testOverrideChoices() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, // not all persisted entities should be displayed @@ -613,7 +639,7 @@ public function testGroupByChoices() $this->persist(array($item1, $item2, $item3, $item4)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::ITEM_GROUP_CLASS, 'choices' => array($item1, $item2, $item3, $item4), @@ -644,7 +670,7 @@ public function testPreferredChoices() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'preferred_choices' => array($entity3, $entity2), @@ -663,7 +689,7 @@ public function testOverrideChoicesWithPreferredChoices() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'choices' => array($entity2, $entity3), @@ -683,7 +709,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -706,7 +732,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesSingleAssocIdentifie $this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_ASSOC_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -727,7 +753,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesCompositeIdentifier( $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -750,7 +776,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifie $repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => $repository->createQueryBuilder('e') @@ -778,7 +804,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleAssocIden $repository = $this->em->getRepository(self::SINGLE_ASSOC_IDENT_CLASS); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_ASSOC_IDENT_CLASS, 'query_builder' => $repository->createQueryBuilder('e') @@ -800,7 +826,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingle $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function ($repository) { @@ -824,7 +850,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureCompos $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, 'query_builder' => function ($repository) { @@ -846,7 +872,7 @@ public function testSubmitSingleStringIdentifier() $this->persist(array($entity1)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -867,7 +893,7 @@ public function testSubmitCompositeStringIdentifier() $this->persist(array($entity1)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -893,7 +919,7 @@ public function testGetManagerForClassIfNoEm() ->with(self::SINGLE_IDENT_CLASS) ->will($this->returnValue($this->em)); - $this->factory->createNamed('name', 'entity', null, array( + $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'choice_label' => 'name', @@ -908,7 +934,7 @@ public function testExplicitEm() $this->emRegistry->expects($this->never()) ->method('getManagerForClass'); - $this->factory->createNamed('name', 'entity', null, array( + $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => $this->em, 'class' => self::SINGLE_IDENT_CLASS, 'choice_label' => 'name', @@ -937,15 +963,15 @@ public function testLoaderCaching() ->addTypeGuesser($entityTypeGuesser) ->getFormFactory(); - $formBuilder = $factory->createNamedBuilder('form', 'form'); + $formBuilder = $factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType'); - $formBuilder->add('property1', 'entity', array( + $formBuilder->add('property1', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'), )); - $formBuilder->add('property2', 'entity', array( + $formBuilder->add('property2', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function (EntityRepository $repo) { @@ -953,7 +979,7 @@ public function testLoaderCaching() }, )); - $formBuilder->add('property3', 'entity', array( + $formBuilder->add('property3', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function (EntityRepository $repo) { @@ -984,14 +1010,14 @@ public function testCacheChoiceLists() $this->persist(array($entity1)); - $field1 = $this->factory->createNamed('name', 'entity', null, array( + $field1 = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'choice_label' => 'name', )); - $field2 = $this->factory->createNamed('name', 'entity', null, array( + $field2 = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, @@ -1012,7 +1038,7 @@ public function testPropertyOption() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php new file mode 100644 index 000000000000..df6c35589508 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\PropertyInfo\Tests; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Tools\Setup; +use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Kévin Dunglas + */ +class DoctrineExtractorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DoctrineExtractor + */ + private $extractor; + + public function setUp() + { + $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'), true); + $entityManager = EntityManager::create(array('driver' => 'pdo_sqlite'), $config); + + $this->extractor = new DoctrineExtractor($entityManager->getMetadataFactory()); + } + + public function testGetProperties() + { + $this->assertEquals( + array( + 'id', + 'guid', + 'time', + 'json', + 'simpleArray', + 'bool', + 'binary', + 'foo', + 'bar', + ), + $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') + ); + } + + /** + * @dataProvider typesProvider + */ + public function testExtract($property, array $type = null) + { + $this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array())); + } + + public function typesProvider() + { + return array( + array('id', array(new Type(Type::BUILTIN_TYPE_INT))), + array('guid', array(new Type(Type::BUILTIN_TYPE_STRING))), + array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))), + array('binary', array(new Type(Type::BUILTIN_TYPE_RESOURCE))), + array('json', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))), + array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))), + array('bar', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_INT), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), + array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), + array('notMapped', null), + ); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php new file mode 100644 index 000000000000..864bd78407c4 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\ManyToOne; + +/** + * @Entity + * + * @author Kévin Dunglas + */ +class DoctrineDummy +{ + /** + * @Id + * @Column(type="smallint") + */ + public $id; + + /** + * @ManyToOne(targetEntity="DoctrineRelation") + */ + public $foo; + + /** + * @ManyToMany(targetEntity="DoctrineRelation") + */ + public $bar; + + /** + * @Column(type="guid") + */ + protected $guid; + + /** + * @Column(type="time") + */ + private $time; + + /** + * @Column(type="json_array") + */ + private $json; + + /** + * @Column(type="simple_array") + */ + private $simpleArray; + + /** + * @Column(type="boolean") + */ + private $bool; + + /** + * @Column(type="binary") + */ + private $binary; + + public $notMapped; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php new file mode 100644 index 000000000000..bfb27e9338d9 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Id; + +/** + * @Entity + * + * @author Kévin Dunglas + */ +class DoctrineRelation +{ + /** + * @Id + * @Column(type="smallint") + */ + public $id; +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index d57e64621bbf..bf215f1697bf 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -20,16 +20,17 @@ "doctrine/common": "~2.4" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/stopwatch": "~2.2", - "symfony/dependency-injection": "~2.2", - "symfony/form": "~2.7,>=2.7.1", - "symfony/http-kernel": "~2.2", - "symfony/property-access": "~2.3", - "symfony/security": "~2.2", - "symfony/expression-language": "~2.2", - "symfony/validator": "~2.5,>=2.5.5", - "symfony/translation": "~2.0,>=2.0.5", + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/dependency-injection": "~2.2|~3.0.0", + "symfony/form": "~2.8|~3.0.0", + "symfony/http-kernel": "~2.2|~3.0.0", + "symfony/property-access": "~2.3|~3.0.0", + "symfony/property-info": "~2.8|3.0", + "symfony/security": "~2.2|~3.0.0", + "symfony/expression-language": "~2.2|~3.0.0", + "symfony/validator": "~2.5,>=2.5.5|~3.0.0", + "symfony/translation": "~2.0,>=2.0.5|~3.0.0", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", "doctrine/orm": "~2.4,>=2.4.5" @@ -37,6 +38,7 @@ "suggest": { "symfony/form": "", "symfony/validator": "", + "symfony/property-info": "", "doctrine/data-fixtures": "", "doctrine/dbal": "", "doctrine/orm": "" @@ -47,7 +49,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php index c959a5a94fcb..f1046c96a6ad 100644 --- a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php @@ -35,6 +35,7 @@ public function getLogs() 'priority' => $record['level'], 'priorityName' => $record['level_name'], 'context' => $record['context'], + 'channel' => isset($record['channel']) ? $record['channel'] : '', ); } diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index a3a4e374130e..61b6ff02d351 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -20,10 +20,10 @@ "monolog/monolog": "~1.11" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/http-kernel": "~2.4", - "symfony/console": "~2.4", - "symfony/event-dispatcher": "~2.2" + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/http-kernel": "~2.4|~3.0.0", + "symfony/console": "~2.4|~3.0.0", + "symfony/event-dispatcher": "~2.2|~3.0.0" }, "suggest": { "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", @@ -36,7 +36,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 8412a277bae9..90ac8e5d9dd1 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index bba5055d0085..e1ec80608c63 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -68,11 +68,13 @@ public function getProxyFactoryCode(Definition $definition, $id) { $instantiation = 'return'; - if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { + if ($definition->isShared()) { $instantiation .= " \$this->services['$id'] ="; - } elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { - $instantiation .= " \$this->services['$id'] = \$this->scopedServices['$scope']['$id'] ="; - } + + if (defined('Symfony\Component\DependencyInjection\ContainerInterface::SCOPE_CONTAINER') && ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { + $instantiation .= " \$this->scopedServices['$scope']['$id'] ="; + } + } $methodName = 'get'.Container::camelize($id).'Service'; $proxyClass = $this->getProxyClassName($definition); diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 24a8edea01b4..b37bf7b87575 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -17,12 +17,12 @@ ], "require": { "php": ">=5.3.9", - "symfony/dependency-injection": "~2.3", + "symfony/dependency-injection": "~2.8|~3.0.0", "ocramius/proxy-manager": "~0.4|~1.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/config": "~2.3" + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/config": "~2.3|~3.0.0" }, "autoload": { "psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" } @@ -30,7 +30,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Swiftmailer/composer.json b/src/Symfony/Bridge/Swiftmailer/composer.json index 6d3fa5057cd9..3e5fcc3bf837 100644 --- a/src/Symfony/Bridge/Swiftmailer/composer.json +++ b/src/Symfony/Bridge/Swiftmailer/composer.json @@ -20,7 +20,7 @@ "swiftmailer/swiftmailer": ">=4.2.0,<6.0-dev" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "symfony/phpunit-bridge": "~2.7|~3.0.0" }, "suggest": { "symfony/http-kernel": "" @@ -31,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index 763a5ec4b31e..90b21d0495ff 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -74,8 +74,20 @@ public function getMacroCount() public function getHtmlCallGraph() { $dumper = new \Twig_Profiler_Dumper_Html(); - - return new \Twig_Markup($dumper->dump($this->getProfile()), 'UTF-8'); + $dump = $dumper->dump($this->getProfile()); + + // needed to remove the hardcoded CSS styles + $dump = str_replace(array( + '', + '', + '', + ), array( + '', + '', + '', + ), $dump); + + return new \Twig_Markup($dump, 'UTF-8'); } public function getProfile() diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index b13d7c0f8533..fa5fae2e77a0 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -13,6 +13,7 @@ use Symfony\Component\Security\Acl\Voter\FieldVote; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; /** * SecurityExtension exposes security context features. @@ -38,7 +39,11 @@ public function isGranted($role, $object = null, $field = null) $object = new FieldVote($object, $field); } - return $this->securityChecker->isGranted($role, $object); + try { + return $this->securityChecker->isGranted($role, $object); + } catch (AuthenticationCredentialsNotFoundException $e) { + return false; + } } /** diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index e3a611c38ffc..3e0a348d9ae9 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -176,6 +176,11 @@ {{ block('form_widget_simple') }} {%- endblock email_widget -%} +{%- block range_widget -%} + {% set type = type|default('range') %} + {{- block('form_widget_simple') -}} +{%- endblock range_widget %} + {%- block button_widget -%} {%- if label is empty -%} {%- if label_format is not empty -%} @@ -314,7 +319,6 @@ {%- block widget_attributes -%} id="{{ id }}" name="{{ full_name }}" - {%- if read_only and attr.readonly is not defined %} readonly="readonly"{% endif -%} {%- if disabled %} disabled="disabled"{% endif -%} {%- if required %} required="required"{% endif -%} {%- for attrname, attrvalue in attr -%} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig new file mode 100644 index 000000000000..6e2b10608235 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig @@ -0,0 +1,321 @@ +{% extends "form_div_layout.html.twig" %} + +{# Based on Foundation 5 Doc #} +{# Widgets #} + +{% block form_widget_simple -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block textarea_widget -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock %} + +{% block money_widget -%} +
+ {% set prepend = '{{' == money_pattern[0:2] %} + {% if not prepend %} +
+ {{ money_pattern|replace({ '{{ widget }}':''}) }} +
+ {% endif %} +
+ {{- block('form_widget_simple') -}} +
+ {% if prepend %} +
+ {{ money_pattern|replace({ '{{ widget }}':''}) }} +
+ {% endif %} +
+{%- endblock money_widget %} + +{% block percent_widget -%} +
+
+ {{- block('form_widget_simple') -}} +
+
+ % +
+
+{%- endblock percent_widget %} + +{% block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} +
+
{{ form_errors(form.date) }}
+
{{ form_errors(form.time) }}
+
+
+
{{ form_widget(form.date, { datetime: true } ) }}
+
{{ form_widget(form.time, { datetime: true } ) }}
+
+ {% endif %} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or not datetime %} +
+ {% endif %} + {{- date_pattern|replace({ + '{{ year }}': '
' ~ form_widget(form.year) ~ '
', + '{{ month }}': '
' ~ form_widget(form.month) ~ '
', + '{{ day }}': '
' ~ form_widget(form.day) ~ '
', + })|raw -}} + {% if datetime is not defined or not datetime %} +
+ {% endif %} + {% endif %} +{%- endblock date_widget %} + +{% block time_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or false == datetime %} +
+ {% endif %} + {% if with_seconds %} +
{{ form_widget(form.hour) }}
+
+
+
+ : +
+
+ {{ form_widget(form.minute) }} +
+
+
+
+
+
+ : +
+
+ {{ form_widget(form.second) }} +
+
+
+ {% else %} +
{{ form_widget(form.hour) }}
+
+
+
+ : +
+
+ {{ form_widget(form.minute) }} +
+
+
+ {% endif %} + {% if datetime is not defined or false == datetime %} +
+ {% endif %} + {% endif %} +{%- endblock time_widget %} + +{% block choice_widget_collapsed -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + + {% if multiple -%} + {% set attr = attr|merge({style: (attr.style|default('') ~ ' height: auto; background-image: none;')|trim}) %} + {% endif %} + + {% if required and placeholder is none and not placeholder_in_choices and not multiple -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} + {% if '-inline' in label_attr.class|default('') %} +
    + {% for child in form %} +
  • {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }}
  • + {% endfor %} +
+ {% else %} +
+ {% for child in form %} + {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }} + {% endfor %} +
+ {% endif %} +{%- endblock choice_widget_expanded %} + +{% block checkbox_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if 'checkbox-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} +
+ {{ form_label(form, null, { widget: parent() }) }} +
+ {% endif %} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if 'radio-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} +
+ {{ form_label(form, null, { widget: parent() }) }} +
+ {% endif %} +{%- endblock radio_widget %} + +{# Labels #} + +{% block form_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %} + {{- block('form_label') -}} +{%- endblock %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {% if required %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if parent_label_class is defined %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ parent_label_class)|trim}) %} + {% endif %} + {% if label is empty %} + {% set label = name|humanize %} + {% endif %} + + {{ widget|raw }} + {{ label|trans({}, translation_domain) }} + +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} +
+
+ {{ form_label(form) }} + {{ form_widget(form) }} + {{ form_errors(form) }} +
+
+{%- endblock form_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} +
+
+ {{ form_widget(form) }} + {{ form_errors(form) }} +
+
+{%- endblock checkbox_row %} + +{% block radio_row -%} +
+
+ {{ form_widget(form) }} + {{ form_errors(form) }} +
+
+{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form.parent %}{% else %}
{% endif %} + {%- for error in errors -%} + {{ error.message }} + {% if not loop.last %}, {% endif %} + {%- endfor -%} + {% if form.parent %}{% else %}
{% endif %} + {%- endif %} +{%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php index a9d161b2b909..8e7655269753 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3LayoutTest.php @@ -115,14 +115,4 @@ protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); } - - public function testRange() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } - - public function testRangeWithMinMaxValues() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 1bce43b83780..e720836c9558 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -69,7 +69,7 @@ protected function tearDown() public function testThemeBlockInheritanceUsingUse() { $view = $this->factory - ->createNamed('name', 'email') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') ->createView() ; @@ -84,7 +84,7 @@ public function testThemeBlockInheritanceUsingUse() public function testThemeBlockInheritanceUsingExtend() { $view = $this->factory - ->createNamed('name', 'email') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') ->createView() ; @@ -99,7 +99,7 @@ public function testThemeBlockInheritanceUsingExtend() public function testThemeBlockInheritanceUsingDynamicExtend() { $view = $this->factory - ->createNamed('name', 'email') + ->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\EmailType') ->createView() ; @@ -208,14 +208,4 @@ public static function themeInheritanceProvider() array(array('parent_label.html.twig'), array('child_label.html.twig')), ); } - - public function testRange() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } - - public function testRangeWithMinMaxValues() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php index 555ea306fca8..f8f3ddf3e5bc 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionTableLayoutTest.php @@ -116,14 +116,4 @@ protected function setTheme(FormView $view, array $themes) { $this->extension->renderer->setTheme($view, $themes); } - - public function testRange() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } - - public function testRangeWithMinMaxValues() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index 48bebdc13f8f..403ed32fd801 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -43,7 +43,7 @@ public function testUnknownFragmentRenderer() ->disableOriginalConstructor() ->getMock() ; - $renderer = new FragmentHandler(array(), false, $context); + $renderer = new FragmentHandler($context); $this->setExpectedException('InvalidArgumentException', 'The "inline" renderer does not exist.'); $renderer->render('/foo'); @@ -62,7 +62,7 @@ protected function getFragmentHandler($return) $context->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); - $renderer = new FragmentHandler(array($strategy), false, $context); + $renderer = new FragmentHandler($context, array($strategy), false); return $renderer; } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 74939001f530..f56133daaab5 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -20,22 +20,22 @@ "twig/twig": "~1.20|~2.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/asset": "~2.7", - "symfony/finder": "~2.3", - "symfony/form": "~2.7,>=2.7.2", - "symfony/http-kernel": "~2.3", - "symfony/intl": "~2.3", - "symfony/routing": "~2.2", - "symfony/templating": "~2.1", - "symfony/translation": "~2.7", - "symfony/yaml": "~2.0,>=2.0.5", - "symfony/security": "~2.6", - "symfony/security-acl": "~2.6", - "symfony/stopwatch": "~2.2", - "symfony/console": "~2.7", - "symfony/var-dumper": "~2.6", - "symfony/expression-language": "~2.4" + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/asset": "~2.7|~3.0.0", + "symfony/finder": "~2.3|~3.0.0", + "symfony/form": "~2.8", + "symfony/http-kernel": "~2.8|~3.0.0", + "symfony/intl": "~2.3|~3.0.0", + "symfony/routing": "~2.2|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.7|~3.0.0", + "symfony/yaml": "~2.0,>=2.0.5|~3.0.0", + "symfony/security": "~2.6|~3.0.0", + "symfony/security-acl": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/console": "~2.7|~3.0.0", + "symfony/var-dumper": "~2.6|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0" }, "suggest": { "symfony/finder": "", @@ -57,7 +57,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig index e7a8245fb3b0..1163d283d020 100644 --- a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig @@ -1,21 +1,16 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} {% block toolbar %} - {% set dumps_count = collector.dumpsCount %} - - {% if dumps_count %} + {% if collector.dumpsCount %} {% set icon %} - - {{ dumps_count }} + {{ include('@Debug/Profiler/icon.svg') }} + {{ collector.dumpsCount }} {% endset %} {% set text %} -
- dump() -
{% for dump in collector.getDumps('html') %}
- in + {% if dump.file %} {% set link = dump.file|file_link(dump.line) %} {% if link %} @@ -26,54 +21,32 @@ {% else %} {{ dump.name }} {% endif %} - line {{ dump.line }}: + + line {{ dump.line }} + {{ dump.data|raw }}
{% endfor %} {% endset %} - {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': true } %} + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }} {% endif %} {% endblock %} {% block menu %} - - - {{- "" -}} - - {{- "" -}} - - dump() - - {{ collector.dumpsCount }} - + + {{ include('@Debug/Profiler/icon.svg') }} + Debug {% endblock %} {% block panel %} -

dump()

+

Dumped Contents

- - - {% if collector.dumpsCount %} -
    - {% for dump in collector.getDumps('html') %} -
  • - in + {% for dump in collector.getDumps('html') %} +
    +

    In {% if dump.line %} {% set link = dump.file|file_link(dump.line) %} {% if link %} @@ -84,19 +57,22 @@ {% else %} {{ dump.name }} {% endif %} - line {{ dump.line }}: - - - {% if dump.fileExcerpt %}{{ dump.fileExcerpt|raw }}{% else %}{{ dump.file|file_excerpt(dump.line) }}{% endif %} - + line {{ dump.line }} - {{ dump.data|raw }} -

  • - {% endfor %} -
+ Show code + + + + + {{ dump.data|raw }} + {% else %} -

- No dumped variable -

- {% endif %} +
+

No content was dumped.

+
+ {% endfor %} {% endblock %} diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg new file mode 100644 index 000000000000..2f7e708c8b3d --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json index e7ccc9a4b11f..66d77643479d 100644 --- a/src/Symfony/Bundle/DebugBundle/composer.json +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -17,15 +17,15 @@ ], "require": { "php": ">=5.3.9", - "symfony/http-kernel": "~2.6", - "symfony/twig-bridge": "~2.6", - "symfony/var-dumper": "~2.6" + "symfony/http-kernel": "~2.6|~3.0.0", + "symfony/twig-bridge": "~2.6|~3.0.0", + "symfony/var-dumper": "~2.6|~3.0.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/config": "~2.3", - "symfony/dependency-injection": "~2.3", - "symfony/web-profiler-bundle": "~2.3" + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/config": "~2.3|~3.0.0", + "symfony/dependency-injection": "~2.3|~3.0.0", + "symfony/web-profiler-bundle": "~2.3|~3.0.0" }, "suggest": { "symfony/config": "For service container configuration", @@ -37,7 +37,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index ad8cb2e6e988..474637d6eaf0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.8.0 +----- + + * Deprecated the Shell + 2.7.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 5b627ded6ae6..647dc0fd5850 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -11,20 +11,34 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; /** * Command that places bundle web assets into a given directory. * * @author Fabien Potencier + * @author Gábor Egyed */ class AssetsInstallCommand extends ContainerAwareCommand { + const METHOD_COPY = 'copy'; + const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; + const METHOD_RELATIVE_SYMLINK = 'relative symlink'; + + /** + * @var Filesystem + */ + private $filesystem; + /** * {@inheritdoc} */ @@ -63,8 +77,6 @@ protected function configure() /** * {@inheritdoc} - * - * @throws \InvalidArgumentException When the target directory does not exist or symlink cannot be used */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -74,77 +86,164 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new \InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target'))); } - $filesystem = $this->getContainer()->get('filesystem'); + $this->filesystem = $this->getContainer()->get('filesystem'); // Create the bundles directory otherwise symlink will fail. $bundlesDir = $targetArg.'/bundles/'; - $filesystem->mkdir($bundlesDir, 0777); + $this->filesystem->mkdir($bundlesDir, 0777); - // relative implies symlink - $symlink = $input->getOption('symlink') || $input->getOption('relative'); + $io = new SymfonyStyle($input, $output); + $io->newLine(); - if ($symlink) { - $output->writeln('Trying to install assets as symbolic links.'); + if ($input->getOption('relative')) { + $expectedMethod = self::METHOD_RELATIVE_SYMLINK; + $io->text('Trying to install assets as relative symbolic links.'); + } elseif ($input->getOption('symlink')) { + $expectedMethod = self::METHOD_ABSOLUTE_SYMLINK; + $io->text('Trying to install assets as absolute symbolic links.'); } else { - $output->writeln('Installing assets as hard copies.'); + $expectedMethod = self::METHOD_COPY; + $io->text('Installing assets as hard copies.'); } + $io->newLine(); + + $rows = array(); + $copyUsed = false; + $exitCode = 0; + /** @var BundleInterface $bundle */ foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { - if (is_dir($originDir = $bundle->getPath().'/Resources/public')) { - $targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName())); - - $output->writeln(sprintf('Installing assets for %s into %s', $bundle->getNamespace(), $targetDir)); - - $filesystem->remove($targetDir); - - if ($symlink) { - if ($input->getOption('relative')) { - $relativeOriginDir = $filesystem->makePathRelative($originDir, realpath($bundlesDir)); - } else { - $relativeOriginDir = $originDir; - } - - try { - $filesystem->symlink($relativeOriginDir, $targetDir); - if (!file_exists($targetDir)) { - throw new IOException('Symbolic link is broken'); - } - $output->writeln('The assets were installed using symbolic links.'); - } catch (IOException $e) { - if (!$input->getOption('relative')) { - $this->hardCopy($originDir, $targetDir); - $output->writeln('It looks like your system doesn\'t support symbolic links, so the assets were installed by copying them.'); - } - - // try again without the relative option - try { - $filesystem->symlink($originDir, $targetDir); - if (!file_exists($targetDir)) { - throw new IOException('Symbolic link is broken'); - } - $output->writeln('It looks like your system doesn\'t support relative symbolic links, so the assets were installed by using absolute symbolic links.'); - } catch (IOException $e) { - $this->hardCopy($originDir, $targetDir); - $output->writeln('It looks like your system doesn\'t support symbolic links, so the assets were installed by copying them.'); - } - } + if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) { + continue; + } + + $targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName())); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $message = sprintf("%s\n-> %s", $bundle->getName(), $targetDir); + } else { + $message = $bundle->getName(); + } + + try { + $this->filesystem->remove($targetDir); + + if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) { + $method = $this->relativeSymlinkWithFallback($originDir, $targetDir); + } elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } else { + $method = $this->hardCopy($originDir, $targetDir); + } + + if (self::METHOD_COPY === $method) { + $copyUsed = true; + } + + if ($method === $expectedMethod) { + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method); } else { - $this->hardCopy($originDir, $targetDir); + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method); } + } catch (\Exception $e) { + $exitCode = 1; + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()); + } + } + + $io->table(array('', 'Bundle', 'Method / Error'), $rows); + + if (0 !== $exitCode) { + $io->error('Some errors occurred while installing assets.'); + } else { + if ($copyUsed) { + $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.'); } + $io->success('All assets were successfully installed.'); } + + return $exitCode; } /** + * Try to create relative symlink. + * + * Falling back to absolute symlink and finally hard copy. + * * @param string $originDir * @param string $targetDir + * + * @return string */ - private function hardCopy($originDir, $targetDir) + private function relativeSymlinkWithFallback($originDir, $targetDir) + { + try { + $this->symlink($originDir, $targetDir, true); + $method = self::METHOD_RELATIVE_SYMLINK; + } catch (IOException $e) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } + + return $method; + } + + /** + * Try to create absolute symlink. + * + * Falling back to hard copy. + * + * @param string $originDir + * @param string $targetDir + * + * @return string + */ + private function absoluteSymlinkWithFallback($originDir, $targetDir) + { + try { + $this->symlink($originDir, $targetDir); + $method = self::METHOD_ABSOLUTE_SYMLINK; + } catch (IOException $e) { + // fall back to copy + $method = $this->hardCopy($originDir, $targetDir); + } + + return $method; + } + + /** + * Creates symbolic link. + * + * @param string $originDir + * @param string $targetDir + * @param bool $relative + * + * @throws IOException If link can not be created. + */ + private function symlink($originDir, $targetDir, $relative = false) { - $filesystem = $this->getContainer()->get('filesystem'); + if ($relative) { + $originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir))); + } + $this->filesystem->symlink($originDir, $targetDir); + if (!file_exists($targetDir)) { + throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir); + } + } - $filesystem->mkdir($targetDir, 0777); + /** + * Copies origin to target. + * + * @param string $originDir + * @param string $targetDir + * + * @return string + */ + private function hardCopy($originDir, $targetDir) + { + $this->filesystem->mkdir($targetDir, 0777); // We use a custom iterator to ignore VCS files - $filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + + return self::METHOD_COPY; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index 6e85702cf678..881a146d0d43 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Finder\Finder; @@ -53,6 +54,9 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $outputIsVerbose = $output->isVerbose(); + $output = new SymfonyStyle($input, $output); + $realCacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); $oldCacheDir = $realCacheDir.'_old'; $filesystem = $this->getContainer()->get('filesystem'); @@ -66,7 +70,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $kernel = $this->getContainer()->get('kernel'); - $output->writeln(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + $output->comment(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); $this->getContainer()->get('cache_clearer')->clear($realCacheDir); if ($input->getOption('no-warmup')) { @@ -78,14 +82,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $warmupDir = substr($realCacheDir, 0, -1).'_'; if ($filesystem->exists($warmupDir)) { - if ($output->isVerbose()) { - $output->writeln(' Clearing outdated warmup directory'); + if ($outputIsVerbose) { + $output->comment('Clearing outdated warmup directory...'); } $filesystem->remove($warmupDir); } - if ($output->isVerbose()) { - $output->writeln(' Warming up cache'); + if ($outputIsVerbose) { + $output->comment('Warming up cache...'); } $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); @@ -96,15 +100,17 @@ protected function execute(InputInterface $input, OutputInterface $output) $filesystem->rename($warmupDir, $realCacheDir); } - if ($output->isVerbose()) { - $output->writeln(' Removing old cache directory'); + if ($outputIsVerbose) { + $output->comment('Removing old cache directory...'); } $filesystem->remove($oldCacheDir); - if ($output->isVerbose()) { - $output->writeln(' Done'); + if ($outputIsVerbose) { + $output->comment('Finished'); } + + $output->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 8e08153d4a96..2352009b980e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Warmup the cache. @@ -53,8 +54,10 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $output = new SymfonyStyle($input, $output); + $kernel = $this->getContainer()->get('kernel'); - $output->writeln(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + $output->comment(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); $warmer = $this->getContainer()->get('cache_warmer'); @@ -63,5 +66,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $warmer->warmUp($this->getContainer()->getParameter('kernel.cache_dir')); + + $output->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php index bfbfaa78bc48..f092b118d29f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterApacheDumperCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\Matcher\Dumper\ApacheMatcherDumper; use Symfony\Component\Routing\RouterInterface; @@ -74,9 +75,11 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $formatter = $this->getHelper('formatter'); + $output = new SymfonyStyle($input, $output); - $output->writeln($formatter->formatSection('warning', 'The router:dump-apache command is deprecated since version 2.5 and will be removed in 3.0', 'comment')); + $output->title('Router Apache Dumper'); + + $output->caution('The router:dump-apache command is deprecated since version 2.5 and will be removed in 3.0.'); $router = $this->getContainer()->get('router'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index 6f7db66a660f..9b96c77868ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\Route; @@ -77,8 +78,10 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $output = new SymfonyStyle($input, $output); + if (false !== strpos($input->getFirstArgument(), ':d')) { - $output->writeln('The use of "router:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:router" instead.'); + $output->caution('The use of "router:debug" command is deprecated since version 2.7 and will be removed in 3.0. Use the "debug:router" instead.'); } $name = $input->getArgument('name'); @@ -89,11 +92,14 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!$route) { throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); } + $this->convertController($route); + $helper->describe($output, $route, array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'name' => $name, + 'output' => $output, )); } else { $routes = $this->getContainer()->get('router')->getRouteCollection(); @@ -106,6 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output) 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), + 'output' => $output, )); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index c1afc9d83b7e..ee690770037e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -11,11 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; @@ -60,7 +61,7 @@ protected function configure() The %command.name% shows which routes match a given request and which don't and for what reason: php %command.full_name% /foo - + or php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose @@ -75,6 +76,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $output = new SymfonyStyle($input, $output); + $router = $this->getContainer()->get('router'); $context = $router->getContext(); if (null !== $method = $input->getOption('method')) { @@ -91,25 +94,26 @@ protected function execute(InputInterface $input, OutputInterface $output) $traces = $matcher->getTraces($input->getArgument('path_info')); + $output->newLine(); + $matches = false; foreach ($traces as $trace) { if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) { - $output->writeln(sprintf('Route "%s" almost matches but %s', $trace['name'], lcfirst($trace['log']))); + $output->text(sprintf('Route "%s" almost matches but %s', $trace['name'], lcfirst($trace['log']))); } elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) { - $output->writeln(sprintf('Route "%s" matches', $trace['name'])); + $output->success(sprintf('Route "%s" matches', $trace['name'])); - $routerDebugcommand = $this->getApplication()->find('debug:router'); - $output->writeln(''); - $routerDebugcommand->run(new ArrayInput(array('name' => $trace['name'])), $output); + $routerDebugCommand = $this->getApplication()->find('debug:router'); + $routerDebugCommand->run(new ArrayInput(array('name' => $trace['name'])), $output); $matches = true; } elseif ($input->getOption('verbose')) { - $output->writeln(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); + $output->text(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log'])); } } if (!$matches) { - $output->writeln(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info'))); + $output->error(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info'))); return 1; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php index 6af52feaaec5..767a935286b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerRunCommand.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\ProcessBuilder; @@ -44,7 +45,8 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'), + new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'), new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null), new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'), )) @@ -83,6 +85,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $stdout = $output; + $output = new SymfonyStyle($input, $output); $documentRoot = $input->getOption('docroot'); if (null === $documentRoot) { @@ -90,7 +94,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!is_dir($documentRoot)) { - $output->writeln(sprintf('The given document root directory "%s" does not exist', $documentRoot)); + $output->error(sprintf('The given document root directory "%s" does not exist', $documentRoot)); return 1; } @@ -99,23 +103,21 @@ protected function execute(InputInterface $input, OutputInterface $output) $address = $input->getArgument('address'); if (false === strpos($address, ':')) { - $output->writeln('The address has to be of the form bind-address:port.'); - - return 1; + $address = $address.':'.$input->getOption('port'); } if ($this->isOtherServerProcessRunning($address)) { - $output->writeln(sprintf('A process is already listening on http://%s.', $address)); + $output->error(sprintf('A process is already listening on http://%s.', $address)); return 1; } if ('prod' === $env) { - $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); + $output->error('Running PHP built-in server in production environment is NOT recommended!'); } - $output->writeln(sprintf("Server running on http://%s\n", $address)); - $output->writeln('Quit the server with CONTROL-C.'); + $output->success(sprintf('Server running on http://%s', $address)); + $output->comment('Quit the server with CONTROL-C.'); if (null === $builder = $this->createPhpProcessBuilder($output, $address, $input->getOption('router'), $env)) { return 1; @@ -125,26 +127,28 @@ protected function execute(InputInterface $input, OutputInterface $output) $builder->setTimeout(null); $process = $builder->getProcess(); - if (OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) { + if (OutputInterface::VERBOSITY_VERBOSE > $stdout->getVerbosity()) { $process->disableOutput(); } $this ->getHelper('process') - ->run($output, $process, null, null, OutputInterface::VERBOSITY_VERBOSE); + ->run($stdout, $process, null, null, OutputInterface::VERBOSITY_VERBOSE); if (!$process->isSuccessful()) { - $output->writeln('Built-in server terminated unexpectedly'); + $errorMessages = array('Built-in server terminated unexpectedly.'); if ($process->isOutputDisabled()) { - $output->writeln('Run the command again with -v option for more details'); + $errorMessages[] = 'Run the command again with -v option for more details.'; } + + $output->error($errorMessages); } return $process->getExitCode(); } - private function createPhpProcessBuilder(OutputInterface $output, $address, $router, $env) + private function createPhpProcessBuilder(SymfonyStyle $output, $address, $router, $env) { $router = $router ?: $this ->getContainer() @@ -153,7 +157,7 @@ private function createPhpProcessBuilder(OutputInterface $output, $address, $rou ; if (!file_exists($router)) { - $output->writeln(sprintf('The given router script "%s" does not exist', $router)); + $output->error(sprintf('The given router script "%s" does not exist.', $router)); return; } @@ -162,7 +166,7 @@ private function createPhpProcessBuilder(OutputInterface $output, $address, $rou $finder = new PhpExecutableFinder(); if (false === $binary = $finder->find()) { - $output->writeln('Unable to find PHP binary to run server'); + $output->error('Unable to find PHP binary to run server.'); return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php index 04906317fa94..309ede6d068d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php @@ -11,11 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\Command; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; @@ -33,9 +33,11 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'), + new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'), new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null), new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'), + new InputOption('force', 'f', InputOption::VALUE_NONE, 'Force web server startup'), )) ->setName('server:start') ->setDescription('Starts PHP built-in web server in the background') @@ -72,11 +74,15 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $output = new SymfonyStyle($input, $output); + if (!extension_loaded('pcntl')) { - $output->writeln('This command needs the pcntl extension to run.'); - $output->writeln('You can either install it or use the server:run command instead to run the built-in web server.'); + $output->error(array( + 'This command needs the pcntl extension to run.', + 'You can either install it or use the "server:run" command instead to run the built-in web server.', + )); - if ($this->getHelper('question')->ask($input, $output, new ConfirmationQuestion('Do you want to start server:run immediately? [Yn] ', true))) { + if ($output->ask('Do you want to execute server:run immediately? [Yn] ', true)) { $command = $this->getApplication()->find('server:run'); return $command->run($input, $output); @@ -92,7 +98,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!is_dir($documentRoot)) { - $output->writeln(sprintf('The given document root directory "%s" does not exist', $documentRoot)); + $output->error(sprintf('The given document root directory "%s" does not exist.', $documentRoot)); return 1; } @@ -106,37 +112,38 @@ protected function execute(InputInterface $input, OutputInterface $output) $address = $input->getArgument('address'); if (false === strpos($address, ':')) { - $output->writeln('The address has to be of the form bind-address:port.'); - - return 1; + $address = $address.':'.$input->getOption('port'); } - if ($this->isOtherServerProcessRunning($address)) { - $output->writeln(sprintf('A process is already listening on http://%s.', $address)); + if (!$input->getOption('force') && $this->isOtherServerProcessRunning($address)) { + $output->error(array( + sprintf('A process is already listening on http://%s.', $address), + 'Use the --force option if the server process terminated unexpectedly to start a new web server process.', + )); return 1; } if ('prod' === $env) { - $output->writeln('Running PHP built-in server in production environment is NOT recommended!'); + $output->error('Running PHP built-in server in production environment is NOT recommended!'); } $pid = pcntl_fork(); if ($pid < 0) { - $output->writeln('Unable to start the server process'); + $output->error('Unable to start the server process.'); return 1; } if ($pid > 0) { - $output->writeln(sprintf('Web server listening on http://%s', $address)); + $output->success(sprintf('Web server listening on http://%s', $address)); return; } if (posix_setsid() < 0) { - $output->writeln('Unable to set the child process as session leader'); + $output->error('Unable to set the child process as session leader'); return 1; } @@ -151,7 +158,7 @@ protected function execute(InputInterface $input, OutputInterface $output) touch($lockFile); if (!$process->isRunning()) { - $output->writeln('Unable to start the server process'); + $output->error('Unable to start the server process'); unlink($lockFile); return 1; @@ -171,13 +178,13 @@ protected function execute(InputInterface $input, OutputInterface $output) * Determine the absolute file path for the router script, using the environment to choose a standard script * if no custom router script is specified. * - * @param string|null $router File path of the custom router script, if set by the user; otherwise null - * @param string $env The application environment - * @param OutputInterface $output An OutputInterface instance + * @param string|null $router File path of the custom router script, if set by the user; otherwise null + * @param string $env The application environment + * @param SymfonyStyle $output An SymfonyStyle instance * * @return string|bool The absolute file path of the router script, or false on failure */ - private function determineRouterScript($router, $env, OutputInterface $output) + private function determineRouterScript($router, $env, SymfonyStyle $output) { if (null === $router) { $router = $this @@ -188,7 +195,7 @@ private function determineRouterScript($router, $env, OutputInterface $output) } if (false === $path = realpath($router)) { - $output->writeln(sprintf('The given router script "%s" does not exist', $router)); + $output->error(sprintf('The given router script "%s" does not exist.', $router)); return false; } @@ -199,18 +206,18 @@ private function determineRouterScript($router, $env, OutputInterface $output) /** * Creates a process to start PHP's built-in web server. * - * @param OutputInterface $output A OutputInterface instance - * @param string $address IP address and port to listen to - * @param string $documentRoot The application's document root - * @param string $router The router filename + * @param SymfonyStyle $output A SymfonyStyle instance + * @param string $address IP address and port to listen to + * @param string $documentRoot The application's document root + * @param string $router The router filename * * @return Process The process */ - private function createServerProcess(OutputInterface $output, $address, $documentRoot, $router) + private function createServerProcess(SymfonyStyle $output, $address, $documentRoot, $router) { $finder = new PhpExecutableFinder(); if (false === $binary = $finder->find()) { - $output->writeln('Unable to find PHP binary to start server'); + $output->error('Unable to find PHP binary to start server.'); return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php index 2c6d2c49ff09..7b73fc9384c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Shows the status of a process that is running PHP's built-in web server in @@ -42,6 +43,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $output = new SymfonyStyle($input, $output); $address = $input->getArgument('address'); // remove an orphaned lock file @@ -50,9 +52,9 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (file_exists($this->getLockFile($address))) { - $output->writeln(sprintf('Web server still listening on http://%s', $address)); + $output->success(sprintf('Web server still listening on http://%s', $address)); } else { - $output->writeln(sprintf('No web server is listening on http://%s', $address)); + $output->warning(sprintf('No web server is listening on http://%s', $address)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php index 9b0656c220b6..d2bdaa167d80 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php @@ -14,6 +14,8 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Stops a background process running PHP's built-in web server. @@ -29,7 +31,8 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'), + new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'), + new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'), )) ->setName('server:stop') ->setDescription('Stops PHP\'s built-in web server that was started with the server:start command') @@ -52,16 +55,22 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $output = new SymfonyStyle($input, $output); + $address = $input->getArgument('address'); + if (false === strpos($address, ':')) { + $address = $address.':'.$input->getOption('port'); + } + $lockFile = $this->getLockFile($address); if (!file_exists($lockFile)) { - $output->writeln(sprintf('No web server is listening on http://%s', $address)); + $output->error(sprintf('No web server is listening on http://%s', $address)); return 1; } unlink($lockFile); - $output->writeln(sprintf('Stopped the web server listening on http://%s', $address)); + $output->success(sprintf('Stopped the web server listening on http://%s', $address)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 30d44493d4c4..a9978bdec147 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -11,12 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; @@ -48,6 +50,7 @@ protected function configure() new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'), new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Displays only missing messages'), new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'), + new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), )) ->setDescription('Displays translation messages information') ->setHelp(<<php %command.full_name% en +You can display information about translations in all registered bundles in a specific locale: + + php %command.full_name% --all en + EOF ) ; @@ -92,7 +99,9 @@ protected function execute(InputInterface $input, OutputInterface $output) $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); + /** @var TranslationLoader $loader */ $loader = $this->getContainer()->get('translation.loader'); + /** @var Kernel $kernel */ $kernel = $this->getContainer()->get('kernel'); // Define Root Path to App folder @@ -109,30 +118,23 @@ protected function execute(InputInterface $input, OutputInterface $output) } catch (\InvalidArgumentException $e) { // such a bundle does not exist, so treat the argument as path $transPaths = array($input->getArgument('bundle').'/Resources/'); + if (!is_dir($transPaths[0])) { throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); } } + } elseif ($input->getOption('all')) { + foreach ($kernel->getBundles() as $bundle) { + $transPaths[] = $bundle->getPath().'/Resources/'; + $transPaths[] = sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName()); + } } // Extract used messages - $extractedCatalogue = new MessageCatalogue($locale); - foreach ($transPaths as $path) { - $path .= 'views'; - - if (is_dir($path)) { - $this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue); - } - } + $extractedCatalogue = $this->extractMessages($locale, $transPaths); // Load defined messages - $currentCatalogue = new MessageCatalogue($locale); - foreach ($transPaths as $path) { - $path .= 'translations'; - if (is_dir($path)) { - $loader->loadMessages($path, $currentCatalogue); - } - } + $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths, $loader); // Merge defined and extracted messages to get all message ids $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue); @@ -155,24 +157,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } // Load the fallback catalogues - $fallbackCatalogues = array(); - $translator = $this->getContainer()->get('translator'); - if ($translator instanceof Translator) { - foreach ($translator->getFallbackLocales() as $fallbackLocale) { - if ($fallbackLocale === $locale) { - continue; - } - - $fallbackCatalogue = new MessageCatalogue($fallbackLocale); - foreach ($transPaths as $path) { - $path = $path.'translations'; - if (is_dir($path)) { - $loader->loadMessages($path, $fallbackCatalogue); - } - } - $fallbackCatalogues[] = $fallbackCatalogue; - } - } + $fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths, $loader); // Display header line $headers = array('State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale)); @@ -265,4 +250,74 @@ private function sanitizeString($string, $length = 40) return $string; } + + /** + * @param string $locale + * @param array $transPaths + * + * @return MessageCatalogue + */ + private function extractMessages($locale, $transPaths) + { + $extractedCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $path = $path.'views'; + if (is_dir($path)) { + $this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue); + } + } + + return $extractedCatalogue; + } + + /** + * @param string $locale + * @param array $transPaths + * @param TranslationLoader $loader + * + * @return MessageCatalogue + */ + private function loadCurrentMessages($locale, $transPaths, TranslationLoader $loader) + { + $currentCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $currentCatalogue); + } + } + + return $currentCatalogue; + } + + /** + * @param string $locale + * @param array $transPaths + * @param TranslationLoader $loader + * + * @return MessageCatalogue[] + */ + private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader $loader) + { + $fallbackCatalogues = array(); + $translator = $this->getContainer()->get('translator'); + if ($translator instanceof Translator) { + foreach ($translator->getFallbackLocales() as $fallbackLocale) { + if ($fallbackLocale === $locale) { + continue; + } + + $fallbackCatalogue = new MessageCatalogue($fallbackLocale); + foreach ($transPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $fallbackCatalogue); + } + } + $fallbackCatalogues[] = $fallbackCatalogue; + } + } + + return $fallbackCatalogues; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 64143f6e21a1..57634b0233cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -12,7 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Translation\Catalogue\DiffOperation; +use Symfony\Component\Translation\Catalogue\TargetOperation; use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -139,7 +139,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // process catalogues $operation = $input->getOption('clean') - ? new DiffOperation($currentCatalogue, $extractedCatalogue) + ? new TargetOperation($currentCatalogue, $extractedCatalogue) : new MergeOperation($currentCatalogue, $extractedCatalogue); // Exit if no messages found. diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index d10db38f37fa..b727c2bd1c34 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -86,6 +86,8 @@ public function doRun(InputInterface $input, OutputInterface $output) $this->setDispatcher($container->get('event_dispatcher')); if (true === $input->hasParameterOption(array('--shell', '-s'))) { + @trigger_error('The "--shell" option is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + $shell = new Shell($this); $shell->setProcessIsolation($input->hasParameterOption(array('--process-isolation'))); $shell->run(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index f61af1cc959c..57780d2e52fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -213,12 +213,16 @@ private function getContainerDefinitionData(Definition $definition, $omitTags = { $data = array( 'class' => (string) $definition->getClass(), - 'scope' => $definition->getScope(), + 'scope' => $definition->getScope(false), 'public' => $definition->isPublic(), 'synthetic' => $definition->isSynthetic(), 'lazy' => $definition->isLazy(), ); + if (method_exists($definition, 'isShared')) { + $data['shared'] = $definition->isShared(); + } + if (method_exists($definition, 'isSynchronized')) { $data['synchronized'] = $definition->isSynchronized(false); } @@ -290,17 +294,27 @@ private function getEventDispatcherListenersData(EventDispatcherInterface $event { $data = array(); - $registeredListeners = $eventDispatcher->getListeners($event); + $registeredListeners = $eventDispatcher->getListeners($event, true); if (null !== $event) { - foreach ($registeredListeners as $listener) { - $data[] = $this->getCallableData($listener); + krsort($registeredListeners); + foreach ($registeredListeners as $priority => $listeners) { + foreach ($listeners as $listener) { + $listener = $this->getCallableData($listener); + $listener['priority'] = $priority; + $data[] = $listener; + } } } else { ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { - foreach ($eventListeners as $eventListener) { - $data[$eventListened][] = $this->getCallableData($eventListener); + krsort($eventListeners); + foreach ($eventListeners as $priority => $listeners) { + foreach ($listeners as $listener) { + $listener = $this->getCallableData($listener); + $listener['priority'] = $priority; + $data[$eventListened][] = $listener; + } } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 8f1227e7e997..8f5437146bb5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -179,12 +179,16 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o protected function describeContainerDefinition(Definition $definition, array $options = array()) { $output = '- Class: `'.$definition->getClass().'`' - ."\n".'- Scope: `'.$definition->getScope().'`' + ."\n".'- Scope: `'.$definition->getScope(false).'`' ."\n".'- Public: '.($definition->isPublic() ? 'yes' : 'no') ."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no') ."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no') ; + if (method_exists($definition, 'isShared')) { + $output .= "\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no'); + } + if (method_exists($definition, 'isSynchronized')) { $output .= "\n".'- Synchronized: '.($definition->isSynchronized(false) ? 'yes' : 'no'); } @@ -269,21 +273,30 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev $this->write(sprintf('# %s', $title)."\n"); - $registeredListeners = $eventDispatcher->getListeners($event); + $registeredListeners = $eventDispatcher->getListeners($event, true); if (null !== $event) { - foreach ($registeredListeners as $order => $listener) { - $this->write("\n".sprintf('## Listener %d', $order + 1)."\n"); - $this->describeCallable($listener); + krsort($registeredListeners); + $order = 1; + foreach ($registeredListeners as $priority => $listeners) { + foreach ($listeners as $listener) { + $this->write("\n".sprintf('## Listener %d', $order++)."\n"); + $this->describeCallable($listener); + $this->write(sprintf('- Priority: `%d`', $priority)."\n"); + } } } else { ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { $this->write("\n".sprintf('## %s', $eventListened)."\n"); - - foreach ($eventListeners as $order => $eventListener) { - $this->write("\n".sprintf('### Listener %d', $order + 1)."\n"); - $this->describeCallable($eventListener); + krsort($eventListeners); + $order = 1; + foreach ($eventListeners as $priority => $listeners) { + foreach ($listeners as $listener) { + $this->write("\n".sprintf('### Listener %d', $order++)."\n"); + $this->describeCallable($listener); + $this->write(sprintf('- Priority: `%d`', $priority)."\n"); + } } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 22d0bde4d42e..c77598c82c63 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -34,11 +34,13 @@ class TextDescriptor extends Descriptor protected function describeRouteCollection(RouteCollection $routes, array $options = array()) { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; - $headers = array('Name', 'Method', 'Scheme', 'Host', 'Path'); - $table = new Table($this->getOutput()); - $table->setStyle('compact'); - $table->setHeaders($showControllers ? array_merge($headers, array('Controller')) : $headers); + $tableHeaders = array('Name', 'Method', 'Scheme', 'Host', 'Path'); + if ($showControllers) { + $tableHeaders[] = 'Controller'; + } + + $tableRows = array(); foreach ($routes->all() as $name => $route) { $row = array( $name, @@ -58,11 +60,16 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $row[] = $controller; } - $table->addRow($row); + $tableRows[] = $row; } - $this->writeText($this->formatSection('router', 'Current routes')."\n", $options); - $table->render(); + if (isset($options['output'])) { + $options['output']->table($tableHeaders, $tableRows); + } else { + $table = new Table($this->getOutput()); + $table->setHeaders($tableHeaders)->setRows($tableRows); + $table->render(); + } } /** @@ -73,26 +80,24 @@ protected function describeRoute(Route $route, array $options = array()) $requirements = $route->getRequirements(); unset($requirements['_scheme'], $requirements['_method']); - // fixme: values were originally written as raw - $description = array( - 'Path '.$route->getPath(), - 'Path Regex '.$route->compile()->getRegex(), - 'Host '.('' !== $route->getHost() ? $route->getHost() : 'ANY'), - 'Host Regex '.('' !== $route->getHost() ? $route->compile()->getHostRegex() : ''), - 'Scheme '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'), - 'Method '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'), - 'Class '.get_class($route), - 'Defaults '.$this->formatRouterConfig($route->getDefaults()), - 'Requirements '.($requirements ? $this->formatRouterConfig($requirements) : 'NO CUSTOM'), - 'Options '.$this->formatRouterConfig($route->getOptions()), + $tableHeaders = array('Property', 'Value'); + $tableRows = array( + array('Route Name', $options['name']), + array('Path', $route->getPath()), + array('Path Regex', $route->compile()->getRegex()), + array('Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')), + array('Host Regex', ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')), + array('Scheme', ($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')), + array('Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')), + array('Requirements', ($requirements ? $this->formatRouterConfig($requirements) : 'NO CUSTOM')), + array('Class', get_class($route)), + array('Defaults', $this->formatRouterConfig($route->getDefaults())), + array('Options', $this->formatRouterConfig($route->getOptions())), ); - if (isset($options['name'])) { - array_unshift($description, 'Name '.$options['name']); - array_unshift($description, $this->formatSection('router', sprintf('Route "%s"', $options['name']))); - } - - $this->writeText(implode("\n", $description)."\n", $options); + $table = new Table($this->getOutput()); + $table->setHeaders($tableHeaders)->setRows($tableRows); + $table->render(); } /** @@ -261,10 +266,13 @@ protected function describeContainerDefinition(Definition $definition, array $op $description[] = 'Tags -'; } - $description[] = sprintf('Scope %s', $definition->getScope()); + $description[] = sprintf('Scope %s', $definition->getScope(false)); $description[] = sprintf('Public %s', $definition->isPublic() ? 'yes' : 'no'); $description[] = sprintf('Synthetic %s', $definition->isSynthetic() ? 'yes' : 'no'); $description[] = sprintf('Lazy %s', $definition->isLazy() ? 'yes' : 'no'); + if (method_exists($definition, 'isShared')) { + $description[] = sprintf('Shared %s', $definition->isShared() ? 'yes' : 'no'); + } if (method_exists($definition, 'isSynchronized')) { $description[] = sprintf('Synchronized %s', $definition->isSynchronized(false) ? 'yes' : 'no'); } @@ -336,33 +344,16 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev $this->writeText($this->formatSection('event_dispatcher', $label)."\n", $options); - $registeredListeners = $eventDispatcher->getListeners($event); + $registeredListeners = $eventDispatcher->getListeners($event, true); if (null !== $event) { $this->writeText("\n"); - $table = new Table($this->getOutput()); - $table->getStyle()->setCellHeaderFormat('%s'); - $table->setHeaders(array('Order', 'Callable')); - - foreach ($registeredListeners as $order => $listener) { - $table->addRow(array(sprintf('#%d', $order + 1), $this->formatCallable($listener))); - } - - $table->render(); + $this->renderEventListenerTable($registeredListeners); } else { ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { $this->writeText(sprintf("\n[Event] %s\n", $eventListened), $options); - - $table = new Table($this->getOutput()); - $table->getStyle()->setCellHeaderFormat('%s'); - $table->setHeaders(array('Order', 'Callable')); - - foreach ($eventListeners as $order => $eventListener) { - $table->addRow(array(sprintf('#%d', $order + 1), $this->formatCallable($eventListener))); - } - - $table->render(); + $this->renderEventListenerTable($eventListeners); } } } @@ -377,22 +368,43 @@ protected function describeCallable($callable, array $options = array()) /** * @param array $array + */ + private function renderEventListenerTable(array $eventListeners) + { + $table = new Table($this->getOutput()); + $table->getStyle()->setCellHeaderFormat('%s'); + $table->setHeaders(array('Order', 'Callable', 'Priority')); + + krsort($eventListeners); + $order = 1; + foreach ($eventListeners as $priority => $listeners) { + foreach ($listeners as $listener) { + $table->addRow(array(sprintf('#%d', $order++), $this->formatCallable($listener), $priority)); + } + } + + $table->render(); + } + + /** + * @param array $config * * @return string */ - private function formatRouterConfig(array $array) + private function formatRouterConfig(array $config) { - if (!count($array)) { + if (empty($config)) { return 'NONE'; } - $string = ''; - ksort($array); - foreach ($array as $name => $value) { - $string .= ($string ? "\n".str_repeat(' ', 13) : '').$name.': '.$this->formatValue($value); + ksort($config); + + $configAsString = ''; + foreach ($config as $key => $value) { + $configAsString .= sprintf("\n%s: %s", $key, $this->formatValue($value)); } - return $string; + return trim($configAsString); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index c37a9009fcff..cc83d6cd1f27 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -362,10 +362,13 @@ private function getContainerDefinitionDocument(Definition $definition, $id = nu } } - $serviceXML->setAttribute('scope', $definition->getScope()); + $serviceXML->setAttribute('scope', $definition->getScope(false)); $serviceXML->setAttribute('public', $definition->isPublic() ? 'true' : 'false'); $serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false'); $serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false'); + if (method_exists($definition, 'isShared')) { + $serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false'); + } if (method_exists($definition, 'isSynchronized')) { $serviceXML->setAttribute('synchronized', $definition->isSynchronized(false) ? 'true' : 'false'); } @@ -446,13 +449,9 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher')); - $registeredListeners = $eventDispatcher->getListeners($event); + $registeredListeners = $eventDispatcher->getListeners($event, true); if (null !== $event) { - foreach ($registeredListeners as $listener) { - $callableXML = $this->getCallableDocument($listener); - - $eventDispatcherXML->appendChild($eventDispatcherXML->ownerDocument->importNode($callableXML->childNodes->item(0), true)); - } + $this->appendEventListenerDocument($eventDispatcherXML, $registeredListeners); } else { ksort($registeredListeners); @@ -460,17 +459,30 @@ private function getEventDispatcherListenersDocument(EventDispatcherInterface $e $eventDispatcherXML->appendChild($eventXML = $dom->createElement('event')); $eventXML->setAttribute('name', $eventListened); - foreach ($eventListeners as $eventListener) { - $callableXML = $this->getCallableDocument($eventListener); - - $eventXML->appendChild($eventXML->ownerDocument->importNode($callableXML->childNodes->item(0), true)); - } + $this->appendEventListenerDocument($eventXML, $eventListeners); } } return $dom; } + /** + * @param \DOMElement $element + * @param array $eventListeners + */ + private function appendEventListenerDocument(\DOMElement $element, array $eventListeners) + { + krsort($eventListeners); + foreach ($eventListeners as $priority => $listeners) { + foreach ($listeners as $listener) { + $callableXML = $this->getCallableDocument($listener); + $callableXML->childNodes->item(0)->setAttribute('priority', $priority); + + $element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), true)); + } + } + } + /** * @param callable $callable * diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php b/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php index 80daebd4cd3b..8a1e407ed253 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Shell.php @@ -16,6 +16,8 @@ /** * Shell. * + * @deprecated since version 2.8, to be removed in 3.0. + * * @author Fabien Potencier */ class Shell extends BaseShell diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php index ecdb73598c1b..b46838114b30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php @@ -159,7 +159,15 @@ protected function denyAccessUnlessGranted($attributes, $object = null, $message */ public function renderView($view, array $parameters = array()) { - return $this->container->get('templating')->render($view, $parameters); + if ($this->container->has('templating')) { + return $this->container->get('templating')->render($view, $parameters); + } + + if (!$this->container->has('twig')) { + throw new \LogicException('You can not use the "renderView" method if the Templating Component or the Twig Bundle are not available.'); + } + + return $this->container->get('twig')->render($view, $parameters); } /** @@ -173,7 +181,21 @@ public function renderView($view, array $parameters = array()) */ public function render($view, array $parameters = array(), Response $response = null) { - return $this->container->get('templating')->renderResponse($view, $parameters, $response); + if ($this->container->has('templating')) { + return $this->container->get('templating')->renderResponse($view, $parameters, $response); + } + + if (!$this->container->has('twig')) { + throw new \LogicException('You can not use the "render" method if the Templating Component or the Twig Bundle are not available.'); + } + + if (null === $response) { + $response = new Response(); + } + + $response->setContent($this->container->get('twig')->render($view, $parameters)); + + return $response; } /** @@ -187,11 +209,21 @@ public function render($view, array $parameters = array(), Response $response = */ public function stream($view, array $parameters = array(), StreamedResponse $response = null) { - $templating = $this->container->get('templating'); - - $callback = function () use ($templating, $view, $parameters) { - $templating->stream($view, $parameters); - }; + if ($this->container->has('templating')) { + $templating = $this->container->get('templating'); + + $callback = function () use ($templating, $view, $parameters) { + $templating->stream($view, $parameters); + }; + } elseif ($this->container->has('twig')) { + $twig = $this->container->get('twig'); + + $callback = function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }; + } else { + throw new \LogicException('You can not use the "stream" method if the Templating Component or the Twig Bundle are not available.'); + } if (null === $response) { return new StreamedResponse($callback); @@ -260,7 +292,16 @@ public function createForm($type, $data = null, array $options = array()) */ public function createFormBuilder($data = null, array $options = array()) { - return $this->container->get('form.factory')->createBuilder('form', $data, $options); + if (method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')) { + $type = 'Symfony\Component\Form\Extension\Core\Type\FormType'; + } else { + // not using the class name is deprecated since Symfony 2.8 and + // is only used for backwards compatibility with older versions + // of the Form component + $type = 'form'; + } + + return $this->container->get('form.factory')->createBuilder($type, $data, $options); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php new file mode 100644 index 000000000000..a8e1c549a2d6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority. + * + * @author Matthias Pigulla + * @author Benjamin Klotz + */ +class ConfigCachePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $resourceCheckers = array(); + + foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $tags) { + $priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0; + $resourceCheckers[$priority][] = new Reference($id); + } + + if (empty($resourceCheckers)) { + return; + } + + // sort by priority and flatten + krsort($resourceCheckers); + $resourceCheckers = call_user_func_array('array_merge', $resourceCheckers); + + $container->getDefinition('config_cache_factory')->replaceArgument(0, $resourceCheckers); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php index 8b46b946e550..cc0c218e4941 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/FormPass.php @@ -34,12 +34,18 @@ public function process(ContainerBuilder $container) $types = array(); foreach ($container->findTaggedServiceIds('form.type') as $serviceId => $tag) { - $alias = isset($tag[0]['alias']) - ? $tag[0]['alias'] - : $serviceId; + // The following if-else block is deprecated and will be removed + // in Symfony 3.0 + // Deprecation errors are triggered in the form registry + if (isset($tag[0]['alias'])) { + $types[$tag[0]['alias']] = $serviceId; + } else { + $types[$serviceId] = $serviceId; + } - // Flip, because we want tag aliases (= type identifiers) as keys - $types[$alias] = $serviceId; + // Support type access by FQCN + $serviceDefinition = $container->getDefinition($serviceId); + $types[$serviceDefinition->getClass()] = $serviceId; } $definition->replaceArgument(1, $types); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php new file mode 100644 index 000000000000..ed852fd04194 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Find all service tags which are defined, but not used and yield a warning log message. + * + * @author Florian Pfitzer + */ +class UnusedTagsPass implements CompilerPassInterface +{ + private $whitelist = array( + 'console.command', + 'config_cache.resource_checker', + 'data_collector', + 'form.type', + 'form.type_extension', + 'form.type_guesser', + 'kernel.cache_clearer', + 'kernel.cache_warmer', + 'kernel.event_listener', + 'kernel.event_subscriber', + 'kernel.fragment_renderer', + 'monolog.logger', + 'routing.expression_language_provider', + 'routing.loader', + 'security.expression_language_provider', + 'security.remember_me_aware', + 'security.voter', + 'serializer.encoder', + 'serializer.normalizer', + 'templating.helper', + 'translation.dumper', + 'translation.extractor', + 'translation.loader', + 'twig.extension', + 'twig.loader', + 'validator.constraint_validator', + 'validator.initializer', + ); + + public function process(ContainerBuilder $container) + { + $compiler = $container->getCompiler(); + $formatter = $compiler->getLoggingFormatter(); + $tags = array_unique(array_merge($container->findTags(), $this->whitelist)); + + foreach ($container->findUnusedTags() as $tag) { + // skip whitelisted tags + if (in_array($tag, $this->whitelist)) { + continue; + } + + // check for typos + $candidates = array(); + foreach ($tags as $definedTag) { + if ($definedTag === $tag) { + continue; + } + + if (false !== strpos($definedTag, $tag) || levenshtein($tag, $definedTag) <= strlen($tag) / 3) { + $candidates[] = $definedTag; + } + } + + $services = array_keys($container->findTaggedServiceIds($tag)); + $message = sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, implode('", "', $services)); + if (!empty($candidates)) { + $message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates)); + } + + $compiler->addLogMessage($formatter->format($this, $message)); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index c14399c0c650..023276e476b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -340,7 +340,8 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->scalarNode('cookie_path')->end() ->scalarNode('cookie_domain')->end() ->booleanNode('cookie_secure')->end() - ->booleanNode('cookie_httponly')->end() + ->booleanNode('cookie_httponly')->defaultTrue()->end() + ->booleanNode('use_cookies')->end() ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() @@ -575,6 +576,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->info('translator configuration') ->canBeEnabled() ->fixXmlConfig('fallback') + ->fixXmlConfig('path') ->children() ->arrayNode('fallbacks') ->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end() @@ -582,6 +584,9 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->defaultValue(array('en')) ->end() ->booleanNode('logging')->defaultValue($this->debug)->end() + ->arrayNode('paths') + ->prototype('scalar')->end() + ->end() ->end() ->end() ->end() @@ -656,6 +661,7 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode) ->children() ->booleanNode('enable_annotations')->defaultFalse()->end() ->scalarNode('cache')->end() + ->scalarNode('name_converter')->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 08a7b12de4eb..1ccb2188d757 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -396,7 +396,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c // session storage $container->setAlias('session.storage', $config['storage_id']); $options = array(); - foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) { + foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) { if (isset($config[$key])) { $options[$key] = $config[$key]; } @@ -695,6 +695,13 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $dirs[] = $dir; } } + foreach ($config['paths'] as $dir) { + if (is_dir($dir)) { + $dirs[] = $dir; + } else { + throw new \UnexpectedValueException(sprintf('%s defined in translator.paths does not exist or is not a directory', $dir)); + } + } if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/translations')) { $dirs[] = $dir; } @@ -966,6 +973,10 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder 1, new Reference($config['cache']) ); } + + if (isset($config['name_converter']) && $config['name_converter']) { + $container->getDefinition('serializer.normalizer.object')->replaceArgument(1, new Reference($config['name_converter'])); + } } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 499f39c6c95c..3a96f71e1176 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -28,6 +28,8 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -90,8 +92,10 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new SerializerPass()); if ($container->getParameter('kernel.debug')) { + $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new ConfigCachePass()); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml index 3e049c0f2ceb..01a271797ae9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/collectors.xml @@ -17,17 +17,17 @@ - + - + - + @@ -46,13 +46,13 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index 7d1a588fbad3..57611435ebc4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -115,6 +115,9 @@ + + + @@ -152,7 +155,7 @@ - + @@ -166,14 +169,14 @@ - + - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index f20552ed1822..a78a125940aa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -10,7 +10,7 @@ - + %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml index 1e8e3c89834d..ddf0a4e32cfe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_debug.xml @@ -21,7 +21,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml index d3687da13a5d..bb484b26a45d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.xml @@ -16,8 +16,8 @@ - %kernel.debug% + %kernel.debug% diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml index cee86b3b72cf..167deb02fb8f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml @@ -26,10 +26,10 @@ + %profiler_listener.only_exceptions% %profiler_listener.only_master_requests% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 6b2f7c968869..a750027406ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -45,6 +45,11 @@ + + + + + @@ -70,6 +75,9 @@ + + + @@ -92,9 +100,9 @@ + - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index fa7aa2b2bd80..4190dad7cb78 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -107,6 +107,7 @@ + @@ -183,6 +184,7 @@ + @@ -216,5 +218,6 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml index e5b53e94938e..db5cdb8a54c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -22,7 +22,7 @@ - null + null @@ -55,5 +55,8 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 2021505726fe..f0d54b0d76a4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -63,5 +63,21 @@ %kernel.secret% + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml index 4e609a06e5d9..428ba0c8ee48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml @@ -13,16 +13,16 @@ - + %test.client.parameters% - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 0007a360c6e4..ff4c18f212f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -45,6 +45,9 @@ %kernel.debug% + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index ccfd44e5ca48..c0bf73a94e98 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -39,6 +39,16 @@ %validator.mapping.cache.prefix% + + + + + %validator.mapping.cache.prefix% + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 9b2f3cb3a437..b7a77671c58e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -36,9 +36,9 @@ + %kernel.default_locale% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php new file mode 100644 index 000000000000..4c628f8e005b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/range_widget.html.php @@ -0,0 +1 @@ +block($form, 'form_widget_simple', array('type' => isset($type) ? $type : 'range')); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php index 14e65a7991c5..ac5a481d0b55 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget_attributes.html.php @@ -1,12 +1,11 @@ -id="escape($id) ?>" name="escape($full_name) ?>" readonly="readonly" -disabled="disabled" -required="required" +id="escape($id) ?>" name="escape($full_name) ?>" disabled="disabled" + required="required" $v): ?> -escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> +escape($k), $view->escape($view['translator']->trans($v, array(), $translation_domain))) ?> -escape($k), $view->escape($k)) ?> +escape($k), $view->escape($k)) ?> -escape($k), $view->escape($v)) ?> +escape($k), $view->escape($v)) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php index 501355beff6f..9830897914a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php @@ -14,7 +14,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Tests\Command\CacheClearCommand\Fixture\TestAppKernel; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; -use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\ConfigCacheFactory; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; @@ -56,15 +56,13 @@ public function testCacheIsFreshAfterCacheClearedWithWarmup() $metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta'); // simply check that cache is warmed up $this->assertGreaterThanOrEqual(1, count($metaFiles)); + $configCacheFactory = new ConfigCacheFactory(true); + $that = $this; + foreach ($metaFiles as $file) { - $configCache = new ConfigCache(substr($file, 0, -5), true); - $this->assertTrue( - $configCache->isFresh(), - sprintf( - 'Meta file "%s" is not fresh', - (string) $file - ) - ); + $configCacheFactory->cache(substr($file, 0, -5), function () use ($that, $file) { + $that->fail(sprintf('Meta file "%s" is not fresh', (string) $file)); + }); } // check that app kernel file present in meta file of container's cache diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php index 8a9800bc865b..38dda10ba7d7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -75,6 +75,9 @@ public function testLegacyDescribeSynchronizedServiceDefinition(Definition $defi $this->assertDescription($expectedDescription, $definition); } + /** + * @group legacy + */ public function provideLegacySynchronizedServiceDefinitionTestData() { return $this->getDescriptionTestData(ObjectsProvider::getLegacyContainerDefinitions()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php index df3f338fbb31..e0d707162ccb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/Descriptor/ObjectsProvider.php @@ -158,8 +158,8 @@ public static function getEventDispatchers() { $eventDispatcher = new EventDispatcher(); - $eventDispatcher->addListener('event1', 'global_function'); - $eventDispatcher->addListener('event1', function () { return 'Closure'; }); + $eventDispatcher->addListener('event1', 'global_function', 255); + $eventDispatcher->addListener('event1', function () { return 'Closure'; }, -1); $eventDispatcher->addListener('event2', new CallableClass()); return array('event_dispatcher_1' => $eventDispatcher); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php new file mode 100644 index 000000000000..c0eef3d627ce --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; + +class ConfigCachePassTest extends \PHPUnit_Framework_TestCase +{ + public function testThatCheckersAreProcessedInPriorityOrder() + { + $services = array( + 'checker_2' => array(0 => array('priority' => 100)), + 'checker_1' => array(0 => array('priority' => 200)), + 'checker_3' => array(), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('findTaggedServiceIds', 'getDefinition', 'hasDefinition') + ); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + $container->expects($this->atLeastOnce()) + ->method('getDefinition') + ->with('config_cache_factory') + ->will($this->returnValue($definition)); + + $definition->expects($this->once()) + ->method('replaceArgument') + ->with(0, array( + new Reference('checker_1'), + new Reference('checker_2'), + new Reference('checker_3'), + )); + + $pass = new ConfigCachePass(); + $pass->process($container); + } + + public function testThatCheckersCanBeMissing() + { + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('findTaggedServiceIds') + ); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue(array())); + + $pass = new ConfigCachePass(); + $pass->process($container); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php new file mode 100644 index 000000000000..d7dc9d8a347d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/FormPassTest.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\AbstractType; + +/** + * @author Bernhard Schussek + */ +class FormPassTest extends \PHPUnit_Framework_TestCase +{ + public function testDoNothingIfFormExtensionNotLoaded() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $container->compile(); + + $this->assertFalse($container->hasDefinition('form.extension')); + } + + public function testAddTaggedTypes() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition(__CLASS__.'_Type1'); + $definition1->addTag('form.type'); + $definition2 = new Definition(__CLASS__.'_Type2'); + $definition2->addTag('form.type'); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.type1', $definition1); + $container->setDefinition('my.type2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertEquals(array( + // As of Symfony 2.8, the class is used to look up types + __CLASS__.'_Type1' => 'my.type1', + __CLASS__.'_Type2' => 'my.type2', + // Before Symfony 2.8, the service ID was used as default alias + 'my.type1' => 'my.type1', + 'my.type2' => 'my.type2', + ), $extDefinition->getArgument(1)); + } + + public function testUseCustomAliasIfSet() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition(__CLASS__.'_Type1'); + $definition1->addTag('form.type', array('alias' => 'mytype1')); + $definition2 = new Definition(__CLASS__.'_Type2'); + $definition2->addTag('form.type', array('alias' => 'mytype2')); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.type1', $definition1); + $container->setDefinition('my.type2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertEquals(array( + __CLASS__.'_Type1' => 'my.type1', + __CLASS__.'_Type2' => 'my.type2', + 'mytype1' => 'my.type1', + 'mytype2' => 'my.type2', + ), $extDefinition->getArgument(1)); + } + + public function testAddTaggedTypeExtensions() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition('stdClass'); + $definition1->addTag('form.type_extension', array('alias' => 'type1')); + $definition2 = new Definition('stdClass'); + $definition2->addTag('form.type_extension', array('alias' => 'type1')); + $definition3 = new Definition('stdClass'); + $definition3->addTag('form.type_extension', array('alias' => 'type2')); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.type_extension1', $definition1); + $container->setDefinition('my.type_extension2', $definition2); + $container->setDefinition('my.type_extension3', $definition3); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertSame(array( + 'type1' => array( + 'my.type_extension1', + 'my.type_extension2', + ), + 'type2' => array( + 'my.type_extension3', + ), + ), $extDefinition->getArgument(2)); + } + + public function testAddTaggedGuessers() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new FormPass()); + + $extDefinition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension'); + $extDefinition->setArguments(array( + new Reference('service_container'), + array(), + array(), + array(), + )); + + $definition1 = new Definition('stdClass'); + $definition1->addTag('form.type_guesser'); + $definition2 = new Definition('stdClass'); + $definition2->addTag('form.type_guesser'); + + $container->setDefinition('form.extension', $extDefinition); + $container->setDefinition('my.guesser1', $definition1); + $container->setDefinition('my.guesser2', $definition2); + + $container->compile(); + + $extDefinition = $container->getDefinition('form.extension'); + + $this->assertSame(array( + 'my.guesser1', + 'my.guesser2', + ), $extDefinition->getArgument(3)); + } +} + +class FormPassTest_Type1 extends AbstractType +{ +} + +class FormPassTest_Type2 extends AbstractType +{ +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php new file mode 100644 index 000000000000..f354007bb982 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/UnusedTagsPassTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; + +class UnusedTagsPassTest extends \PHPUnit_Framework_TestCase +{ + public function testProcess() + { + $pass = new UnusedTagsPass(); + + $formatter = $this->getMock('Symfony\Component\DependencyInjection\Compiler\LoggingFormatter'); + $formatter + ->expects($this->at(0)) + ->method('format') + ->with($pass, 'Tag "kenrel.event_subscriber" was defined on service(s) "foo", "bar", but was never used. Did you mean "kernel.event_subscriber"?') + ; + + $compiler = $this->getMock('Symfony\Component\DependencyInjection\Compiler\Compiler'); + $compiler->expects($this->once())->method('getLoggingFormatter')->will($this->returnValue($formatter)); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder', + array('findTaggedServiceIds', 'getCompiler', 'findUnusedTags', 'findTags') + ); + $container->expects($this->once())->method('getCompiler')->will($this->returnValue($compiler)); + $container->expects($this->once()) + ->method('findTags') + ->will($this->returnValue(array('kenrel.event_subscriber'))); + $container->expects($this->once()) + ->method('findUnusedTags') + ->will($this->returnValue(array('kenrel.event_subscriber', 'form.type'))); + $container->expects($this->once()) + ->method('findTaggedServiceIds') + ->with('kenrel.event_subscriber') + ->will($this->returnValue(array( + 'foo' => array(), + 'bar' => array(), + ))); + + $pass->process($container); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index f8bb4cb2fc73..04b6c95b46da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -157,6 +157,7 @@ protected static function getBundleDefaultConfig() 'enabled' => false, 'fallbacks' => array('en'), 'logging' => true, + 'paths' => array(), ), 'validation' => array( 'enabled' => false, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 5022aeaf9f20..7c135dfc352f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -32,7 +32,8 @@ 'cookie_path' => '/', 'cookie_domain' => 'example.com', 'cookie_secure' => true, - 'cookie_httponly' => true, + 'cookie_httponly' => false, + 'use_cookies' => true, 'gc_maxlifetime' => 90000, 'gc_divisor' => 108, 'gc_probability' => 1, @@ -50,6 +51,7 @@ 'translator' => array( 'enabled' => true, 'fallback' => 'fr', + 'paths' => array('%kernel.root_dir%/Fixtures/translations'), ), 'validation' => array( 'enabled' => true, @@ -60,7 +62,12 @@ 'debug' => true, 'file_cache_dir' => '%kernel.cache_dir%/annotations', ), - 'serializer' => array('enabled' => true), + 'serializer' => array( + 'enabled' => true, + 'enable_annotations' => true, + 'cache' => 'serializer.mapping.cache.apc', + 'name_converter' => 'serializer.name_converter.camel_case_to_snake_case', + ), 'ide' => 'file%%link%%format', 'request' => array( 'formats' => array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_disabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_disabled.php new file mode 100644 index 000000000000..dedd090beb77 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_disabled.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'serializer' => array( + 'enabled' => false, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_enabled.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_enabled.php new file mode 100644 index 000000000000..eadad57ec718 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/serializer_enabled.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', array( + 'serializer' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/translations/test_paths.en.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/translations/test_paths.en.yml new file mode 100644 index 000000000000..d4e682c47c9c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/translations/test_paths.en.yml @@ -0,0 +1,2 @@ +custom: + paths: test diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index 5b16a5979609..b94f44762fda 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -14,7 +14,7 @@ - + text/csv @@ -34,9 +34,11 @@ theme2 - + + %kernel.root_dir%/Fixtures/translations + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_disabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_disabled.xml new file mode 100644 index 000000000000..73f1dccb1a6e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_disabled.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_enabled.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_enabled.xml new file mode 100644 index 000000000000..e202fc1b5ee2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/serializer_enabled.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index be1b41e25f89..13ceca12a4f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -24,7 +24,8 @@ framework: cookie_path: / cookie_domain: example.com cookie_secure: true - cookie_httponly: true + cookie_httponly: false + use_cookies: true gc_probability: 1 gc_divisor: 108 gc_maxlifetime: 90000 @@ -39,6 +40,7 @@ framework: translator: enabled: true fallback: fr + paths: ['%kernel.root_dir%/Fixtures/translations'] validation: enabled: true cache: apc @@ -46,7 +48,11 @@ framework: cache: file debug: true file_cache_dir: %kernel.cache_dir%/annotations - serializer: { enabled: true } + serializer: + enabled: true + enable_annotations: true + cache: serializer.mapping.cache.apc + name_converter: serializer.name_converter.camel_case_to_snake_case ide: file%%link%%format request: formats: diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_disabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_disabled.yml new file mode 100644 index 000000000000..330e19a6976e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_disabled.yml @@ -0,0 +1,3 @@ +framework: + serializer: + enabled: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_enabled.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_enabled.yml new file mode 100644 index 000000000000..40a1ff7d65b3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/serializer_enabled.yml @@ -0,0 +1,3 @@ +framework: + serializer: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index db8c8cd689f3..ad0c427100dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -149,7 +149,8 @@ public function testSession() $this->assertEquals('/', $options['cookie_path']); $this->assertEquals('example.com', $options['cookie_domain']); $this->assertTrue($options['cookie_secure']); - $this->assertTrue($options['cookie_httponly']); + $this->assertFalse($options['cookie_httponly']); + $this->assertTrue($options['use_cookies']); $this->assertEquals(108, $options['gc_divisor']); $this->assertEquals(1, $options['gc_probability']); $this->assertEquals(90000, $options['gc_maxlifetime']); @@ -242,9 +243,14 @@ public function testTranslator() $files, '->registerTranslatorConfiguration() finds Security translation resources' ); + $this->assertContains( + strtr(__DIR__.'/Fixtures/translations/test_paths.en.yml', '/', DIRECTORY_SEPARATOR), + $files, + '->registerTranslatorConfiguration() finds translation resources in custom paths' + ); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals(array('fr'), $calls[0][1][0]); + $this->assertEquals(array('fr'), $calls[1][1][0]); } public function testTranslatorMultipleFallbacks() @@ -252,7 +258,7 @@ public function testTranslatorMultipleFallbacks() $container = $this->createContainerFromFile('translator_fallbacks'); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals(array('en', 'fr'), $calls[0][1][0]); + $this->assertEquals(array('en', 'fr'), $calls[1][1][0]); } /** @@ -441,6 +447,13 @@ public function testSerializerEnabled() { $container = $this->createContainerFromFile('full'); $this->assertTrue($container->has('serializer')); + + $argument = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0); + + $this->assertCount(1, $argument); + $this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass()); + $this->assertEquals(new Reference('serializer.mapping.cache.apc'), $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1)); + $this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1)); } public function testAssetHelperWhenAssetsAreEnabled() @@ -459,6 +472,20 @@ public function testAssetHelperWhenTemplatesAreEnabledAndAssetsAreDisabled() $this->assertSame('assets.packages', (string) $packages); } + public function testSerializerServiceIsRegisteredWhenEnabled() + { + $container = $this->createContainerFromFile('serializer_enabled'); + + $this->assertTrue($container->hasDefinition('serializer')); + } + + public function testSerializerServiceIsNotRegisteredWhenDisabled() + { + $container = $this->createContainerFromFile('serializer_disabled'); + + $this->assertFalse($container->hasDefinition('serializer')); + } + protected function createContainer(array $data = array()) { return new ContainerBuilder(new ParameterBag(array_merge(array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json index 047f4e8c16a4..9be35dad0705 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.json @@ -6,6 +6,7 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": false, "abstract": true, "file": null, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md index 1c3b958bd92c..de404d24d0f5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.md @@ -12,6 +12,7 @@ definition_1 - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: no - Abstract: yes - Factory Class: `Full\Qualified\FactoryClass` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml index b21190dc7983..59a1e85c6bb8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_public.xml @@ -2,7 +2,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json index 3397fd67acd6..c76d13ee4234 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.json @@ -6,6 +6,7 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": false, "abstract": true, "file": null, @@ -21,6 +22,7 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, "file": "\/path\/to\/file", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md index b3018b80b7f3..3a3de41c409e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.md @@ -12,6 +12,7 @@ definition_1 - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: no - Abstract: yes - Factory Class: `Full\Qualified\FactoryClass` @@ -25,6 +26,7 @@ definition_2 - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no - File: `/path/to/file` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml index 7aecc4f629e7..5ceee2772a99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_services.xml @@ -2,10 +2,10 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json index 53bf114e81e0..40a3da00963a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.json @@ -6,6 +6,7 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, "file": "\/path\/to\/file", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md index 56a2c390779a..c0d4f11e3326 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.md @@ -12,6 +12,7 @@ definition_2 - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no - File: `/path/to/file` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml index d6ac0b750b16..51bb9c254f54 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tag1.xml @@ -1,6 +1,6 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json index 3837b95cb89e..6844d2d18076 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.json @@ -6,6 +6,7 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, "file": "\/path\/to\/file", @@ -20,6 +21,7 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, "file": "\/path\/to\/file", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md index 6577037f9c00..551c9cb24b29 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.md @@ -12,6 +12,7 @@ definition_2 - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no - File: `/path/to/file` @@ -30,6 +31,7 @@ definition_2 - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no - File: `/path/to/file` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml index be9d2f015bd2..01f324860885 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/builder_1_tags.xml @@ -1,12 +1,12 @@ - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json index 8de781dfc45a..92f1300b4bd5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.json @@ -4,6 +4,7 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": false, "abstract": true, "file": null, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md index 68d3569732c6..6c18a6c2bbf8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.md @@ -3,6 +3,7 @@ - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: no - Abstract: yes - Factory Class: `Full\Qualified\FactoryClass` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt index af495497dd35..4c37faccf29a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.txt @@ -5,6 +5,7 @@ Public yes Synthetic no Lazy yes +Shared yes Synchronized no Abstract yes Factory Class Full\Qualified\FactoryClass diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml index 92a9bbd70bd3..ec8a8cefa9e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_1.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json index 9d58434c17e1..22a094928a48 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.json @@ -4,6 +4,7 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, "file": "\/path\/to\/file", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md index 6b2f14651d30..866858799427 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.md @@ -3,6 +3,7 @@ - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no - File: `/path/to/file` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt index 28a00d583b09..62fc7d2dc6c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.txt @@ -8,6 +8,7 @@ Public no Synthetic yes Lazy no +Shared yes Synchronized no Abstract no Required File /path/to/file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml index f128e522e599..ce9b1d05220c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/definition_2.xml @@ -1,5 +1,5 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json index e40e130d453c..4b68f0cefc0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.json @@ -1,9 +1,11 @@ [ { "type": "function", - "name": "global_function" + "name": "global_function", + "priority": 255 }, { - "type": "closure" + "type": "closure", + "priority": -1 } ] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md index 206c44f71752..98b81ecdce42 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.md @@ -4,7 +4,9 @@ - Type: `function` - Name: `global_function` +- Priority: `255` ## Listener 2 - Type: `closure` +- Priority: `-1` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt index 22b17a19cfb9..45035d12d622 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.txt @@ -1,8 +1,8 @@ [event_dispatcher] Registered listeners for event event1 -+-------+-------------------+ -| Order | Callable | -+-------+-------------------+ -| #1 | global_function() | -| #2 | \Closure() | -+-------+-------------------+ ++-------+-------------------+----------+ +| Order | Callable | Priority | ++-------+-------------------+----------+ +| #1 | global_function() | 255 | +| #2 | \Closure() | -1 | ++-------+-------------------+----------+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml index 4806f1f1280c..bc03189af7b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_event1.xml @@ -1,5 +1,5 @@ - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json index 56fc7a4f1e54..30772d9a4a21 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.json @@ -2,16 +2,19 @@ "event1": [ { "type": "function", - "name": "global_function" + "name": "global_function", + "priority": 255 }, { - "type": "closure" + "type": "closure", + "priority": -1 } ], "event2": [ { "type": "object", - "name": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass" + "name": "Symfony\\Bundle\\FrameworkBundle\\Tests\\Console\\Descriptor\\CallableClass", + "priority": 0 } ] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md index ad4b79e3117f..eb809789d5f1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.md @@ -6,10 +6,12 @@ - Type: `function` - Name: `global_function` +- Priority: `255` ### Listener 2 - Type: `closure` +- Priority: `-1` ## event2 @@ -17,3 +19,4 @@ - Type: `object` - Name: `Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\CallableClass` +- Priority: `0` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt index 95a5b4648e93..88e5dc9c8969 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.txt @@ -1,16 +1,16 @@ [event_dispatcher] Registered listeners by event [Event] event1 -+-------+-------------------+ -| Order | Callable | -+-------+-------------------+ -| #1 | global_function() | -| #2 | \Closure() | -+-------+-------------------+ ++-------+-------------------+----------+ +| Order | Callable | Priority | ++-------+-------------------+----------+ +| #1 | global_function() | 255 | +| #2 | \Closure() | -1 | ++-------+-------------------+----------+ [Event] event2 -+-------+-----------------------------------------------------------------------------------+ -| Order | Callable | -+-------+-----------------------------------------------------------------------------------+ -| #1 | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\CallableClass::__invoke() | -+-------+-----------------------------------------------------------------------------------+ ++-------+-----------------------------------------------------------------------------------+----------+ +| Order | Callable | Priority | ++-------+-----------------------------------------------------------------------------------+----------+ +| #1 | Symfony\Bundle\FrameworkBundle\Tests\Console\Descriptor\CallableClass::__invoke() | 0 | ++-------+-----------------------------------------------------------------------------------+----------+ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml index 3e4b20d82379..d7443f974366 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/event_dispatcher_1_events.xml @@ -1,10 +1,10 @@ - - + + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json index 6372d9e5b56d..b7a5dec87df7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.json @@ -4,6 +4,7 @@ "public": true, "synthetic": false, "lazy": true, + "shared": true, "synchronized": true, "abstract": true, "file": null, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md index d9832a1511ab..f527ab9ff874 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.md @@ -3,6 +3,7 @@ - Public: yes - Synthetic: no - Lazy: yes +- Shared: yes - Synchronized: yes - Abstract: yes - Factory Class: `Full\Qualified\FactoryClass` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt index 3d9cbb2077c3..09340efcf5d8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.txt @@ -5,6 +5,7 @@ Public yes Synthetic no Lazy yes +Shared yes Synchronized yes Abstract yes Factory Class Full\Qualified\FactoryClass diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml index 75d082024457..6088d9a21b5a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_1.xml @@ -1,2 +1,2 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json index 278a5bfed413..bb0f5685f36a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.json @@ -4,6 +4,7 @@ "public": false, "synthetic": true, "lazy": false, + "shared": true, "synchronized": false, "abstract": false, "file": "\/path\/to\/file", diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md index f552debbf18b..43227638d88a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.md @@ -3,6 +3,7 @@ - Public: no - Synthetic: yes - Lazy: no +- Shared: yes - Synchronized: no - Abstract: no - File: `/path/to/file` diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt index 28a00d583b09..62fc7d2dc6c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.txt @@ -8,6 +8,7 @@ Public no Synthetic yes Lazy no +Shared yes Synchronized no Abstract no Required File /path/to/file diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml index dd3e2e06d717..7a2154487d1e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/legacy_synchronized_service_definition_2.xml @@ -1,5 +1,5 @@ - + val1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index 4af5b929cbb5..52d91ec12758 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -128,14 +128,4 @@ public static function themeInheritanceProvider() array(array('TestBundle:Parent'), array('TestBundle:Child')), ); } - - public function testRange() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } - - public function testRangeWithMinMaxValues() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index 1bf641fe1b93..4a5c025c6d37 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -115,14 +115,4 @@ protected function setTheme(FormView $view, array $themes) { $this->engine->get('form')->setTheme($view, $themes); } - - public function testRange() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } - - public function testRangeWithMinMaxValues() - { - // No-op for forward compatibility with AbstractLayoutTest 2.8 - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 93b63cd8ed02..b60e65c51767 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -17,36 +17,36 @@ ], "require": { "php": ">=5.3.9", - "symfony/asset": "~2.7", - "symfony/dependency-injection": "~2.6,>=2.6.2", - "symfony/config": "~2.4", - "symfony/event-dispatcher": "~2.5", - "symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4", - "symfony/http-kernel": "~2.7", - "symfony/filesystem": "~2.3", - "symfony/routing": "~2.6,>2.6.4", - "symfony/security-core": "~2.6", - "symfony/security-csrf": "~2.6", - "symfony/stopwatch": "~2.3", - "symfony/templating": "~2.1", - "symfony/translation": "~2.7", + "symfony/asset": "~2.7|~3.0.0", + "symfony/dependency-injection": "~2.8", + "symfony/config": "~2.8", + "symfony/event-dispatcher": "~2.8|~3.0.0", + "symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4|~3.0.0", + "symfony/http-kernel": "~2.8", + "symfony/filesystem": "~2.3|~3.0.0", + "symfony/routing": "~2.8|~3.0.0", + "symfony/security-core": "~2.6|~3.0.0", + "symfony/security-csrf": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.8", "doctrine/annotations": "~1.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/browser-kit": "~2.4", - "symfony/console": "~2.7", - "symfony/css-selector": "~2.0,>=2.0.5", - "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/finder": "~2.0,>=2.0.5", - "symfony/intl": "~2.3", - "symfony/security": "~2.6", - "symfony/form": "~2.7,>=2.7.2", - "symfony/class-loader": "~2.1", - "symfony/expression-language": "~2.6", - "symfony/process": "~2.0,>=2.0.5", - "symfony/validator": "~2.5", - "symfony/yaml": "~2.0,>=2.0.5" + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/browser-kit": "~2.4|~3.0.0", + "symfony/console": "~2.7|~3.0.0", + "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0", + "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0", + "symfony/finder": "~2.0,>=2.0.5|~3.0.0", + "symfony/intl": "~2.3|~3.0.0", + "symfony/security": "~2.6|~3.0.0", + "symfony/form": "~2.8", + "symfony/class-loader": "~2.1|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/process": "~2.0,>=2.0.5|~3.0.0", + "symfony/validator": "~2.5|~3.0.0", + "symfony/yaml": "~2.0,>=2.0.5|~3.0.0" }, "suggest": { "symfony/console": "For using the console commands", @@ -63,7 +63,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 657777f02000..1508f58c647e 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,12 +1,18 @@ CHANGELOG ========= +2.8.0 +----- + + * deprecated the `key` setting of `anonymous` and `remember_me` in favor of the + `secret` setting. + 2.6.0 ----- * Added the possibility to override the default success/failure handler to get the provider key and the options injected - * Deprecated the `security.context` service for the `security.token_storage` and + * Deprecated the `security.context` service for the `security.token_storage` and `security.authorization_checker` services. 2.4.0 diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index a6ea333d95b8..7b3e111974ea 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\Security\Core\Role\RoleInterface; +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; /** * SecurityDataCollector. @@ -27,17 +28,20 @@ class SecurityDataCollector extends DataCollector { private $tokenStorage; private $roleHierarchy; + private $logoutUrlGenerator; /** * Constructor. * * @param TokenStorageInterface|null $tokenStorage * @param RoleHierarchyInterface|null $roleHierarchy + * @param LogoutUrlGenerator|null $logoutUrlGenerator */ - public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null) + public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null) { $this->tokenStorage = $tokenStorage; $this->roleHierarchy = $roleHierarchy; + $this->logoutUrlGenerator = $logoutUrlGenerator; } /** @@ -50,6 +54,7 @@ public function collect(Request $request, Response $response, \Exception $except 'enabled' => false, 'authenticated' => false, 'token_class' => null, + 'logout_url' => null, 'user' => '', 'roles' => array(), 'inherited_roles' => array(), @@ -60,6 +65,7 @@ public function collect(Request $request, Response $response, \Exception $except 'enabled' => true, 'authenticated' => false, 'token_class' => null, + 'logout_url' => null, 'user' => '', 'roles' => array(), 'inherited_roles' => array(), @@ -68,6 +74,7 @@ public function collect(Request $request, Response $response, \Exception $except } else { $inheritedRoles = array(); $assignedRoles = $token->getRoles(); + if (null !== $this->roleHierarchy) { $allRoles = $this->roleHierarchy->getReachableRoles($assignedRoles); foreach ($allRoles as $role) { @@ -76,10 +83,21 @@ public function collect(Request $request, Response $response, \Exception $except } } } + + $logoutUrl = null; + try { + if (null !== $this->logoutUrlGenerator) { + $logoutUrl = $this->logoutUrlGenerator->getLogoutPath(); + } + } catch(\Exception $e) { + // fail silently when the logout URL cannot be generated + } + $this->data = array( 'enabled' => true, 'authenticated' => $token->isAuthenticated(), 'token_class' => get_class($token), + 'logout_url' => $logoutUrl, 'user' => $token->getUsername(), 'roles' => array_map(function (RoleInterface $role) { return $role->getRole();}, $assignedRoles), 'inherited_roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles), @@ -159,6 +177,16 @@ public function getTokenClass() return $this->data['token_class']; } + /** + * Get the provider key (i.e. the name of the active firewall). + * + * @return string The provider key + */ + public function getLogoutUrl() + { + return $this->data['logout_url']; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php index 497807174d24..992d5a5f7c6f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddSecurityVotersPass.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\LogicException; /** * Adds all configured security voters to the access decision manager. @@ -40,6 +41,10 @@ public function process(ContainerBuilder $container) $voters = iterator_to_array($voters); ksort($voters); - $container->getDefinition('security.access.decision_manager')->replaceArgument(0, array_values($voters)); + if (!$voters) { + throw new LogicException('No security voters found. You need to tag at least one with "security.voter"'); + } + + $container->getDefinition('security.access.decision_manager')->addMethodCall('setVoters', array(array_values($voters))); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index 36dbcdf89a1b..c2381a4344cb 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -286,8 +286,22 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto ->end() ->arrayNode('anonymous') ->canBeUnset() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['key']); }) + ->then(function ($v) { + if (isset($v['secret'])) { + throw new \LogicException('Cannot set both key and secret options for security.firewall.anonymous, use only secret instead.'); + } + + @trigger_error('security.firewall.anonymous.key is deprecated since version 2.8 and will be removed in 3.0. Use security.firewall.anonymous.secret instead.', E_USER_DEPRECATED); + + $v['secret'] = $v['key']; + + unset($v['key']); + }) + ->end() ->children() - ->scalarNode('key')->defaultValue(uniqid('', true))->end() + ->scalarNode('secret')->defaultValue(uniqid('', true))->end() ->end() ->end() ->arrayNode('switch_user') diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php new file mode 100644 index 000000000000..c758b32b8d86 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginLdapFactory.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * FormLoginLdapFactory creates services for form login ldap authentication. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class FormLoginLdapFactory extends FormLoginFactory +{ + protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) + { + $provider = 'security.authentication.provider.ldap_bind.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind')) + ->replaceArgument(0, new Reference($userProviderId)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference($config['service'])) + ->replaceArgument(4, $config['dn_string']) + ; + + return $provider; + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->end() + ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->end() + ; + } + + public function getKey() + { + return 'form-login-ldap'; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php new file mode 100644 index 000000000000..23752677775e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Configures the "guard" authentication provider key under a firewall. + * + * @author Ryan Weaver + */ +class GuardAuthenticationFactory implements SecurityFactoryInterface +{ + public function getPosition() + { + return 'pre_auth'; + } + + public function getKey() + { + return 'guard'; + } + + public function addConfiguration(NodeDefinition $node) + { + $node + ->fixXmlConfig('authenticator') + ->children() + ->scalarNode('provider') + ->info('A key from the "providers" section of your security config, in case your user provider is different than the firewall') + ->end() + ->scalarNode('entry_point') + ->info('A service id (of one of your authenticators) whose start() method should be called when an anonymous user hits a page that requires authentication') + ->defaultValue(null) + ->end() + ->arrayNode('authenticators') + ->info('An array of service ids for all of your "authenticators"') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->end() + ; + } + + public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + { + $authenticatorIds = $config['authenticators']; + $authenticatorReferences = array(); + foreach ($authenticatorIds as $authenticatorId) { + $authenticatorReferences[] = new Reference($authenticatorId); + } + + // configure the GuardAuthenticationFactory to have the dynamic constructor arguments + $providerId = 'security.authentication.provider.guard.'.$id; + $container + ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.guard')) + ->replaceArgument(0, $authenticatorReferences) + ->replaceArgument(1, new Reference($userProvider)) + ->replaceArgument(2, $id) + ; + + // listener + $listenerId = 'security.authentication.listener.guard.'.$id; + $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.guard')); + $listener->replaceArgument(2, $id); + $listener->replaceArgument(3, $authenticatorReferences); + + // determine the entryPointId to use + $entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config); + + // this is always injected - then the listener decides if it should be used + $container + ->getDefinition($listenerId) + ->addTag('security.remember_me_aware', array('id' => $id, 'provider' => $userProvider)); + + return array($providerId, $listenerId, $entryPointId); + } + + private function determineEntryPoint($defaultEntryPointId, array $config) + { + if ($defaultEntryPointId) { + // explode if they've configured the entry_point, but there is already one + if ($config['entry_point']) { + throw new \LogicException(sprintf( + 'The guard authentication provider cannot use the "%s" entry_point because another entry point is already configured by another provider! Either remove the other provider or move the entry_point configuration as a root key under your firewall (i.e. at the same level as "guard").', + $config['entry_point'] + )); + } + + return $defaultEntryPointId; + } + + if ($config['entry_point']) { + // if it's configured explicitly, use it! + return $config['entry_point']; + } + + $authenticatorIds = $config['authenticators']; + if (count($authenticatorIds) == 1) { + // if there is only one authenticator, use that as the entry point + return array_shift($authenticatorIds); + } + + // we have multiple entry points - we must ask them to configure one + throw new \LogicException(sprintf( + 'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of you configurators (%s)', + implode(', ', $authenticatorIds) + )); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php new file mode 100644 index 000000000000..23c013058408 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicLdapFactory.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * HttpBasicFactory creates services for HTTP basic authentication. + * + * @author Fabien Potencier + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class HttpBasicLdapFactory extends HttpBasicFactory +{ + public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) + { + $provider = 'security.authentication.provider.ldap_bind.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind')) + ->replaceArgument(0, new Reference($userProvider)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference($config['service'])) + ->replaceArgument(4, $config['dn_string']) + ; + + // entry point + $entryPointId = $this->createEntryPoint($container, $id, $config, $defaultEntryPoint); + + // listener + $listenerId = 'security.authentication.listener.basic.'.$id; + $listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.basic')); + $listener->replaceArgument(2, $id); + $listener->replaceArgument(3, new Reference($entryPointId)); + + return array($provider, $listenerId, $entryPointId); + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->end() + ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->end() + ; + } + + public function getKey() + { + return 'http-basic-ldap'; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index 7aa4f5baa03e..d8321f52456a 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -35,7 +35,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, $authProviderId = 'security.authentication.provider.rememberme.'.$id; $container ->setDefinition($authProviderId, new DefinitionDecorator('security.authentication.provider.rememberme')) - ->addArgument($config['key']) + ->addArgument($config['secret']) ->addArgument($id) ; @@ -56,7 +56,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, } $rememberMeServices = $container->setDefinition($rememberMeServicesId, new DefinitionDecorator($templateId)); - $rememberMeServices->replaceArgument(1, $config['key']); + $rememberMeServices->replaceArgument(1, $config['secret']); $rememberMeServices->replaceArgument(2, $id); if (isset($config['token_provider'])) { @@ -120,10 +120,25 @@ public function getKey() public function addConfiguration(NodeDefinition $node) { $node->fixXmlConfig('user_provider'); - $builder = $node->children(); + $builder = $node + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['key']); }) + ->then(function ($v) { + if (isset($v['secret'])) { + throw new \LogicException('Cannot set both key and secret options for remember_me, use only secret instead.'); + } + + @trigger_error('remember_me.key is deprecated since version 2.8 and will be removed in 3.0. Use remember_me.secret instead.', E_USER_DEPRECATED); + + $v['secret'] = $v['key']; + + unset($v['key']); + }) + ->end() + ->children(); $builder - ->scalarNode('key')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('secret')->isRequired()->cannotBeEmpty()->end() ->scalarNode('token_provider')->end() ->arrayNode('user_providers') ->beforeNormalization() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php new file mode 100644 index 000000000000..068cda6a1fe8 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/LdapFactory.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * LdapFactory creates services for Ldap user provider. + * + * @author Grégoire Pineau + * @author Charles Sarrazin + */ +class LdapFactory implements UserProviderFactoryInterface +{ + public function create(ContainerBuilder $container, $id, $config) + { + $container + ->setDefinition($id, new DefinitionDecorator('security.user.provider.ldap')) + ->replaceArgument(0, new Reference($config['service'])) + ->replaceArgument(1, $config['base_dn']) + ->replaceArgument(2, $config['search_dn']) + ->replaceArgument(3, $config['search_password']) + ->replaceArgument(4, $config['default_roles']) + ->replaceArgument(5, $config['uid_key']) + ->replaceArgument(6, $config['filter']) + ; + } + + public function getKey() + { + return 'ldap'; + } + + public function addConfiguration(NodeDefinition $node) + { + $node + ->children() + ->scalarNode('service')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('base_dn')->isRequired()->cannotBeEmpty()->end() + ->scalarNode('search_dn')->end() + ->scalarNode('search_password')->end() + ->arrayNode('default_roles') + ->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end() + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->scalarNode('uid_key')->defaultValue('sAMAccountName')->end() + ->scalarNode('filter')->defaultValue('({uid_key}={username})')->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 36c16e0dbc7d..6db34e4d2810 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -65,6 +65,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('templating_php.xml'); $loader->load('templating_twig.xml'); $loader->load('collectors.xml'); + $loader->load('guard.xml'); if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { $container->removeDefinition('security.expression_language'); @@ -410,7 +411,7 @@ private function createAuthenticationListeners($container, $id, $firewall, &$aut $listenerId = 'security.authentication.listener.anonymous.'.$id; $container ->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.anonymous')) - ->replaceArgument(1, $firewall['anonymous']['key']) + ->replaceArgument(1, $firewall['anonymous']['secret']) ; $listeners[] = new Reference($listenerId); @@ -418,7 +419,7 @@ private function createAuthenticationListeners($container, $id, $firewall, &$aut $providerId = 'security.authentication.provider.anonymous.'.$id; $container ->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.anonymous')) - ->replaceArgument(0, $firewall['anonymous']['key']) + ->replaceArgument(0, $firewall['anonymous']['secret']) ; $authenticationProviders[] = $providerId; diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml index 8f6a608c6de8..bfc236b8cf8e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml @@ -13,6 +13,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml new file mode 100644 index 000000000000..0524cf2b95b4 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index b7c1407c1cc5..b1a2cdfc80cb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -158,10 +158,21 @@ + + + + + + + + + + + @@ -169,6 +180,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 917e90f79281..948286bb5a85 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -223,6 +223,15 @@ %security.authentication.hide_user_not_found% + + + + + + + %security.authentication.hide_user_not_found% + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg new file mode 100644 index 000000000000..02033fdc7f5e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig index 923be8381064..dd724682d749 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Resources/views/Collector/security.html.twig @@ -1,92 +1,122 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% block page_title 'Security' %} + {% block toolbar %} {% if collector.tokenClass %} - {% set color_code = (collector.enabled and collector.authenticated) ? 'green' : 'yellow' %} - {% set authentication_color_code = (collector.enabled and collector.authenticated) ? 'green' : 'red' %} - {% set authentication_color_text = (collector.enabled and collector.authenticated) ? 'Yes' : 'No' %} + {% set is_authenticated = collector.enabled and collector.authenticated %} + {% set color_code = is_authenticated ? '' : 'yellow' %} {% else %} - {% set color_code = collector.enabled ? 'red' : 'black' %} + {% set color_code = collector.enabled ? 'red' : '' %} {% endif %} + + {% set icon %} + {{ include('@Security/Collector/icon.svg') }} + {{ collector.user|default('n/a') }} + {% endset %} + {% set text %} {% if collector.tokenClass %}
Logged in as - {{ collector.user }} + {{ collector.user }}
+
Authenticated - {{ authentication_color_text }} + {{ is_authenticated ? 'Yes' : 'No' }}
+ {% if collector.tokenClass != null %}
Token class - {{ collector.tokenClass|abbr_class }} + {{ collector.tokenClass|abbr_class }} +
+ {% endif %} + {% if collector.logoutUrl %} +
+ Actions + Logout
{% endif %} {% elseif collector.enabled %} - You are not authenticated. +
+ You are not authenticated. +
{% else %} - The security is disabled. +
+ The security is disabled. +
{% endif %} {% endset %} - {% set icon %} - - - {% if collector.user %}
{{ collector.user }}
{% endif %} - {% endset %} - {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: color_code }) }} {% endblock %} {% block menu %} - - - Security - + + {{ include('@Security/Collector/icon.svg') }} + Security + {% endblock %} {% block panel %} -

Security

+

Security Token

+ {% if collector.tokenClass %} +
+
+ {{ collector.user == 'anon.' ? 'Anonymous' : collector.user }} + Username +
+ +
+ {{ include('@WebProfiler/Icon/' ~ (collector.authenticated ? 'yes' : 'no') ~ '.svg') }} + Authenticated +
+
+ - - - - - - - - - - - - - {% if collector.supportsRoleHierarchy %} - - - - - {% endif %} - {% if collector.tokenClass != null %} - - - - - {% endif %} + + + + + + + + + + + + + {% if collector.supportsRoleHierarchy %} + + + + + {% endif %} + + {% if collector.tokenClass %} + + + + + {% endif %} +
Username{{ collector.user }}
Authenticated? - {% if collector.authenticated %} - yes - {% else %} - no {% if not collector.roles|length %}(probably because the user has no roles){% endif %} - {% endif %} -
Roles{{ collector.roles|yaml_encode }}
Inherited Roles{{ collector.inheritedRoles|yaml_encode }}
Token class{{ collector.tokenClass }}
PropertyValue
Roles + {{ collector.roles is empty ? 'none' : collector.roles|yaml_encode }} + + {% if not collector.authenticated and collector.roles is empty %} +

User is not authenticated probably because they have no roles.

+ {% endif %} +
Inherited Roles{{ collector.inheritedRoles is empty ? 'none' : collector.inheritedRoles|yaml_encode }}
Token class{{ collector.tokenClass }}
{% elseif collector.enabled %} -

- No token -

+
+

There is no security token.

+
{% else %} -

- The security component is disabled -

+
+

The security component is disabled.

+
{% endif %} {% endblock %} diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 72f7b68de959..f2dfc991fbce 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -15,7 +15,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory; @@ -23,6 +25,8 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimplePreAuthenticationFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\LdapFactory; /** * Bundle. @@ -37,15 +41,19 @@ public function build(ContainerBuilder $container) $extension = $container->getExtension('security'); $extension->addSecurityListenerFactory(new FormLoginFactory()); + $extension->addSecurityListenerFactory(new FormLoginLdapFactory()); $extension->addSecurityListenerFactory(new HttpBasicFactory()); + $extension->addSecurityListenerFactory(new HttpBasicLdapFactory()); $extension->addSecurityListenerFactory(new HttpDigestFactory()); $extension->addSecurityListenerFactory(new RememberMeFactory()); $extension->addSecurityListenerFactory(new X509Factory()); $extension->addSecurityListenerFactory(new RemoteUserFactory()); $extension->addSecurityListenerFactory(new SimplePreAuthenticationFactory()); $extension->addSecurityListenerFactory(new SimpleFormFactory()); + $extension->addSecurityListenerFactory(new GuardAuthenticationFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); + $extension->addUserProviderFactory(new LdapFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index b16a46ff0345..4521c8cdcd22 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -71,7 +71,7 @@ 'x509' => true, 'remote_user' => true, 'logout' => true, - 'remember_me' => array('key' => 'TheKey'), + 'remember_me' => array('secret' => 'TheSecret'), ), 'host' => array( 'pattern' => '/test', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php index 93a30444139e..e0ca4f6dedf3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php @@ -9,7 +9,7 @@ 'main' => array( 'form_login' => true, 'remember_me' => array( - 'key' => 'TheyKey', + 'secret' => 'TheSecret', 'catch_exceptions' => false, 'token_provider' => 'token_provider_id', ), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index 1a56aa88fda0..e5f5905fa7e0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -56,7 +56,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml index 167475689157..b6ade91a0797 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml @@ -11,7 +11,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index 93c231ea235f..6b27806e564b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -55,7 +55,7 @@ security: remote_user: true logout: true remember_me: - key: TheKey + secret: TheSecret host: pattern: /test host: foo\.example\.org diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml index 3a38b33c521c..a521c8c6a803 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml @@ -7,6 +7,6 @@ security: main: form_login: true remember_me: - key: TheKey + secret: TheSecret catch_exceptions: false token_provider: token_provider_id diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php new file mode 100644 index 000000000000..cfbc37859b97 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory; + +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class GuardAuthenticationFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getValidConfigurationTests + */ + public function testAddValidConfiguration(array $inputConfig, array $expectedConfig) + { + $factory = new GuardAuthenticationFactory(); + $nodeDefinition = new ArrayNodeDefinition('guard'); + $factory->addConfiguration($nodeDefinition); + + $node = $nodeDefinition->getNode(); + $normalizedConfig = $node->normalize($inputConfig); + $finalizedConfig = $node->finalize($normalizedConfig); + + $this->assertEquals($expectedConfig, $finalizedConfig); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @dataProvider getInvalidConfigurationTests + */ + public function testAddInvalidConfiguration(array $inputConfig) + { + $factory = new GuardAuthenticationFactory(); + $nodeDefinition = new ArrayNodeDefinition('guard'); + $factory->addConfiguration($nodeDefinition); + + $node = $nodeDefinition->getNode(); + $normalizedConfig = $node->normalize($inputConfig); + // will validate and throw an exception on invalid + $node->finalize($normalizedConfig); + } + + public function getValidConfigurationTests() + { + $tests = array(); + + // completely basic + $tests[] = array( + array( + 'authenticators' => array('authenticator1', 'authenticator2'), + 'provider' => 'some_provider', + 'entry_point' => 'the_entry_point', + ), + array( + 'authenticators' => array('authenticator1', 'authenticator2'), + 'provider' => 'some_provider', + 'entry_point' => 'the_entry_point', + ), + ); + + // testing xml config fix: authenticator -> authenticators + $tests[] = array( + array( + 'authenticator' => array('authenticator1', 'authenticator2'), + ), + array( + 'authenticators' => array('authenticator1', 'authenticator2'), + 'entry_point' => null, + ), + ); + + return $tests; + } + + public function getInvalidConfigurationTests() + { + $tests = array(); + + // testing not empty + $tests[] = array( + array('authenticators' => array()), + ); + + return $tests; + } + + public function testBasicCreate() + { + // simple configuration + $config = array( + 'authenticators' => array('authenticator123'), + 'entry_point' => null, + ); + list($container, $entryPointId) = $this->executeCreate($config, null); + $this->assertEquals('authenticator123', $entryPointId); + + $providerDefinition = $container->getDefinition('security.authentication.provider.guard.my_firewall'); + $this->assertEquals(array( + 'index_0' => array(new Reference('authenticator123')), + 'index_1' => new Reference('my_user_provider'), + 'index_2' => 'my_firewall', + ), $providerDefinition->getArguments()); + + $listenerDefinition = $container->getDefinition('security.authentication.listener.guard.my_firewall'); + $this->assertEquals('my_firewall', $listenerDefinition->getArgument(2)); + $this->assertEquals(array(new Reference('authenticator123')), $listenerDefinition->getArgument(3)); + } + + public function testExistingDefaultEntryPointUsed() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123'), + 'entry_point' => null, + ); + list($container, $entryPointId) = $this->executeCreate($config, 'some_default_entry_point'); + $this->assertEquals('some_default_entry_point', $entryPointId); + } + + /** + * @expectedException \LogicException + */ + public function testCannotOverrideDefaultEntryPoint() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123'), + 'entry_point' => 'authenticator123', + ); + $this->executeCreate($config, 'some_default_entry_point'); + } + + /** + * @expectedException \LogicException + */ + public function testMultipleAuthenticatorsRequiresEntryPoint() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123', 'authenticatorABC'), + 'entry_point' => null, + ); + $this->executeCreate($config, null); + } + + public function testCreateWithEntryPoint() + { + // any existing default entry point is used + $config = array( + 'authenticators' => array('authenticator123', 'authenticatorABC'), + 'entry_point' => 'authenticatorABC', + ); + list($container, $entryPointId) = $this->executeCreate($config, null); + $this->assertEquals('authenticatorABC', $entryPointId); + } + + private function executeCreate(array $config, $defaultEntryPointId) + { + $container = new ContainerBuilder(); + $container->register('security.authentication.provider.guard'); + $container->register('security.authentication.listener.guard'); + $id = 'my_firewall'; + $userProviderId = 'my_user_provider'; + + $factory = new GuardAuthenticationFactory(); + list($providerId, $listenerId, $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId); + + return array($container, $entryPointId); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php index 1eccbfd795be..dd7c19e9c7f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Controller/LoginController.php @@ -19,7 +19,7 @@ class LoginController extends ContainerAware { public function loginAction() { - $form = $this->container->get('form.factory')->create('user_login'); + $form = $this->container->get('form.factory')->create('Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType'); return $this->container->get('templating')->renderResponse('CsrfFormLoginBundle:Login:login.html.twig', array( 'form' => $form->createView(), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginFormType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php similarity index 79% rename from src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginFormType.php rename to src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index d76d8fd629bb..48b87fbecbfc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginFormType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -16,7 +16,7 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; -use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Security; @@ -27,16 +27,16 @@ * @author Henrik Bjornskov * @author Jeremy Mikola */ -class UserLoginFormType extends AbstractType +class UserLoginType extends AbstractType { - private $request; + private $requestStack; /** - * @param Request $request A request instance + * @param RequestStack $requestStack A RequestStack instance */ - public function __construct(Request $request) + public function __construct(RequestStack $requestStack) { - $this->request = $request; + $this->requestStack = $requestStack; } /** @@ -45,12 +45,12 @@ public function __construct(Request $request) public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('username', 'text') - ->add('password', 'password') - ->add('_target_path', 'hidden') + ->add('username', 'Symfony\Component\Form\Extension\Core\Type\TextType') + ->add('password', 'Symfony\Component\Form\Extension\Core\Type\PasswordType') + ->add('_target_path', 'Symfony\Component\Form\Extension\Core\Type\HiddenType') ; - $request = $this->request; + $request = $this->requestStack->getCurrentRequest(); /* Note: since the Security component's form login listener intercepts * the POST request, this form will never really be bound to the @@ -87,12 +87,4 @@ public function configureOptions(OptionsResolver $resolver) 'intention' => 'authenticate', )); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'user_login'; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index e1e2b0e88393..d7ad6049aa61 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -3,12 +3,11 @@ imports: services: csrf_form_login.form.type: - class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginFormType - scope: request + class: Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\CsrfFormLoginBundle\Form\UserLoginType arguments: - - @request + - @request_stack tags: - - { name: form.type, alias: user_login } + - { name: form.type } security: encoders: diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 881031d0209f..5239f039fee7 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -17,26 +17,26 @@ ], "require": { "php": ">=5.3.9", - "symfony/security": "~2.7", - "symfony/security-acl": "~2.7", - "symfony/http-kernel": "~2.2" + "symfony/security": "~2.8|~3.0.0", + "symfony/security-acl": "~2.7|~3.0.0", + "symfony/http-kernel": "~2.2|~3.0.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7", - "symfony/browser-kit": "~2.4", - "symfony/console": "~2.7", - "symfony/css-selector": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.6,>=2.6.6", - "symfony/dom-crawler": "~2.0,>=2.0.5", - "symfony/form": "~2.7", - "symfony/framework-bundle": "~2.7", - "symfony/http-foundation": "~2.3", - "symfony/twig-bundle": "~2.7", - "symfony/twig-bridge": "~2.7", - "symfony/process": "~2.0,>=2.0.5", - "symfony/validator": "~2.5", - "symfony/yaml": "~2.0,>=2.0.5", - "symfony/expression-language": "~2.6", + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/browser-kit": "~2.4|~3.0.0", + "symfony/config": "~2.8|~3.0.0", + "symfony/console": "~2.7|~3.0.0", + "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0", + "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0", + "symfony/form": "~2.8", + "symfony/framework-bundle": "~2.8", + "symfony/http-foundation": "~2.4|~3.0.0", + "symfony/twig-bundle": "~2.7|~3.0.0", + "symfony/twig-bridge": "~2.7|~3.0.0", + "symfony/process": "~2.0,>=2.0.5|~3.0.0", + "symfony/validator": "~2.5|~3.0.0", + "symfony/yaml": "~2.0,>=2.0.5|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", "doctrine/doctrine-bundle": "~1.2", "twig/twig": "~1.20|~2.0", "ircmaxell/password-compat": "~1.0" @@ -47,7 +47,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } diff --git a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php index 65827eba5a6b..5dde9406914a 100644 --- a/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php +++ b/src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheCacheWarmer.php @@ -11,9 +11,11 @@ namespace Symfony\Bundle\TwigBundle\CacheWarmer; +use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinderInterface; +use Symfony\Component\Templating\TemplateReference; /** * Generates the Twig cache for all templates. @@ -27,14 +29,16 @@ class TemplateCacheCacheWarmer implements CacheWarmerInterface { protected $container; protected $finder; + private $paths; /** * Constructor. * * @param ContainerInterface $container The dependency injection container * @param TemplateFinderInterface $finder The template paths cache warmer + * @param array $paths Additional twig paths to warm */ - public function __construct(ContainerInterface $container, TemplateFinderInterface $finder) + public function __construct(ContainerInterface $container, TemplateFinderInterface $finder, array $paths = array()) { // We don't inject the Twig environment directly as it depends on the // template locator (via the loader) which might be a cached one. @@ -42,6 +46,7 @@ public function __construct(ContainerInterface $container, TemplateFinderInterfa // has been warmed up $this->container = $container; $this->finder = $finder; + $this->paths = $paths; } /** @@ -53,7 +58,13 @@ public function warmUp($cacheDir) { $twig = $this->container->get('twig'); - foreach ($this->finder->findAllTemplates() as $template) { + $templates = $this->finder->findAllTemplates(); + + foreach ($this->paths as $path => $namespace) { + $templates = array_merge($templates, $this->findTemplatesInFolder($namespace, $path)); + } + + foreach ($templates as $template) { if ('twig' !== $template->get('engine')) { continue; } @@ -75,4 +86,32 @@ public function isOptional() { return true; } + + /** + * Find templates in the given directory. + * + * @param string $namespace The namespace for these templates + * @param string $dir The folder where to look for templates + * + * @return array An array of templates of type TemplateReferenceInterface + */ + private function findTemplatesInFolder($namespace, $dir) + { + if (!is_dir($dir)) { + return array(); + } + + $templates = array(); + $finder = new Finder(); + + foreach ($finder->files()->followLinks()->in($dir) as $file) { + $name = $file->getRelativePathname(); + $templates[] = new TemplateReference( + $namespace ? sprintf('@%s/%s', $namespace, $name) : $name, + 'twig' + ); + } + + return $templates; + } } diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index b0c172c1e9e1..237fda48ec93 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -11,12 +11,10 @@ namespace Symfony\Bundle\TwigBundle\Controller; -use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference; use Symfony\Component\HttpKernel\Exception\FlattenException; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\TemplateReferenceInterface; /** * ExceptionController renders error or exception pages for a given @@ -96,7 +94,7 @@ protected function getAndCleanOutputBuffering($startObLevel) * @param int $code An HTTP response status code * @param bool $showException * - * @return TemplateReferenceInterface + * @return string */ protected function findTemplate(Request $request, $format, $code, $showException) { @@ -107,14 +105,14 @@ protected function findTemplate(Request $request, $format, $code, $showException // For error pages, try to find a template for the specific HTTP status code and format if (!$showException) { - $template = new TemplateReference('TwigBundle', 'Exception', $name.$code, $format, 'twig'); + $template = sprintf('@Twig/Exception/%s%s.%s.twig', $name, $code, $format); if ($this->templateExists($template)) { return $template; } } // try to find a template for the given format - $template = new TemplateReference('TwigBundle', 'Exception', $name, $format, 'twig'); + $template = sprintf('@Twig/Exception/%s.%s.twig', $name, $format); if ($this->templateExists($template)) { return $template; } @@ -122,7 +120,7 @@ protected function findTemplate(Request $request, $format, $code, $showException // default to a generic HTML exception $request->setRequestFormat('html'); - return new TemplateReference('TwigBundle', 'Exception', $showException ? 'exception_full' : $name, 'html', 'twig'); + return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name); } // to be removed when the minimum required version of Twig is >= 3.0 diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 7b97e120baa4..c0171219d048 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -76,21 +77,29 @@ public function load(array $configs, ContainerBuilder $container) } } + $container->getDefinition('twig.cache_warmer')->replaceArgument(2, $config['paths']); + // register bundles as Twig namespaces foreach ($container->getParameter('kernel.bundles') as $bundle => $class) { - if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$bundle.'/views')) { + $dir = $container->getParameter('kernel.root_dir').'/Resources/'.$bundle.'/views'; + if (is_dir($dir)) { $this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $bundle); } + $container->addResource(new FileExistenceResource($dir)); $reflection = new \ReflectionClass($class); - if (is_dir($dir = dirname($reflection->getFileName()).'/Resources/views')) { + $dir = dirname($reflection->getFileName()).'/Resources/views'; + if (is_dir($dir)) { $this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $bundle); } + $container->addResource(new FileExistenceResource($dir)); } - if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/views')) { + $dir = $container->getParameter('kernel.root_dir').'/Resources/views'; + if (is_dir($dir)) { $twigFilesystemLoaderDefinition->addMethodCall('addPath', array($dir)); } + $container->addResource(new FileExistenceResource($dir)); if (!empty($config['globals'])) { $def = $container->getDefinition('twig'); diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 9e1a11777418..bb871f440a20 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -48,6 +48,7 @@ + @@ -78,7 +79,7 @@ - + diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig index 074389ce919e..c27cc56e6a07 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.atom.twig @@ -1 +1 @@ -{% include 'TwigBundle:Exception:error.xml.twig' %} +{% include '@Twig/Exception/error.xml.twig' %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig index 074389ce919e..c27cc56e6a07 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.rdf.twig @@ -1 +1 @@ -{% include 'TwigBundle:Exception:error.xml.twig' %} +{% include '@Twig/Exception/error.xml.twig' %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig index 989740fdd01c..d507ce46f694 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.atom.twig @@ -1 +1 @@ -{% include 'TwigBundle:Exception:exception.xml.twig' with { 'exception': exception } %} +{% include '@Twig/Exception/exception.xml.twig' with { 'exception': exception } %} diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig index 870d4a0fd240..bdf242b7f199 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.css.twig @@ -1,3 +1,3 @@ /* -{% include 'TwigBundle:Exception:exception.txt.twig' with { 'exception': exception } %} +{% include '@Twig/Exception/exception.txt.twig' with { 'exception': exception } %} */ diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig index f09ffb3c658d..947df655836b 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.html.twig @@ -37,7 +37,7 @@ {% for position, e in exception.toarray %} - {% include 'TwigBundle:Exception:traces.html.twig' with { 'exception': e, 'position': position, 'count': previous_count } only %} + {% include '@Twig/Exception/traces.html.twig' with { 'exception': e, 'position': position, 'count': previous_count } only %} {% endfor %} {% if logger %} @@ -63,7 +63,7 @@
- {% include 'TwigBundle:Exception:logs.html.twig' with { 'logs': logger.logs } only %} + {% include '@Twig/Exception/logs.html.twig' with { 'logs': logger.logs } only %}
{% endif %} @@ -88,7 +88,7 @@ {% endif %} -{% include 'TwigBundle:Exception:traces_text.html.twig' with { 'exception': exception } only %} +{% include '@Twig/Exception/traces_text.html.twig' with { 'exception': exception } only %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/body.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/body.css.twig deleted file mode 100644 index fa4d07691743..000000000000 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/body.css.twig +++ /dev/null @@ -1,137 +0,0 @@ -/* -Copyright (c) 2010, Yahoo! Inc. All rights reserved. -Code licensed under the BSD License: -http://developer.yahoo.com/yui/license.html -version: 3.1.2 -build: 56 -*/ -.sf-reset div,.sf-reset dl,.sf-reset dt,.sf-reset dd,.sf-reset ul,.sf-reset ol,.sf-reset li,.sf-reset h1,.sf-reset h2,.sf-reset h3,.sf-reset h4,.sf-reset h5,.sf-reset h6,.sf-reset pre,.sf-reset code,.sf-reset form,.sf-reset fieldset,.sf-reset legend,.sf-reset input,.sf-reset textarea,.sf-reset p,.sf-reset blockquote,.sf-reset th,.sf-reset td{margin:0;padding:0;}.sf-reset table{border-collapse:collapse;border-spacing:0;}.sf-reset fieldset,.sf-reset img{border:0;}.sf-reset address,.sf-reset caption,.sf-reset cite,.sf-reset code,.sf-reset dfn,.sf-reset em,.sf-reset strong,.sf-reset th,.sf-reset var{font-style:normal;font-weight:normal;}.sf-reset li{list-style:none;}.sf-reset caption,.sf-reset th{text-align:left;}.sf-reset h1,.sf-reset h2,.sf-reset h3,.sf-reset h4,.sf-reset h5,.sf-reset h6{font-size:100%;font-weight:normal;}.sf-reset q:before,.sf-reset q:after{content:'';}.sf-reset abbr,.sf-reset acronym{border:0;font-variant:normal;}.sf-reset sup{vertical-align:text-top;}.sf-reset sub{vertical-align:text-bottom;}.sf-reset input,.sf-reset textarea,.sf-reset select{font-family:inherit;font-size:inherit;font-weight:inherit;}.sf-reset input,.sf-reset textarea,.sf-reset select{font-size:100%;}.sf-reset legend{color:#000;} -.sf-reset abbr { - border-bottom: 1px dotted #000; - cursor: help; -} -.sf-reset p { - font-size: 14px; - line-height: 20px; - padding-bottom: 20px; -} -.sf-reset strong { - color: #313131; - font-weight: bold; -} -.sf-reset a { - color: #6c6159; -} -.sf-reset a img { - border: none; -} -.sf-reset a:hover { - text-decoration: underline; -} -.sf-reset em { - font-style: italic; -} -.sf-reset h2, -.sf-reset h3 { - font-weight: bold; -} -.sf-reset h1 { - font-family: Georgia, "Times New Roman", Times, serif; - font-size: 20px; - color: #313131; - word-break: break-all; -} -.sf-reset li { - padding-bottom: 10px; -} -.sf-reset .block { - -moz-border-radius: 16px; - -webkit-border-radius: 16px; - border-radius: 16px; - margin-bottom: 20px; - background-color: #FFFFFF; - border: 1px solid #dfdfdf; - padding: 40px 50px; -} -.sf-reset h2 { - font-size: 16px; - font-family: Arial, Helvetica, sans-serif; -} -.sf-reset li a { - background: none; - color: #868686; - text-decoration: none; -} -.sf-reset li a:hover { - background: none; - color: #313131; - text-decoration: underline; -} -.sf-reset ol { - padding: 10px 0; -} -.sf-reset ol li { - list-style: decimal; - margin-left: 20px; - padding: 2px; - padding-bottom: 20px; -} -.sf-reset ol ol li { - list-style-position: inside; - margin-left: 0; - white-space: nowrap; - font-size: 12px; - padding-bottom: 0; -} -.sf-reset li .selected { - background-color: #ffd; -} -.sf-button { - display: -moz-inline-box; - display: inline-block; - text-align: center; - vertical-align: middle; - border: 0; - background: transparent none; - text-transform: uppercase; - cursor: pointer; - font: bold 11px Arial, Helvetica, sans-serif; -} -.sf-button span { - text-decoration: none; - display: block; - height: 28px; - float: left; -} -.sf-button .border-l { - text-decoration: none; - display: block; - height: 28px; - float: left; - padding: 0 0 0 7px; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQtJREFUeNpiPHnyJAMakARiByDWYEGT8ADiYGVlZStubm5xlv///4MEQYoKZGRkQkRERLRYWVl5wYJQyXBZWdkwCQkJUxAHKgaWlAHSLqKiosb//v1DsYMFKGCvoqJiDmQzwXTAJYECulxcXNLoumCSoszMzDzoumDGghQwYZUECWIzkrAkSIIGOmlkLI10AiX//P379x8jIyMTNmPf/v79+ysLCwsvuiQoNi5//fr1Kch4dAyS3P/gwYMTQBP+wxwHw0xA4gkQ73v9+vUZdJ2w1Lf82bNn4iCHCQoKasHsZw4ODgbRIL8c+/Lly5M3b978Y2dn5wC6npkFLXnsAOKLjx49AmUHLYAAAwBoQubG016R5wAAAABJRU5ErkJggg==) no-repeat top left; -} -.sf-button .border-r { - padding: 0 7px 0 0; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAR1JREFUeNpiPHnyZCMDA8MNID5gZmb2nAEJMH7//v3N169fX969e/cYkL8WqGAHXPLv37//QYzfv39/fvPmzbUnT56sAXInmJub/2H5/x8sx8DCwsIrISFhDmQyPX78+CmQXs70798/BmQsKipqBNTgdvz4cWkmkE5kDATMioqKZkCFdiwg1eiAi4tLGqhQF24nMmBmZuYEigth1QkEbEBxTlySYPvJkwSJ00AnjYylgU6gxB8g/oFVEphkvgLF32KNMmCCewYUv4qhEyj47+HDhyeBzIMYOoEp8CxQw56wsLAncJ1//vz5/P79+2svX74EJc2V4BT58+fPd8CE/QKYHMGJOiIiAp6oWW7evDkNSF8DZYfIyEiU7AAQYACJ2vxVdJW4eQAAAABJRU5ErkJggg==) right top no-repeat; -} -.sf-button .btn-bg { - padding: 0px 14px; - color: #636363; - line-height: 28px; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAYAAACgXdXMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpiPnny5EKGf//+/Wf6//8/A4QAcrGzKCZwGc9sa2urBBBgAIbDUoYVp9lmAAAAAElFTkSuQmCC) repeat-x top left; -} -.sf-button:hover .border-l, -.sf-button-selected .border-l { - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAR9JREFUeNpi/P//PwMyOHfunDqQSgNiexZkibNnzxYBqZa3HOs5v7PcYQBLnjlzhg1IbfzIdsTjA/t+ht9Mr8GKwZL//v3r+sB+0OMN+zqIEf8gFMvJkyd1gXTOa9YNDP//otrPAtSV/Jp9HfPff78Z0AEL0LUeXxivMfxD0wXTqfjj/2ugkf+wSrL9/YtpJEyS4S8WI5Ek/+GR/POPFjr//cenE6/kP9q4Fo/kr39/mdj+M/zFkGQCSj5i+ccPjLJ/GBgkuYOHQR1sNDpmAkb2LBmWwL///zKCIxwZM0VHR18G6p4uxeLLAA4tJMwEshiou1iMxXaHLGswA+t/YbhORuQUv2DBAnCifvxzI+enP3dQJUFg/vz5sOzgBBBgAPxX9j0YnH4JAAAAAElFTkSuQmCC) no-repeat top left; -} -.sf-button:hover .border-r, -.sf-button-selected .border-r { - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAcCAYAAACtQ6WLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAT5JREFUeNpiPHv27BkGBoaDQDzLyMjoJgMSYHrM3WX8hn1d0f///88DFRYhSzIuv2X5H8Rg/SfKIPDTkYH/l80OINffxMTkF9O/f/8ZQPgnwyuGl+wrGd6x7vf49+9fO9jYf3+Bkkj4NesmBqAV+SdPntQC6vzHgIz//gOawbqOGchOxtAJwp8Zr4F0e7D8/fuPAR38/P8eZIo0yz8skv8YvoIk+YE6/zNgAyD7sRqLkPzzjxY6/+HS+R+fTkZ8djLh08lCUCcuSWawJGbwMTGwg7zyBatX2Bj5QZKPsBrLzaICktzN8g/NWEYGZgYZjoC/wMiei5FMpFh8QPSU6Ojoy3Cd7EwiDBJsDgxiLNY7gLrKQGIsHAxSDHxAO2TZ/b8D+TVxcXF9MCtYtLiKLgDpfUDVsxITE1GyA0CAAQA2E/N8VuHyAAAAAABJRU5ErkJggg==) right top no-repeat; -} -.sf-button:hover .btn-bg, -.sf-button-selected .btn-bg { - color: #FFFFFF; - text-shadow:0 1px 1px #6b9311; - background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAIAAAAvP0KbAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiPnv2LNMdvlymf///M/37B8R/QfQ/MP33L4j+B6Qh7L9//sHpf2h8MA1V+w/KRjYLaDaLCU8vQIABAFO3TxZriO4yAAAAAElFTkSuQmCC) repeat-x top left; -} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig index acc76300b59d..d04cf37e6c7b 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/header.html.twig @@ -1,25 +1,14 @@ -