diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index cc725bab2c..c471e64dd9 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1341,8 +1341,21 @@ UPGRADE FROM 2.x to 3.0 * Using a colon in an unquoted mapping value leads to a `ParseException`. * Starting an unquoted string with `@`, `` ` ``, `|`, or `>` leads to a `ParseException`. - * Deprecated non-escaped \ in double-quoted strings when parsing Yaml - ("Foo\Var" is not valid whereas "Foo\\Var" is) + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) leads to a `ParseException`. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + * The ability to pass file names to `Yaml::parse()` has been removed. diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig index 767e2798f3..e997615d11 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -25,15 +25,13 @@ col-sm-2 {# Rows #} {% block form_row -%} -{% spaceless %}
- {{ form_label(form) }} + {{- form_label(form) -}}
- {{ form_widget(form) }} - {{ form_errors(form) }} + {{- form_widget(form) -}} + {{- form_errors(form) -}}
-
-{% endspaceless %} +{##} {%- endblock form_row %} {% block checkbox_row -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php new file mode 100644 index 0000000000..bf26dda05b --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionBootstrap3HorizontalLayoutTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubTranslator; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\StubFilesystemLoader; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Tests\AbstractBootstrap3HorizontalLayoutTest; + +class FormExtensionBootstrap3HorizontalLayoutTest extends AbstractBootstrap3HorizontalLayoutTest +{ + /** + * @var FormExtension + */ + protected $extension; + + protected $testableFeatures = array( + 'choice_attr', + ); + + protected function setUp() + { + parent::setUp(); + + $rendererEngine = new TwigRendererEngine(array( + 'bootstrap_3_horizontal_layout.html.twig', + 'custom_widgets.html.twig', + )); + $renderer = new TwigRenderer($rendererEngine, $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')); + + $this->extension = new FormExtension($renderer); + + $loader = new StubFilesystemLoader(array( + __DIR__.'/../../Resources/views/Form', + __DIR__.'/Fixtures/templates/form', + )); + + $environment = new \Twig_Environment($loader, array('strict_variables' => true)); + $environment->addExtension(new TranslationExtension(new StubTranslator())); + $environment->addExtension($this->extension); + + $this->extension->initRuntime($environment); + } + + protected function tearDown() + { + parent::tearDown(); + + $this->extension = null; + } + + protected function renderForm(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form', $vars); + } + + protected function renderEnctype(FormView $view) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); + } + + protected function renderLabel(FormView $view, $label = null, array $vars = array()) + { + if ($label !== null) { + $vars += array('label' => $label); + } + + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'label', $vars); + } + + protected function renderErrors(FormView $view) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'errors'); + } + + protected function renderWidget(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'widget', $vars); + } + + protected function renderRow(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'row', $vars); + } + + protected function renderRest(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); + } + + protected function renderStart(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars); + } + + protected function renderEnd(FormView $view, array $vars = array()) + { + return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars); + } + + protected function setTheme(FormView $view, array $themes) + { + $this->extension->renderer->setTheme($view, $themes); + } +} diff --git a/src/Symfony/Component/Asset/Package.php b/src/Symfony/Component/Asset/Package.php index 43bdcf21db..77b1c934eb 100644 --- a/src/Symfony/Component/Asset/Package.php +++ b/src/Symfony/Component/Asset/Package.php @@ -60,6 +60,9 @@ class Package implements PackageInterface return $this->context; } + /** + * @return VersionStrategyInterface + */ protected function getVersionStrategy() { return $this->versionStrategy; diff --git a/src/Symfony/Component/Asset/UrlPackage.php b/src/Symfony/Component/Asset/UrlPackage.php index 6381a9c1cd..3626ec843c 100644 --- a/src/Symfony/Component/Asset/UrlPackage.php +++ b/src/Symfony/Component/Asset/UrlPackage.php @@ -39,10 +39,11 @@ class UrlPackage extends Package private $sslPackage; /** - * @param string|array $baseUrls Base asset URLs + * @param string|string[] $baseUrls Base asset URLs * @param VersionStrategyInterface $versionStrategy The version strategy + * @param ContextInterface|null $context Context */ - public function __construct($baseUrls = array(), VersionStrategyInterface $versionStrategy, ContextInterface $context = null) + public function __construct($baseUrls, VersionStrategyInterface $versionStrategy, ContextInterface $context = null) { parent::__construct($versionStrategy, $context); diff --git a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php index 6028eb57fe..857cf9432b 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/StaticVersionStrategy.php @@ -21,6 +21,10 @@ class StaticVersionStrategy implements VersionStrategyInterface private $version; private $format; + /** + * @param string $version Version number + * @param string $format Url format + */ public function __construct($version, $format = null) { $this->version = $version; diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index 4fb08772d7..d1375df889 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -217,7 +217,7 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf return $this->data; } - private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null, array &$outputByHash) + private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output, array &$outputByHash) { $hash = spl_object_hash($form); @@ -236,7 +236,7 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf } } - private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null, array &$outputByHash) + private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output, array &$outputByHash) { $viewHash = spl_object_hash($view); $formHash = null; diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3HorizontalLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3HorizontalLayoutTest.php new file mode 100644 index 0000000000..1273fa505b --- /dev/null +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3HorizontalLayoutTest.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests; + +abstract class AbstractBootstrap3HorizontalLayoutTest extends AbstractBootstrap3LayoutTest +{ + public function testLabelOnForm() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateType'); + $view = $form->createView(); + $this->renderWidget($view, array('label' => 'foo')); + $html = $this->renderLabel($view); + + $this->assertMatchesXpath($html, +'/label + [@class="col-sm-2 control-label required"] + [.="[trans]Name[/trans]"] +' + ); + } + + public function testLabelDoesNotRenderFieldAttributes() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), null, array( + 'attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="col-sm-2 control-label required"] +' + ); + } + + public function testLabelWithCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), null, array( + 'label_attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="my&class col-sm-2 control-label required"] +' + ); + } + + public function testLabelWithCustomTextAndCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); + $html = $this->renderLabel($form->createView(), 'Custom label', array( + 'label_attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="my&class col-sm-2 control-label required"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testLabelWithCustomTextAsOptionAndCustomAttributesPassedDirectly() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, array( + 'label' => 'Custom label', + )); + $html = $this->renderLabel($form->createView(), null, array( + 'label_attr' => array( + 'class' => 'my&class', + ), + )); + + $this->assertMatchesXpath($html, +'/label + [@for="name"] + [@class="my&class col-sm-2 control-label required"] + [.="[trans]Custom label[/trans]"] +' + ); + } + + public function testStartTag() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('
', $html); + } + + public function testStartTagWithOverriddenVars() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'put', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView(), array( + 'method' => 'post', + 'action' => 'http://foo.com/directory', + )); + + $this->assertSame('', $html); + } + + public function testStartTagForMultipartForm() + { + $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') + ->getForm(); + + $html = $this->renderStart($form->createView()); + + $this->assertSame('', $html); + } + + public function testStartTagWithExtraAttributes() + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\FormType', null, array( + 'method' => 'get', + 'action' => 'http://example.com/directory', + )); + + $html = $this->renderStart($form->createView(), array( + 'attr' => array('class' => 'foobar'), + )); + + $this->assertSame('', $html); + } +} diff --git a/src/Symfony/Component/HttpKernel/HttpCache/Store.php b/src/Symfony/Component/HttpKernel/HttpCache/Store.php index f9f3bdf01e..463caa01da 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/Store.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -110,7 +110,10 @@ class Store implements StoreInterface public function isLocked(Request $request) { - return is_file($this->getPath($this->getCacheKey($request).'.lck')); + $path = $this->getPath($this->getCacheKey($request).'.lck'); + clearstatcache(true, $path); + + return is_file($path); } /** diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php index d1fc9db8ec..0460898f9a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -224,7 +224,7 @@ class ControllerResolverTest extends \PHPUnit_Framework_TestCase { } - protected function controllerMethod3($foo, $bar = null, $foobar) + protected function controllerMethod3($foo, $bar, $foobar) { } diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index fe1676fbf3..c731dd7a97 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -26,6 +26,8 @@ use Symfony\Component\Serializer\NameConverter\NameConverterInterface; */ class ObjectNormalizer extends AbstractNormalizer { + private static $attributesCache = array(); + /** * @var PropertyAccessorInterface */ @@ -58,42 +60,7 @@ class ObjectNormalizer extends AbstractNormalizer } $data = array(); - $attributes = $this->getAllowedAttributes($object, $context, true); - - // If not using groups, detect manually - if (false === $attributes) { - $attributes = array(); - - // methods - $reflClass = new \ReflectionClass($object); - foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) { - if ( - !$reflMethod->isStatic() && - !$reflMethod->isConstructor() && - !$reflMethod->isDestructor() && - 0 === $reflMethod->getNumberOfRequiredParameters() - ) { - $name = $reflMethod->getName(); - - if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) { - // getters and hassers - $attributes[lcfirst(substr($name, 3))] = true; - } elseif (strpos($name, 'is') === 0) { - // issers - $attributes[lcfirst(substr($name, 2))] = true; - } - } - } - - // properties - foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) { - if (!$reflProperty->isStatic()) { - $attributes[$reflProperty->getName()] = true; - } - } - - $attributes = array_keys($attributes); - } + $attributes = $this->getAttributes($object, $context); foreach ($attributes as $attribute) { if (in_array($attribute, $this->ignoredAttributes)) { @@ -162,4 +129,64 @@ class ObjectNormalizer extends AbstractNormalizer return $object; } + + /** + * Gets and caches attributes for this class and context. + * + * @param object $object + * @param array $context + * + * @return array + */ + private function getAttributes($object, array $context) + { + $key = sprintf('%s-%s', get_class($object), serialize($context)); + + if (isset(self::$attributesCache[$key])) { + return self::$attributesCache[$key]; + } + + $allowedAttributes = $this->getAllowedAttributes($object, $context, true); + + if (false !== $allowedAttributes) { + return self::$attributesCache[$key] = $allowedAttributes; + } + + // If not using groups, detect manually + $attributes = array(); + + // methods + $reflClass = new \ReflectionClass($object); + foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) { + if ( + $reflMethod->getNumberOfRequiredParameters() !== 0 || + $reflMethod->isStatic() || + $reflMethod->isConstructor() || + $reflMethod->isDestructor() + ) { + continue; + } + + $name = $reflMethod->getName(); + + if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) { + // getters and hassers + $attributes[lcfirst(substr($name, 3))] = true; + } elseif (strpos($name, 'is') === 0) { + // issers + $attributes[lcfirst(substr($name, 2))] = true; + } + } + + // properties + foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) { + if ($reflProperty->isStatic()) { + continue; + } + + $attributes[$reflProperty->getName()] = true; + } + + return self::$attributesCache[$key] = array_keys($attributes); + } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index ce9830ea0d..2a16d69f90 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -29,7 +29,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy; class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase { /** - * @var ObjectNormalizerTest + * @var ObjectNormalizer */ private $normalizer; /** @@ -211,6 +211,18 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($obj, $normalized); } + public function testNormalizeNoPropertyInGroup() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory); + $this->normalizer->setSerializer($this->serializer); + + $obj = new GroupDummy(); + $obj->setFoo('foo'); + + $this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist')))); + } + public function testGroupsNormalizeWithNameConverter() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); diff --git a/src/Symfony/Component/Yaml/CHANGELOG.md b/src/Symfony/Component/Yaml/CHANGELOG.md index e910a7c3a1..fe77de73d4 100644 --- a/src/Symfony/Component/Yaml/CHANGELOG.md +++ b/src/Symfony/Component/Yaml/CHANGELOG.md @@ -12,8 +12,20 @@ CHANGELOG * Deprecated usage of a colon in an unquoted mapping value * Deprecated usage of @, \`, | and > at the beginning of an unquoted string - * Deprecated non-escaped \ in double-quoted strings when parsing Yaml - ("Foo\Var" is not valid whereas "Foo\\Var" is) + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` 2.1.0 -----