diff --git a/.travis.yml b/.travis.yml index 8e656ce8b1..6980630039 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ matrix: - php: 5.6 - php: 7.0 env: deps=high - - php: 7.1 + - php: 7.0 env: deps=low fast_finish: true diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 136e9db9e7..6fd14ce7d3 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -83,17 +83,17 @@ {% endif %} - + {% if forward_handler is defined %} -
-
- Forwarded to - - {{ forward_handler }} - ({{ collector.forward.token }}) - -
-
+
+
+ Forwarded to + + {{ forward_handler }} + ({{ collector.forward.token }}) + +
+
{% endif %} {% endset %} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index 8944235e8a..d06a799024 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -127,6 +127,7 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface $def->setFile($parentDef->getFile()); $def->setPublic($parentDef->isPublic()); $def->setLazy($parentDef->isLazy()); + $def->setAutowired($parentDef->isAutowired()); // overwrite with values specified in the decorator $changes = $definition->getChanges(); @@ -151,6 +152,9 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface if (isset($changes['deprecated'])) { $def->setDeprecated($definition->isDeprecated(), $definition->getDeprecationMessage('%service_id%')); } + if (isset($changes['autowire'])) { + $def->setAutowired($definition->isAutowired()); + } if (isset($changes['decorated_service'])) { $decoratedService = $definition->getDecoratedService(); if (null === $decoratedService) { diff --git a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php index 217ac851be..f17657a3a1 100644 --- a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php +++ b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php @@ -134,6 +134,16 @@ class DefinitionDecorator extends Definition return parent::setDeprecated($boolean, $template); } + /** + * {@inheritdoc} + */ + public function setAutowired($autowired) + { + $this->changes['autowire'] = true; + + return parent::setAutowired($autowired); + } + /** * Gets an argument to pass to the service constructor/factory method. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php index 0ce7d3bc72..d31dcfcd7f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php @@ -210,6 +210,36 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase $this->assertTrue($container->getDefinition('child1')->isLazy()); } + public function testSetAutowiredOnServiceHasParent() + { + $container = new ContainerBuilder(); + + $container->register('parent', 'stdClass'); + + $container->setDefinition('child1', new DefinitionDecorator('parent')) + ->setAutowired(true) + ; + + $this->process($container); + + $this->assertTrue($container->getDefinition('child1')->isAutowired()); + } + + public function testSetAutowiredOnServiceIsParent() + { + $container = new ContainerBuilder(); + + $container->register('parent', 'stdClass') + ->setAutowired(true) + ; + + $container->setDefinition('child1', new DefinitionDecorator('parent')); + + $this->process($container); + + $this->assertTrue($container->getDefinition('child1')->isAutowired()); + } + public function testDeepDefinitionsResolving() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionDecoratorTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionDecoratorTest.php index 12122ff968..c42cf2350d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionDecoratorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionDecoratorTest.php @@ -69,6 +69,16 @@ class DefinitionDecoratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array('lazy' => true), $def->getChanges()); } + public function testSetAutowired() + { + $def = new DefinitionDecorator('foo'); + + $this->assertFalse($def->isAutowired()); + $this->assertSame($def, $def->setAutowired(false)); + $this->assertFalse($def->isAutowired()); + $this->assertEquals(array('autowire' => true), $def->getChanges()); + } + public function testSetArgument() { $def = new DefinitionDecorator('foo'); diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 82461d0159..28093be434 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -61,11 +61,14 @@ class IpUtils */ public static function checkIp4($requestIp, $ip) { + if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return false; + } + if (false !== strpos($ip, '/')) { list($address, $netmask) = explode('/', $ip, 2); if ($netmask === '0') { - // Ensure IP is valid - using ip2long below implicitly validates, but we need to do it manually here return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index 877053f0f3..a6d28a2943 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -37,6 +37,7 @@ class IpUtilsTest extends \PHPUnit_Framework_TestCase array(true, '1.2.3.4', '0.0.0.0/0'), array(true, '1.2.3.4', '192.168.1.0/0'), array(false, '1.2.3.4', '256.256.256/0'), // invalid CIDR notation + array(false, 'an_invalid_ip', '192.168.1.0/24'), ); } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 471f19f603..b5829afd38 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -248,8 +248,18 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer return; } - $builtinType = $type->getBuiltinType(); - $class = $type->getClassName(); + if ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { + $builtinType = Type::BUILTIN_TYPE_OBJECT; + $class = $collectionValueType->getClassName().'[]'; + + if (null !== $collectionKeyType = $type->getCollectionKeyType()) { + $context['key_type'] = $collectionKeyType; + } + } else { + $builtinType = $type->getBuiltinType(); + $class = $type->getClassName(); + } + $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php index 921e312bd0..7d3d87c510 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -30,6 +31,8 @@ class ArrayDenormalizer implements DenormalizerInterface, SerializerAwareInterfa /** * {@inheritdoc} + * + * @throws UnexpectedValueException */ public function denormalize($data, $class, $format = null, array $context = array()) { @@ -46,12 +49,16 @@ class ArrayDenormalizer implements DenormalizerInterface, SerializerAwareInterfa $serializer = $this->serializer; $class = substr($class, 0, -2); - return array_map( - function ($data) use ($serializer, $class, $format, $context) { - return $serializer->denormalize($data, $class, $format, $context); - }, - $data - ); + $builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null; + foreach ($data as $key => $value) { + if (null !== $builtinType && !call_user_func('is_'.$builtinType, $key)) { + throw new UnexpectedValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, gettype($key))); + } + + $data[$key] = $serializer->denormalize($value, $class, $format, $context); + } + + return $data; } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index ad937e78d9..1e0c43ea1a 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -12,9 +12,12 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -569,13 +572,21 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase public function testDenomalizeRecursive() { - $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); - $serializer = new Serializer(array(new DateTimeNormalizer(), $normalizer)); + $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor())); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer)); + + $obj = $serializer->denormalize(array( + 'inner' => array('foo' => 'foo', 'bar' => 'bar'), + 'date' => '1988/01/21', + 'inners' => array(array('foo' => 1), array('foo' => 2)), + ), ObjectOuter::class); - $obj = $serializer->denormalize(array('inner' => array('foo' => 'foo', 'bar' => 'bar'), 'date' => '1988/01/21'), ObjectOuter::class); $this->assertEquals('foo', $obj->getInner()->foo); $this->assertEquals('bar', $obj->getInner()->bar); $this->assertEquals('1988-01-21', $obj->getDate()->format('Y-m-d')); + $this->assertEquals(1, $obj->getInners()[0]->foo); + $this->assertEquals(2, $obj->getInners()[1]->foo); } /** @@ -590,6 +601,19 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase $serializer->denormalize(array('date' => 'foo'), ObjectOuter::class); } + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage The type of the key "a" must be "int" ("string" given). + */ + public function testRejectInvalidKey() + { + $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor())); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer)); + + $serializer->denormalize(array('inners' => array('a' => array('foo' => 1))), ObjectOuter::class); + } + public function testExtractAttributesRespectsFormat() { $normalizer = new FormatAndContextAwareNormalizer(); @@ -784,6 +808,11 @@ class ObjectOuter private $inner; private $date; + /** + * @var ObjectInner[] + */ + private $inners; + public function getInner() { return $this->inner; @@ -803,6 +832,16 @@ class ObjectOuter { return $this->date; } + + public function setInners(array $inners) + { + $this->inners = $inners; + } + + public function getInners() + { + return $this->inners; + } } class ObjectInner diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 13d60e1a9e..1f4d76d605 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -24,9 +24,10 @@ "symfony/property-access": "~2.8|~3.0", "symfony/http-foundation": "~2.8|~3.0", "symfony/cache": "~3.1", - "symfony/property-info": "~2.8|~3.0", + "symfony/property-info": "~3.1", "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0" + "doctrine/cache": "~1.0", + "phpdocumentor/reflection-docblock": "~3.0" }, "conflict": { "symfony/property-access": ">=3.0,<3.0.4|>=2.8,<2.8.4"