diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
index 02b77319ac249..720da85750526 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/form.html.twig
@@ -177,6 +177,12 @@
color: inherit;
text-decoration: inherit;
}
+ h2 + h3.form-data-type {
+ margin-top: 0;
+ }
+ h3.form-data-type + h3 {
+ margin-top: 1em;
+ }
{% endblock %}
@@ -455,9 +461,10 @@
{% macro form_tree_details(name, data, forms_by_hash, show) %}
{% import _self as tree %}
-
- {{ name|default('(no name)') }} {% if data.type_class is defined %}({{ profiler_dump(data.type_class) }}){% endif %}
-
+
{{ name|default('(no name)') }}
+ {% if data.type_class is defined %}
+
+ {% endif %}
{% if data.errors is defined and data.errors|length > 0 %}
diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
index 59d28a33c1bea..1f2f4d40acfa0 100644
--- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
+++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
@@ -192,4 +192,9 @@ public function provideDsnWithOptions()
array(\Memcached::OPT_SOCKET_RECV_SIZE => 1, \Memcached::OPT_SOCKET_SEND_SIZE => 2, \Memcached::OPT_RETRY_TIMEOUT => 8),
);
}
+
+ public function testClear()
+ {
+ $this->assertTrue($this->createCachePool()->clear());
+ }
}
diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php
index cf04f1cf85664..8160f14116162 100644
--- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php
+++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php
@@ -260,7 +260,7 @@ protected function doDelete(array $ids)
*/
protected function doClear($namespace)
{
- return false;
+ return '' === $namespace && $this->getClient()->flush();
}
private function checkResultCode($result)
diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php
index b7f3ca37fe6fd..3ee37ec4e7929 100644
--- a/src/Symfony/Component/Debug/DebugClassLoader.php
+++ b/src/Symfony/Component/Debug/DebugClassLoader.php
@@ -137,14 +137,14 @@ public function loadClass($class)
try {
if ($this->isFinder && !isset($this->loaded[$class])) {
$this->loaded[$class] = true;
- if ($file = $this->classLoader[0]->findFile($class) ?: false) {
- $wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file);
-
+ if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
+ // no-op
+ } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
require $file;
- if ($wasCached) {
- return;
- }
+ return;
+ } else {
+ require $file;
}
} else {
\call_user_func($this->classLoader, $class);
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
index 78073d93169d7..610a56bb0e607 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php
@@ -93,8 +93,12 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
}
if (!$definition->isShared()) {
+ if (!$graph->hasNode($id)) {
+ return true;
+ }
+
foreach ($graph->getNode($id)->getInEdges() as $edge) {
- if ($edge->isWeak()) {
+ if ($edge->isWeak() || $edge->isLazy()) {
return false;
}
}
@@ -115,13 +119,19 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
}
$ids = array();
+ $isReferencedByConstructor = false;
foreach ($graph->getNode($id)->getInEdges() as $edge) {
- if ($edge->isWeak()) {
+ $isReferencedByConstructor = $isReferencedByConstructor || $edge->isReferencedByConstructor();
+ if ($edge->isWeak() || $edge->isLazy()) {
return false;
}
$ids[] = $edge->getSourceNode()->getId();
}
+ if (!$ids) {
+ return true;
+ }
+
if (\count(array_unique($ids)) > 1) {
return false;
}
@@ -130,6 +140,10 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe
return false;
}
- return !$ids || $this->container->getDefinition($ids[0])->isShared();
+ if ($isReferencedByConstructor && $this->container->getDefinition($ids[0])->isLazy() && ($definition->getProperties() || $definition->getMethodCalls() || $definition->getConfigurator())) {
+ return false;
+ }
+
+ return $this->container->getDefinition($ids[0])->isShared();
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index d9a9c9c8237bb..102033ce6d1fe 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -156,17 +156,18 @@ public function dump(array $options = array())
}
(new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container);
+ $checkedNodes = array();
$this->circularReferences = array();
- foreach (array(true, false) as $byConstructor) {
- foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
- if (!$node->getValue() instanceof Definition) {
- continue;
- }
- $currentPath = array($id => true);
- $this->analyzeCircularReferences($node->getOutEdges(), $currentPath, $id, $byConstructor);
+ foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
+ if (!$node->getValue() instanceof Definition) {
+ continue;
+ }
+ if (!isset($checkedNodes[$id])) {
+ $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes);
}
}
$this->container->getCompiler()->getServiceReferenceGraph()->clear();
+ $checkedNodes = array();
$this->docStar = $options['debug'] ? '*' : '';
@@ -307,12 +308,12 @@ private function getProxyDumper(): ProxyDumper
return $this->proxyDumper;
}
- private function analyzeCircularReferences(array $edges, &$currentPath, $sourceId, $byConstructor)
+ private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = array())
{
+ $checkedNodes[$sourceId] = true;
+ $currentPath[$sourceId] = $sourceId;
+
foreach ($edges as $edge) {
- if ($byConstructor && !$edge->isReferencedByConstructor()) {
- continue;
- }
$node = $edge->getDestNode();
$id = $node->getId();
@@ -321,20 +322,42 @@ private function analyzeCircularReferences(array $edges, &$currentPath, $sourceI
} elseif (isset($currentPath[$id])) {
$currentId = $id;
foreach (array_reverse($currentPath) as $parentId) {
- if (!isset($this->circularReferences[$parentId][$currentId])) {
- $this->circularReferences[$parentId][$currentId] = $byConstructor;
+ $this->circularReferences[$parentId][$currentId] = $currentId;
+ if ($parentId === $id) {
+ break;
}
+ $currentId = $parentId;
+ }
+ } elseif (!isset($checkedNodes[$id])) {
+ $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath);
+ } elseif (isset($this->circularReferences[$id])) {
+ $this->connectCircularReferences($id, $currentPath);
+ }
+ }
+ unset($currentPath[$sourceId]);
+ }
+
+ private function connectCircularReferences($sourceId, &$currentPath, &$subPath = array())
+ {
+ $subPath[$sourceId] = $sourceId;
+ $currentPath[$sourceId] = $sourceId;
+
+ foreach ($this->circularReferences[$sourceId] as $id) {
+ if (isset($currentPath[$id])) {
+ $currentId = $id;
+ foreach (array_reverse($currentPath) as $parentId) {
+ $this->circularReferences[$parentId][$currentId] = $currentId;
if ($parentId === $id) {
break;
}
$currentId = $parentId;
}
- } else {
- $currentPath[$id] = $id;
- $this->analyzeCircularReferences($node->getOutEdges(), $currentPath, $id, $byConstructor);
- unset($currentPath[$id]);
+ } elseif (!isset($subPath[$id]) && isset($this->circularReferences[$id])) {
+ $this->connectCircularReferences($id, $currentPath, $subPath);
}
}
+ unset($currentPath[$sourceId]);
+ unset($subPath[$sourceId]);
}
private function collectLineage($class, array &$lineage)
@@ -539,7 +562,8 @@ private function addServiceConfigurator(Definition $definition, string $variable
if (\is_array($callable)) {
if ($callable[0] instanceof Reference
- || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
+ || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))
+ ) {
return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
@@ -691,7 +715,7 @@ private function addInlineReference(string $id, Definition $definition, string $
$hasSelfRef = isset($this->circularReferences[$id][$targetId]);
$forConstructor = $forConstructor && !isset($this->definitionVariables[$definition]);
- $code = $hasSelfRef && $this->circularReferences[$id][$targetId] && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : '';
+ $code = $hasSelfRef && !$forConstructor ? $this->addInlineService($id, $definition, $definition) : '';
if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) {
return $code;
@@ -1584,6 +1608,7 @@ private function getServiceCall(string $id, Reference $reference = null): string
if ($definition->isShared()) {
$code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code);
}
+ $code = "($code)";
} elseif ($this->asFiles && !$this->isHotPath($definition)) {
$code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id));
if (!$definition->isShared()) {
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index 962137e73da38..fa4e42017e03e 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -792,7 +792,7 @@ public function testExpressionReferencingPrivateService()
->setPublic(false);
$container->register('public_foo', 'stdClass')
->setPublic(true)
- ->addArgument(new Expression('service("private_foo")'));
+ ->addArgument(new Expression('service("private_foo").bar'));
$container->compile();
$dumper = new PhpDumper($container);
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
index 7f9c5cec280b1..fba001613d2b4 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
@@ -347,7 +347,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () {
yield 0 => ($this->services['foo'] ?? $this->load('getFooService.php'));
- yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar());
+ yield 1 => ($this->privates['tagged_iterator_foo'] ?? ($this->privates['tagged_iterator_foo'] = new \Bar()));
}, 2));
[Container%s/getTaggedIteratorFooService.php] => services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () {
yield 0 => ($this->services['foo'] ?? $this->getFooService());
- yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar());
+ yield 1 => ($this->privates['tagged_iterator_foo'] ?? ($this->privates['tagged_iterator_foo'] = new \Bar()));
}, 2));
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
index 7923ae879f5f2..1a14c5d4d6875 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php
@@ -114,7 +114,7 @@ protected function getBar3Service()
{
$this->services['bar3'] = $instance = new \BarCircular();
- $a = ($this->services['foobar3'] ?? $this->services['foobar3'] = new \FoobarCircular());
+ $a = ($this->services['foobar3'] ?? ($this->services['foobar3'] = new \FoobarCircular()));
$instance->addFoobar($a, $a);
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php
index 93063503bd0d7..4b914f206f10f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php
@@ -70,7 +70,7 @@ public function getRemovedIds()
*/
protected function getBarService()
{
- return $this->services['bar'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? $this->privates['bar_%env(BAR)%'] = new \stdClass()));
+ return $this->services['bar'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? ($this->privates['bar_%env(BAR)%'] = new \stdClass())));
}
/**
@@ -80,7 +80,7 @@ protected function getBarService()
*/
protected function getFooService()
{
- return $this->services['foo'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? $this->privates['bar_%env(BAR)%'] = new \stdClass()), array('baz_'.$this->getEnv('string:BAR') => new \stdClass()));
+ return $this->services['foo'] = new \stdClass(($this->privates['bar_%env(BAR)%'] ?? ($this->privates['bar_%env(BAR)%'] = new \stdClass())), array('baz_'.$this->getEnv('string:BAR') => new \stdClass()));
}
public function getParameter($name)
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php
index c3351a208c3eb..c5b90d19b2caa 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_errored_definition.php
@@ -402,7 +402,7 @@ protected function getTaggedIteratorService()
{
return $this->services['tagged_iterator'] = new \Bar(new RewindableGenerator(function () {
yield 0 => ($this->services['foo'] ?? $this->getFooService());
- yield 1 => ($this->privates['tagged_iterator_foo'] ?? $this->privates['tagged_iterator_foo'] = new \Bar());
+ yield 1 => ($this->privates['tagged_iterator_foo'] ?? ($this->privates['tagged_iterator_foo'] = new \Bar()));
}, 2));
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php
index 59bbce3a995c4..c705797921383 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php
@@ -76,7 +76,7 @@ public function getRemovedIds()
*/
protected function getBarServiceService()
{
- return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass()));
+ return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass())));
}
/**
@@ -89,7 +89,7 @@ protected function getFooServiceService()
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('bar' => function () {
return ($this->services['bar_service'] ?? $this->getBarServiceService());
}, 'baz' => function (): \stdClass {
- return ($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass());
+ return ($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass()));
}, 'nil' => function () {
return NULL;
}));
@@ -133,7 +133,7 @@ protected function getTranslator_Loader3Service()
protected function getTranslator1Service()
{
return $this->services['translator_1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_1' => function () {
- return ($this->services['translator.loader_1'] ?? $this->services['translator.loader_1'] = new \stdClass());
+ return ($this->services['translator.loader_1'] ?? ($this->services['translator.loader_1'] = new \stdClass()));
})));
}
@@ -145,10 +145,10 @@ protected function getTranslator1Service()
protected function getTranslator2Service()
{
$this->services['translator_2'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_2' => function () {
- return ($this->services['translator.loader_2'] ?? $this->services['translator.loader_2'] = new \stdClass());
+ return ($this->services['translator.loader_2'] ?? ($this->services['translator.loader_2'] = new \stdClass()));
})));
- $instance->addResource('db', ($this->services['translator.loader_2'] ?? $this->services['translator.loader_2'] = new \stdClass()), 'nl');
+ $instance->addResource('db', ($this->services['translator.loader_2'] ?? ($this->services['translator.loader_2'] = new \stdClass())), 'nl');
return $instance;
}
@@ -161,10 +161,10 @@ protected function getTranslator2Service()
protected function getTranslator3Service()
{
$this->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_3' => function () {
- return ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass());
+ return ($this->services['translator.loader_3'] ?? ($this->services['translator.loader_3'] = new \stdClass()));
})));
- $a = ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass());
+ $a = ($this->services['translator.loader_3'] ?? ($this->services['translator.loader_3'] = new \stdClass()));
$instance->addResource('db', $a, 'nl');
$instance->addResource('db', $a, 'en');
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php
index 86315e2ebaffc..7a55fab633998 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_frozen.php
@@ -67,7 +67,7 @@ public function getRemovedIds()
*/
protected function getBarServiceService()
{
- return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass()));
+ return $this->services['bar_service'] = new \stdClass(($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass())));
}
/**
@@ -77,6 +77,6 @@ protected function getBarServiceService()
*/
protected function getFooServiceService()
{
- return $this->services['foo_service'] = new \stdClass(($this->privates['baz_service'] ?? $this->privates['baz_service'] = new \stdClass()));
+ return $this->services['foo_service'] = new \stdClass(($this->privates['baz_service'] ?? ($this->privates['baz_service'] = new \stdClass())));
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php
index 5caf9104dd34d..0e9686f0f5e33 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_private_in_expression.php
@@ -67,6 +67,6 @@ public function getRemovedIds()
*/
protected function getPublicFooService()
{
- return $this->services['public_foo'] = new \stdClass(($this->privates['private_foo'] ?? $this->privates['private_foo'] = new \stdClass()));
+ return $this->services['public_foo'] = new \stdClass(($this->privates['private_foo'] ?? ($this->privates['private_foo'] = new \stdClass()))->bar);
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
index 76ce9e55328e3..12fc940a461b8 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
@@ -79,7 +79,7 @@ protected function getRot13EnvVarProcessorService()
protected function getContainer_EnvVarProcessorsLocatorService()
{
return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('rot13' => function () {
- return ($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor());
+ return ($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] ?? ($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor()));
}));
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
index 2c887e0e21e0c..bc19940bb3b1e 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
@@ -80,13 +80,13 @@ protected function getTestServiceSubscriberService()
protected function getFooServiceService()
{
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
- return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition());
+ return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()));
}, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => function (): \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber {
- return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber());
+ return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()));
}, 'bar' => function (): \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
- return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber());
+ return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()));
}, 'baz' => function (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
- return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition());
+ return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()));
})))->withContext('foo_service', $this));
}
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php
index 0f5090c80bebe..a2fe59acdfeff 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_uninitialized_ref.php
@@ -107,7 +107,7 @@ protected function getBazService()
{
$this->services['baz'] = $instance = new \stdClass();
- $instance->foo3 = ($this->privates['foo3'] ?? $this->privates['foo3'] = new \stdClass());
+ $instance->foo3 = ($this->privates['foo3'] ?? ($this->privates['foo3'] = new \stdClass()));
return $instance;
}
diff --git a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
index f9e581c9a6795..2a36fbea5aea3 100644
--- a/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
+++ b/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php
@@ -134,19 +134,24 @@ public function dispatch($eventName, Event $event = null)
}
$this->preProcess($eventName);
- $this->preDispatch($eventName, $event);
-
- $e = $this->stopwatch->start($eventName, 'section');
-
- $this->dispatcher->dispatch($eventName, $event);
-
- if ($e->isStarted()) {
- $e->stop();
+ try {
+ $this->preDispatch($eventName, $event);
+ try {
+ $e = $this->stopwatch->start($eventName, 'section');
+ try {
+ $this->dispatcher->dispatch($eventName, $event);
+ } finally {
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+ }
+ } finally {
+ $this->postDispatch($eventName, $event);
+ }
+ } finally {
+ $this->postProcess($eventName);
}
- $this->postDispatch($eventName, $event);
- $this->postProcess($eventName);
-
return $event;
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
index 75502db0e9f56..95f44c86c7567 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
@@ -146,7 +146,7 @@ public function reverseTransform($value)
return;
}
- if ('NaN' === $value) {
+ if (\in_array($value, array('NaN', 'NAN', 'nan'), true)) {
throw new TransformationFailedException('"NaN" is not a valid number');
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
index 1b699d51e84ab..56fcfe17e9bf7 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
@@ -105,6 +105,7 @@ public function configureOptions(OptionsResolver $resolver)
'data_class' => $dataClass,
'empty_data' => $emptyData,
'multiple' => false,
+ 'allow_file_upload' => true,
));
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
index b96d2d7a97f24..cbb9f7b54781f 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php
@@ -179,6 +179,7 @@ public function configureOptions(OptionsResolver $resolver)
'attr' => array(),
'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.',
'upload_max_size_message' => $uploadMaxSizeMessage, // internal
+ 'allow_file_upload' => false,
'help' => null,
));
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index efbd259b4ef7b..85c448492e9a8 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -532,6 +532,11 @@ public function submit($submittedData, $clearMissing = true)
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
+ } elseif ($this->config->getOption('allow_file_upload')) {
+ // no-op
+ } elseif ($this->config->getRequestHandler()->isFileUpload($submittedData)) {
+ $submittedData = null;
+ $this->transformationFailure = new TransformationFailedException('Submitted data was expected to be text or number, file upload given.');
}
$dispatcher = $this->config->getEventDispatcher();
@@ -541,6 +546,10 @@ public function submit($submittedData, $clearMissing = true)
$viewData = null;
try {
+ if (null !== $this->transformationFailure) {
+ throw $this->transformationFailure;
+ }
+
// Hook to change content of the data submitted by the browser
if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) {
$event = new FormEvent($this, $submittedData);
diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
index 3128a544fc831..8b6982e454f2b 100644
--- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php
+++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php
@@ -709,7 +709,7 @@ public function testSubmitPostOrPutRequestWithSingleChildForm($method)
'REQUEST_METHOD' => $method,
));
- $form = $this->getBuilder('image')
+ $form = $this->getBuilder('image', null, null, array('allow_file_upload' => true))
->setMethod($method)
->setRequestHandler(new HttpFoundationRequestHandler())
->getForm();
@@ -1039,6 +1039,21 @@ public function testDisabledButtonIsNotSubmitted()
$this->assertFalse($submit->isSubmitted());
}
+ public function testFileUpload()
+ {
+ $reqHandler = new HttpFoundationRequestHandler();
+ $this->form->add($this->getBuilder('foo')->setRequestHandler($reqHandler)->getForm());
+ $this->form->add($this->getBuilder('bar')->setRequestHandler($reqHandler)->getForm());
+
+ $this->form->submit(array(
+ 'foo' => 'Foo',
+ 'bar' => new UploadedFile(__FILE__, 'upload.png', 'image/png', UPLOAD_ERR_OK),
+ ));
+
+ $this->assertSame('Submitted data was expected to be text or number, file upload given.', $this->form->get('bar')->getTransformationFailure()->getMessage());
+ $this->assertNull($this->form->get('bar')->getData());
+ }
+
protected function createForm()
{
return $this->getBuilder()
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
index d6a4662102232..176d3a9a58f9e 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php
@@ -514,24 +514,24 @@ public function testReverseTransformExpectsValidNumber()
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
+ * @dataProvider nanRepresentationProvider
*
* @see https://github.com/symfony/symfony/issues/3161
*/
- public function testReverseTransformDisallowsNaN()
+ public function testReverseTransformDisallowsNaN($nan)
{
$transformer = new NumberToLocalizedStringTransformer();
- $transformer->reverseTransform('NaN');
+ $transformer->reverseTransform($nan);
}
- /**
- * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
- */
- public function testReverseTransformDisallowsNaN2()
+ public function nanRepresentationProvider()
{
- $transformer = new NumberToLocalizedStringTransformer();
-
- $transformer->reverseTransform('nan');
+ return array(
+ array('nan'),
+ array('NaN'), // see https://github.com/symfony/symfony/issues/3161
+ array('NAN'),
+ );
}
/**
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
index 0f43316313f63..f3c1bf4fbb037 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.json
@@ -28,6 +28,7 @@
"parent": {
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType": [
"action",
+ "allow_file_upload",
"attr",
"auto_initialize",
"block_name",
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
index 34120481edb44..c6c58f68fedd8 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_1.txt
@@ -8,15 +8,16 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice")
choice_attr FormType FormType FormTypeCsrfExtension
choice_label -------------------- ------------------------- -----------------------
choice_loader compound action csrf_field_name
- choice_name data_class attr csrf_message
- choice_translation_domain empty_data auto_initialize csrf_protection
- choice_value error_bubbling block_name csrf_token_id
- choices trim by_reference csrf_token_manager
- expanded data
- group_by disabled
- multiple help
- placeholder inherit_data
- preferred_choices label
+ choice_name data_class allow_file_upload csrf_message
+ choice_translation_domain empty_data attr csrf_protection
+ choice_value error_bubbling auto_initialize csrf_token_id
+ choices trim block_name csrf_token_manager
+ expanded by_reference
+ group_by data
+ multiple disabled
+ placeholder help
+ preferred_choices inherit_data
+ label
label_attr
label_format
mapped
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json
index b0735e7248687..98828a4e6f034 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.json
@@ -4,6 +4,7 @@
"options": {
"own": [
"action",
+ "allow_file_upload",
"attr",
"auto_initialize",
"block_name",
diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt
index 78a4db0e684f7..ec1d372c7d7c9 100644
--- a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt
+++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/resolved_form_type_2.txt
@@ -6,6 +6,7 @@ Symfony\Component\Form\Extension\Core\Type\FormType (Block prefix: "form")
Options
-------------------------
action
+ allow_file_upload
attr
auto_initialize
block_name
diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php
index 06d210056c99d..2b713374f4216 100644
--- a/src/Symfony/Component/HttpKernel/HttpKernel.php
+++ b/src/Symfony/Component/HttpKernel/HttpKernel.php
@@ -248,6 +248,9 @@ private function handleException(\Exception $e, Request $request, int $type): Re
}
}
+ /**
+ * Returns a human-readable string for the specified variable.
+ */
private function varToString($var): string
{
if (\is_object($var)) {
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index 244f664a6454c..1070b64cb96d2 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -63,11 +63,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private $requestStackSize = 0;
private $resetServices = false;
- const VERSION = '4.1.8';
- const VERSION_ID = 40108;
+ const VERSION = '4.1.9';
+ const VERSION_ID = 40109;
const MAJOR_VERSION = 4;
const MINOR_VERSION = 1;
- const RELEASE_VERSION = 8;
+ const RELEASE_VERSION = 9;
const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '01/2019';
diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
index d8a274d4a812e..574425eb457f3 100644
--- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
+++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
@@ -638,8 +638,8 @@ public function setSymbol($attr, $value)
/**
* Not supported. Set a text attribute.
*
- * @param int $attr An attribute specifier, one of the text attribute constants
- * @param int $value The attribute value
+ * @param int $attr An attribute specifier, one of the text attribute constants
+ * @param string $value The attribute value
*
* @return bool true on success or false on failure
*
diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
index 5efbe2a906ea4..2db7357d6488a 100644
--- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
+++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
@@ -587,7 +587,8 @@ private function writeCollection($zval, $property, $collection, $addMethod, $rem
*/
private function getWriteAccessInfo(string $class, string $property, $value): array
{
- $key = str_replace('\\', '.', $class).'..'.$property;
+ $useAdderAndRemover = \is_array($value) || $value instanceof \Traversable;
+ $key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover;
if (isset($this->writePropertyCache[$key])) {
return $this->writePropertyCache[$key];
@@ -607,6 +608,16 @@ private function getWriteAccessInfo(string $class, string $property, $value): ar
$camelized = $this->camelize($property);
$singulars = (array) Inflector::singularize($camelized);
+ if ($useAdderAndRemover) {
+ $methods = $this->findAdderAndRemover($reflClass, $singulars);
+
+ if (null !== $methods) {
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
+ $access[self::ACCESS_ADDER] = $methods[0];
+ $access[self::ACCESS_REMOVER] = $methods[1];
+ }
+ }
+
if (!isset($access[self::ACCESS_TYPE])) {
$setter = 'set'.$camelized;
$getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
@@ -628,22 +639,16 @@ private function getWriteAccessInfo(string $class, string $property, $value): ar
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
$access[self::ACCESS_NAME] = $setter;
} elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) {
- if (\is_array($value) || $value instanceof \Traversable) {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
- $access[self::ACCESS_ADDER] = $methods[0];
- $access[self::ACCESS_REMOVER] = $methods[1];
- } else {
- $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
- $access[self::ACCESS_NAME] = sprintf(
- 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
- 'the new value must be an array or an instance of \Traversable, '.
- '"%s" given.',
- $property,
- $reflClass->name,
- implode('()", "', $methods),
- \is_object($value) ? \get_class($value) : \gettype($value)
- );
- }
+ $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
+ $access[self::ACCESS_NAME] = sprintf(
+ 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
+ 'the new value must be an array or an instance of \Traversable, '.
+ '"%s" given.',
+ $property,
+ $reflClass->name,
+ implode('()", "', $methods),
+ \is_object($value) ? \get_class($value) : \gettype($value)
+ );
} else {
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
$access[self::ACCESS_NAME] = sprintf(
@@ -680,6 +685,18 @@ private function isPropertyWritable($object, string $property): bool
$access = $this->getWriteAccessInfo(\get_class($object), $property, array());
+ $isWritable = self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]
+ || self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]
+ || self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]
+ || (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property))
+ || self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE];
+
+ if ($isWritable) {
+ return true;
+ }
+
+ $access = $this->getWriteAccessInfo(\get_class($object), $property, '');
+
return self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]
|| self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]
diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
index e2918a9b4efcb..d6bf88372cc7a 100644
--- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
+++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php
@@ -708,7 +708,27 @@ public function testWriteToPluralPropertyWhileSingularOneExists()
$this->propertyAccessor->isWritable($object, 'emails'); //cache access info
$this->propertyAccessor->setValue($object, 'emails', array('test@email.com'));
- self::assertEquals(array('test@email.com'), $object->getEmails());
- self::assertNull($object->getEmail());
+ $this->assertEquals(array('test@email.com'), $object->getEmails());
+ $this->assertNull($object->getEmail());
+ }
+
+ public function testAdderAndRemoverArePreferredOverSetter()
+ {
+ $object = new TestPluralAdderRemoverAndSetter();
+
+ $this->propertyAccessor->isWritable($object, 'emails'); //cache access info
+ $this->propertyAccessor->setValue($object, 'emails', array('test@email.com'));
+
+ $this->assertEquals(array('test@email.com'), $object->getEmails());
+ }
+
+ public function testAdderAndRemoverArePreferredOverSetterForSameSingularAndPlural()
+ {
+ $object = new TestPluralAdderRemoverAndSetterSameSingularAndPlural();
+
+ $this->propertyAccessor->isWritable($object, 'aircraft'); //cache access info
+ $this->propertyAccessor->setValue($object, 'aircraft', array('aeroplane'));
+
+ $this->assertEquals(array('aeroplane'), $object->getAircraft());
}
}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetter.php b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetter.php
new file mode 100644
index 0000000000000..ecb3f9b4a9d32
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetter.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyAccess\Tests;
+
+class TestPluralAdderRemoverAndSetter
+{
+ private $emails = array();
+
+ public function getEmails()
+ {
+ return $this->emails;
+ }
+
+ public function setEmails(array $emails)
+ {
+ $this->emails = array('foo@email.com');
+ }
+
+ public function addEmail($email)
+ {
+ $this->emails[] = $email;
+ }
+
+ public function removeEmail($email)
+ {
+ $this->emails = array_diff($this->emails, array($email));
+ }
+}
diff --git a/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetterSameSingularAndPlural.php b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetterSameSingularAndPlural.php
new file mode 100644
index 0000000000000..bb3b4f4688dc5
--- /dev/null
+++ b/src/Symfony/Component/PropertyAccess/Tests/TestPluralAdderRemoverAndSetterSameSingularAndPlural.php
@@ -0,0 +1,28 @@
+aircraft;
+ }
+
+ public function setAircraft(array $aircraft)
+ {
+ $this->aircraft = array('plane');
+ }
+
+ public function addAircraft($aircraft)
+ {
+ $this->aircraft[] = $aircraft;
+ }
+
+ public function removeAircraft($aircraft)
+ {
+ $this->aircraft = array_diff($this->aircraft, array($aircraft));
+ }
+}
diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
index 4cb1c7429bb10..0dc0a178a8542 100644
--- a/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
+++ b/src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
@@ -161,10 +161,10 @@ public function match($pathinfo)
throw new ResourceNotFoundException();
}
- private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
+ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array
EOF
- .$code."\n return null;\n }";
+ .$code."\n return array();\n }";
}
return " public function match(\$rawPathinfo)\n".$code."\n throw \$allow ? new MethodNotAllowedException(array_keys(\$allow)) : new ResourceNotFoundException();\n }";
@@ -550,10 +550,23 @@ private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \st
private function compileSwitchDefault(bool $hasVars, bool $matchHost): string
{
$code = sprintf("
- if ('/' !== \$pathinfo && \$hasTrailingSlash !== ('/' === \$pathinfo[-1])) {
- %s;
+ if ('/' !== \$pathinfo) {%s
+ if (\$hasTrailingSlash !== ('/' === \$pathinfo[-1])) {%s
+ break;
+ }
}\n",
- $this->supportsRedirections ? 'return null' : 'break'
+ $hasVars ? "
+ if ('/' === \$pathinfo[-1]) {
+ if (preg_match(\$regex, substr(\$pathinfo, 0, -1), \$n) && \$m === (int) \$n['MARK']) {
+ \$matches = \$n;
+ } else {
+ \$hasTrailingSlash = true;
+ }
+ }\n" : '',
+ $this->supportsRedirections ? "
+ if ((!\$requiredMethods || isset(\$requiredMethods['GET'])) && 'GET' === \$canonicalMethod) {
+ return \$allow = \$allowSchemes = array();
+ }" : ''
);
if ($hasVars) {
@@ -610,26 +623,52 @@ private function compileSwitchDefault(bool $hasVars, bool $matchHost): string
*/
private function compileRoute(Route $route, string $name, bool $checkHost, bool $hasTrailingSlash): string
{
+ $compiledRoute = $route->compile();
+ $conditions = array();
+ $matches = (bool) $compiledRoute->getPathVariables();
+ $hostMatches = (bool) $compiledRoute->getHostVariables();
+ $methods = array_flip($route->getMethods());
+ $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
$code = " // $name";
- if ('/' !== $route->getPath()) {
+ if ('/' === $route->getPath()) {
+ $code .= "\n";
+ } elseif (!$matches) {
$code .= sprintf("
- if ('/' !== \$pathinfo && '/' %s \$pathinfo[-1]) {
- %s;
- }\n",
+ if ('/' !== \$pathinfo && '/' %s \$pathinfo[-1]) {%s
+ goto $gotoname;
+ }\n\n",
$hasTrailingSlash ? '!==' : '===',
- $this->supportsRedirections ? 'return null' : 'break'
+ $this->supportsRedirections && (!$methods || isset($methods['GET'])) ? "
+ if ('GET' === \$canonicalMethod) {
+ return \$allow = \$allowSchemes = array();
+ }" : ''
+ );
+ } elseif ($hasTrailingSlash) {
+ $code .= sprintf("
+ if ('/' !== \$pathinfo[-1]) {%s
+ goto $gotoname;
+ }
+ if ('/' !== \$pathinfo && preg_match(\$regex, substr(\$pathinfo, 0, -1), \$n) && \$m === (int) \$n['MARK']) {
+ \$matches = \$n;
+ }\n\n",
+ $this->supportsRedirections && (!$methods || isset($methods['GET'])) ? "
+ if ('GET' === \$canonicalMethod) {
+ return \$allow = \$allowSchemes = array();
+ }" : ''
);
} else {
- $code .= "\n";
+ $code .= sprintf("
+ if ('/' !== \$pathinfo && '/' === \$pathinfo[-1] && preg_match(\$regex, substr(\$pathinfo, 0, -1), \$n) && \$m === (int) \$n['MARK']) {%s
+ goto $gotoname;
+ }\n\n",
+ $this->supportsRedirections && (!$methods || isset($methods['GET'])) ? "
+ if ('GET' === \$canonicalMethod) {
+ return \$allow = \$allowSchemes = array();
+ }" : ''
+ );
}
- $compiledRoute = $route->compile();
- $conditions = array();
- $matches = (bool) $compiledRoute->getPathVariables();
- $hostMatches = (bool) $compiledRoute->getHostVariables();
- $methods = array_flip($route->getMethods());
-
if ($route->getCondition()) {
$expression = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request'));
@@ -658,8 +697,6 @@ private function compileRoute(Route $route, string $name, bool $checkHost, bool
$code = $this->indent($code);
}
- $gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
-
// the offset where the return value is appended below, with indendation
$retOffset = 12 + \strlen($code);
$defaults = $route->getDefaults();
@@ -741,16 +778,10 @@ private function compileRoute(Route $route, string $name, bool $checkHost, bool
$code = substr_replace($code, 'return', $retOffset, 6);
}
if ($conditions) {
- $code .= " }\n";
- } elseif ($schemes || $methods) {
- $code .= ' ';
- }
-
- if ($schemes || $methods) {
- $code .= " $gotoname:\n";
+ $code = $this->indent($code)." }\n";
}
- return $conditions ? $this->indent($code) : $code;
+ return $code." $gotoname:\n";
}
private function getExpressionLanguage()
diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
index f2b353dfeb140..f8379b41c5153 100644
--- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
+++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php
@@ -130,21 +130,26 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac
*/
protected function matchCollection($pathinfo, RouteCollection $routes)
{
+ // HEAD and GET are equivalent as per RFC
+ if ('HEAD' === $method = $this->context->getMethod()) {
+ $method = 'GET';
+ }
$supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface;
foreach ($routes as $name => $route) {
$compiledRoute = $route->compile();
$staticPrefix = $compiledRoute->getStaticPrefix();
+ $requiredMethods = $route->getMethods();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) {
// no-op
- } elseif (!$supportsTrailingSlash) {
+ } elseif (!$supportsTrailingSlash || ($requiredMethods && !\in_array('GET', $requiredMethods)) || 'GET' !== $method) {
continue;
} elseif ('/' === $staticPrefix[-1] && substr($staticPrefix, 0, -1) === $pathinfo) {
- return;
+ return $this->allow = $this->allowSchemes = array();
} elseif ('/' === $pathinfo[-1] && substr($pathinfo, 0, -1) === $staticPrefix) {
- return;
+ return $this->allow = $this->allowSchemes = array();
} else {
continue;
}
@@ -160,8 +165,20 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
continue;
}
- if ($supportsTrailingSlash && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return;
+ if ($supportsTrailingSlash) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $m)) {
+ $matches = $m;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || \in_array('GET', $requiredMethods)) && 'GET' === $method) {
+ return $this->allow = $this->allowSchemes = array();
+ }
+ continue;
+ }
}
$hostMatches = array();
@@ -176,12 +193,7 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
}
$hasRequiredScheme = !$route->getSchemes() || $route->hasScheme($this->context->getScheme());
- if ($requiredMethods = $route->getMethods()) {
- // HEAD and GET are equivalent as per RFC
- if ('HEAD' === $method = $this->context->getMethod()) {
- $method = 'GET';
- }
-
+ if ($requiredMethods) {
if (!\in_array($method, $requiredMethods)) {
if ($hasRequiredScheme) {
$this->allow = array_merge($this->allow, $requiredMethods);
@@ -199,6 +211,8 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array()));
}
+
+ return array();
}
/**
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
index 531d1ecabae75..c5074514e86e4 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php
@@ -54,8 +54,10 @@ public function match($rawPathinfo)
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
if ($requiredHost) {
@@ -140,15 +142,24 @@ public function match($rawPathinfo)
$matches = array('foo' => $matches[1] ?? null);
// baz4
- if ('/' !== $pathinfo && '/' !== $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo[-1]) {
+ goto not_baz4;
}
+ if ('/' !== $pathinfo && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ }
+
return $this->mergeDefaults(array('_route' => 'baz4') + $matches, array());
+ not_baz4:
// baz5
- if ('/' !== $pathinfo && '/' !== $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo[-1]) {
+ goto not_baz5;
+ }
+ if ('/' !== $pathinfo && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
}
+
$ret = $this->mergeDefaults(array('_route' => 'baz5') + $matches, array());
if (!isset(($a = array('POST' => 0))[$requestMethod])) {
$allow += $a;
@@ -159,9 +170,13 @@ public function match($rawPathinfo)
not_baz5:
// baz.baz6
- if ('/' !== $pathinfo && '/' !== $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo[-1]) {
+ goto not_bazbaz6;
+ }
+ if ('/' !== $pathinfo && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
}
+
$ret = $this->mergeDefaults(array('_route' => 'baz.baz6') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
@@ -176,9 +191,10 @@ public function match($rawPathinfo)
$matches = array('foo' => $matches[1] ?? null);
// foo1
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ goto not_foo1;
}
+
$ret = $this->mergeDefaults(array('_route' => 'foo1') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
@@ -193,20 +209,24 @@ public function match($rawPathinfo)
$matches = array('foo1' => $matches[1] ?? null);
// foo2
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ goto not_foo2;
}
+
return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array());
+ not_foo2:
break;
case 279:
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// foo3
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ goto not_foo3;
}
+
return $this->mergeDefaults(array('_route' => 'foo3') + $matches, array());
+ not_foo3:
break;
default:
@@ -232,8 +252,18 @@ public function match($rawPathinfo)
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
foreach ($vars as $i => $v) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php
index eee7e080d89fd..ab907b2393c91 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher10.php
@@ -2793,8 +2793,18 @@ public function match($rawPathinfo)
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
foreach ($vars as $i => $v) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php
index 8f3f0599f2eb4..5621ec968c13e 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher11.php
@@ -50,7 +50,7 @@ public function match($pathinfo)
throw new ResourceNotFoundException();
}
- private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
+ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo) ?: '/';
@@ -118,8 +118,21 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
foreach ($vars as $i => $v) {
@@ -154,6 +167,6 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
- return null;
+ return array();
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php
index 406e13336a6cb..d92394f5a6f52 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher12.php
@@ -63,8 +63,18 @@ public function match($rawPathinfo)
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
foreach ($vars as $i => $v) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php
index 4965615b43df2..23c9327857175 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher13.php
@@ -45,16 +45,20 @@ public function match($rawPathinfo)
$matches = array('foo' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// r1
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ goto not_r1;
}
+
return $this->mergeDefaults(array('_route' => 'r1') + $matches, array());
+ not_r1:
// r2
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ goto not_r2;
}
+
return $this->mergeDefaults(array('_route' => 'r2') + $matches, array());
+ not_r2:
break;
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
index fc08f2b73e946..d8d0e3bc372ab 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php
@@ -50,7 +50,7 @@ public function match($pathinfo)
throw new ResourceNotFoundException();
}
- private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
+ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo) ?: '/';
@@ -91,8 +91,13 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
if ($requiredHost) {
@@ -177,15 +182,27 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
$matches = array('foo' => $matches[1] ?? null);
// baz4
- if ('/' !== $pathinfo && '/' !== $pathinfo[-1]) {
- return null;
+ if ('/' !== $pathinfo[-1]) {
+ if ('GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ goto not_baz4;
+ }
+ if ('/' !== $pathinfo && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
}
+
return $this->mergeDefaults(array('_route' => 'baz4') + $matches, array());
+ not_baz4:
// baz5
- if ('/' !== $pathinfo && '/' !== $pathinfo[-1]) {
- return null;
+ if ('/' !== $pathinfo[-1]) {
+ goto not_baz5;
+ }
+ if ('/' !== $pathinfo && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
}
+
$ret = $this->mergeDefaults(array('_route' => 'baz5') + $matches, array());
if (!isset(($a = array('POST' => 0))[$requestMethod])) {
$allow += $a;
@@ -196,9 +213,13 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
not_baz5:
// baz.baz6
- if ('/' !== $pathinfo && '/' !== $pathinfo[-1]) {
- return null;
+ if ('/' !== $pathinfo[-1]) {
+ goto not_bazbaz6;
+ }
+ if ('/' !== $pathinfo && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
}
+
$ret = $this->mergeDefaults(array('_route' => 'baz.baz6') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
@@ -213,9 +234,10 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
$matches = array('foo' => $matches[1] ?? null);
// foo1
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- return null;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ goto not_foo1;
}
+
$ret = $this->mergeDefaults(array('_route' => 'foo1') + $matches, array());
if (!isset(($a = array('PUT' => 0))[$requestMethod])) {
$allow += $a;
@@ -230,20 +252,30 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
$matches = array('foo1' => $matches[1] ?? null);
// foo2
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- return null;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ if ('GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ goto not_foo2;
}
+
return $this->mergeDefaults(array('_route' => 'foo2') + $matches, array());
+ not_foo2:
break;
case 279:
$matches = array('_locale' => $matches[1] ?? null, 'foo' => $matches[2] ?? null);
// foo3
- if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- return null;
+ if ('/' !== $pathinfo && '/' === $pathinfo[-1] && preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ if ('GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ goto not_foo3;
}
+
return $this->mergeDefaults(array('_route' => 'foo3') + $matches, array());
+ not_foo3:
break;
default:
@@ -269,8 +301,21 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
foreach ($vars as $i => $v) {
@@ -305,6 +350,6 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
- return null;
+ return array();
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
index 291be1475760a..a6d11f6b7e501 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php
@@ -30,11 +30,13 @@ public function match($rawPathinfo)
case '/with-condition':
// with-condition
if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ goto not_withcondition;
}
+
if (($context->getMethod() == "GET")) {
return array('_route' => 'with-condition');
}
+ not_withcondition:
break;
default:
$routes = array(
@@ -46,8 +48,10 @@ public function match($rawPathinfo)
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
@@ -82,8 +86,18 @@ public function match($rawPathinfo)
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
foreach ($vars as $i => $v) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
index 6bec91e309735..97ace0d51063e 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php
@@ -30,8 +30,9 @@ public function match($rawPathinfo)
case '/put_and_post':
// put_and_post
if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ goto not_put_and_post;
}
+
$ret = array('_route' => 'put_and_post');
if (!isset(($a = array('PUT' => 0, 'POST' => 1))[$requestMethod])) {
$allow += $a;
@@ -42,8 +43,9 @@ public function match($rawPathinfo)
not_put_and_post:
// put_and_get_and_head
if ('/' !== $pathinfo && '/' === $pathinfo[-1]) {
- break;
+ goto not_put_and_get_and_head;
}
+
$ret = array('_route' => 'put_and_get_and_head');
if (!isset(($a = array('PUT' => 0, 'GET' => 1, 'HEAD' => 2))[$canonicalMethod])) {
$allow += $a;
@@ -66,8 +68,10 @@ public function match($rawPathinfo)
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php
index dee606f0a9bfa..c8d7b40e951c9 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher5.php
@@ -50,7 +50,7 @@ public function match($pathinfo)
throw new ResourceNotFoundException();
}
- private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
+ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo) ?: '/';
@@ -83,8 +83,13 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
@@ -121,8 +126,21 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
foreach ($vars as $i => $v) {
@@ -157,6 +175,6 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
- return null;
+ return array();
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php
index 839481ba2db2d..749ebd1183353 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher6.php
@@ -44,8 +44,10 @@ public function match($rawPathinfo)
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
@@ -98,8 +100,18 @@ public function match($rawPathinfo)
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
foreach ($vars as $i => $v) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php
index 07c8af66697ef..b3513bc3f7323 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher7.php
@@ -50,7 +50,7 @@ public function match($pathinfo)
throw new ResourceNotFoundException();
}
- private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
+ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): array
{
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo) ?: '/';
@@ -79,8 +79,13 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$trimmedPathinfo];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
@@ -133,8 +138,21 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- return null;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ if ((!$requiredMethods || isset($requiredMethods['GET'])) && 'GET' === $canonicalMethod) {
+ return $allow = $allowSchemes = array();
+ }
+ break;
+ }
}
foreach ($vars as $i => $v) {
@@ -169,6 +187,6 @@ private function doMatch(string $rawPathinfo, array &$allow = array(), array &$a
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
}
- return null;
+ return array();
}
}
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php
index ec5127e7b3a89..f80eb4c9d87a1 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher8.php
@@ -51,8 +51,18 @@ public function match($rawPathinfo)
list($ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash) = $routes[$m];
- if ('/' !== $pathinfo && $hasTrailingSlash !== ('/' === $pathinfo[-1])) {
- break;
+ if ('/' !== $pathinfo) {
+ if ('/' === $pathinfo[-1]) {
+ if (preg_match($regex, substr($pathinfo, 0, -1), $n) && $m === (int) $n['MARK']) {
+ $matches = $n;
+ } else {
+ $hasTrailingSlash = true;
+ }
+ }
+
+ if ($hasTrailingSlash !== ('/' === $pathinfo[-1])) {
+ break;
+ }
}
foreach ($vars as $i => $v) {
diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php
index dbea6ab94fef7..8b567addeb8b0 100644
--- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php
+++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher9.php
@@ -33,14 +33,17 @@ public function match($rawPathinfo)
if (preg_match('#^(?P[^\\.]++)\\.e\\.c\\.b\\.a$#sDi', $host, $hostMatches)) {
return $this->mergeDefaults(array('_route' => 'a') + $hostMatches, array());
}
+ not_a:
// c
if (preg_match('#^(?P[^\\.]++)\\.e\\.c\\.b\\.a$#sDi', $host, $hostMatches)) {
return $this->mergeDefaults(array('_route' => 'c') + $hostMatches, array());
}
+ not_c:
// b
if ('d.c.b.a' === $host) {
return array('_route' => 'b');
}
+ not_b:
break;
}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
index eb1703353b21e..f1d6b5325d0a5 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php
@@ -170,6 +170,23 @@ public function testMissingTrailingSlashAndScheme()
$matcher->match('/foo');
}
+ public function testSlashAndVerbPrecedenceWithRedirection()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('post')));
+ $coll->add('b', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('get')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $expected = array(
+ '_route' => 'b',
+ 'customerId' => '123',
+ );
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons/'));
+
+ $matcher->expects($this->once())->method('redirect')->with('/api/customers/123/contactpersons/')->willReturn(array());
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
+ }
+
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext()));
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
index c1add96271dc2..159f2725867d7 100644
--- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
+++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php
@@ -680,6 +680,59 @@ public function testHostWithDot()
$this->assertEquals('b', $matcher->match('/bar/abc.123')['_route']);
}
+ public function testSlashVariant()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/foo/{bar}', array(), array('bar' => '.*')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals('a', $matcher->match('/foo/')['_route']);
+ }
+
+ public function testSlashVariant2()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/foo/{bar}/', array(), array('bar' => '.*')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertEquals(array('_route' => 'a', 'bar' => 'bar'), $matcher->match('/foo/bar/'));
+ }
+
+ public function testSlashWithVerb()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/{foo}', array(), array(), array(), '', array(), array('put', 'delete')));
+ $coll->add('b', new Route('/bar/'));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $this->assertSame(array('_route' => 'b'), $matcher->match('/bar/'));
+ }
+
+ public function testSlashAndVerbPrecedence()
+ {
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('post')));
+ $coll->add('b', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('get')));
+
+ $matcher = $this->getUrlMatcher($coll);
+ $expected = array(
+ '_route' => 'b',
+ 'customerId' => '123',
+ );
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
+
+ $coll = new RouteCollection();
+ $coll->add('a', new Route('/api/customers/{customerId}/contactpersons/', array(), array(), array(), '', array(), array('get')));
+ $coll->add('b', new Route('/api/customers/{customerId}/contactpersons', array(), array(), array(), '', array(), array('post')));
+
+ $matcher = $this->getUrlMatcher($coll, new RequestContext('', 'POST'));
+ $expected = array(
+ '_route' => 'b',
+ 'customerId' => '123',
+ );
+ $this->assertEquals($expected, $matcher->match('/api/customers/123/contactpersons'));
+ }
+
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return new UrlMatcher($routes, $context ?: new RequestContext());
diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php
index f334a6d5c4a92..82f6123c59a49 100644
--- a/src/Symfony/Component/Security/Http/HttpUtils.php
+++ b/src/Symfony/Component/Security/Http/HttpUtils.php
@@ -59,7 +59,7 @@ public function __construct(UrlGeneratorInterface $urlGenerator = null, $urlMatc
*/
public function createRedirectResponse(Request $request, $path, $status = 302)
{
- if (null !== $this->domainRegexp && preg_match('#^https?://[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) {
+ if (null !== $this->domainRegexp && preg_match('#^https?:[/\\\\]{2,}+[^/]++#i', $path, $host) && !preg_match(sprintf($this->domainRegexp, preg_quote($request->getHttpHost())), $host[0])) {
$path = '/';
}
diff --git a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
index 28b242995d4bf..a0d6f79714414 100644
--- a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
@@ -54,14 +54,28 @@ public function testCreateRedirectResponseWithRequestsDomain()
$this->assertTrue($response->isRedirect('http://localhost/blog'));
}
- public function testCreateRedirectResponseWithBadRequestsDomain()
+ /**
+ * @dataProvider badRequestDomainUrls
+ */
+ public function testCreateRedirectResponseWithBadRequestsDomain($url)
{
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
- $response = $utils->createRedirectResponse($this->getRequest(), 'http://pirate.net/foo');
+ $response = $utils->createRedirectResponse($this->getRequest(), $url);
$this->assertTrue($response->isRedirect('http://localhost/'));
}
+ public function badRequestDomainUrls()
+ {
+ return array(
+ array('http://pirate.net/foo'),
+ array('http:\\\\pirate.net/foo'),
+ array('http:/\\pirate.net/foo'),
+ array('http:\\/pirate.net/foo'),
+ array('http://////pirate.net/foo'),
+ );
+ }
+
public function testCreateRedirectResponseWithProtocolRelativeTarget()
{
$utils = new HttpUtils($this->getUrlGenerator(), null, '#^https?://%s$#i');
diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
index b2ebe97bd57e9..08380ddfe9e08 100644
--- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php
@@ -55,7 +55,8 @@ public function normalize($object, $format = null, array $context = array())
$timezone = $this->getTimezone($context);
if (null !== $timezone) {
- $object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone);
+ $object = clone $object;
+ $object = $object->setTimezone($timezone);
}
return $object->format($format);
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
index 178519b30e687..99b224996cb1b 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php
@@ -78,6 +78,82 @@ public function normalizeUsingTimeZonePassedInContextProvider()
yield array('2016-12-01T09:00:00+09:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan'));
}
+ /**
+ * @dataProvider normalizeUsingTimeZonePassedInContextAndExpectedFormatWithMicrosecondsProvider
+ */
+ public function testNormalizeUsingTimeZonePassedInContextAndFormattedWithMicroseconds($expected, $expectedFormat, $input, $timezone)
+ {
+ $this->assertSame(
+ $expected,
+ $this->normalizer->normalize(
+ $input,
+ null,
+ array(
+ DateTimeNormalizer::TIMEZONE_KEY => $timezone,
+ DateTimeNormalizer::FORMAT_KEY => $expectedFormat,
+ )
+ )
+ );
+ }
+
+ public function normalizeUsingTimeZonePassedInContextAndExpectedFormatWithMicrosecondsProvider()
+ {
+ yield array(
+ '2018-12-01T18:03:06.067634',
+ 'Y-m-d\TH:i:s.u',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ null,
+ );
+
+ yield array(
+ '2018-12-01T18:03:06.067634',
+ 'Y-m-d\TH:i:s.u',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('UTC'),
+ );
+
+ yield array(
+ '2018-12-01T19:03:06.067634+01:00',
+ 'Y-m-d\TH:i:s.uP',
+ \DateTimeImmutable::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('Europe/Rome'),
+ );
+
+ yield array(
+ '2018-12-01T20:03:06.067634+02:00',
+ 'Y-m-d\TH:i:s.uP',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('Europe/Kiev'),
+ );
+
+ yield array(
+ '2018-12-01T21:03:06.067634',
+ 'Y-m-d\TH:i:s.u',
+ \DateTime::createFromFormat(
+ 'Y-m-d\TH:i:s.u',
+ '2018-12-01T18:03:06.067634',
+ new \DateTimeZone('UTC')
+ ),
+ new \DateTimeZone('Europe/Moscow'),
+ );
+ }
+
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
* @expectedExceptionMessage The object must implement the "\DateTimeInterface".
diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php
index 57ece5ba37259..8651913c3e7f3 100644
--- a/src/Symfony/Component/Validator/ConstraintViolation.php
+++ b/src/Symfony/Component/Validator/ConstraintViolation.php
@@ -79,13 +79,13 @@ public function __toString()
}
$propertyPath = (string) $this->propertyPath;
- $code = $this->code;
+ $code = (string) $this->code;
if ('' !== $propertyPath && '[' !== $propertyPath[0] && '' !== $class) {
$class .= '.';
}
- if (!empty($code)) {
+ if ('' !== $code) {
$code = ' (code '.$code.')';
}
diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
index 544c82f6c074b..e4f7df1757da6 100644
--- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
+++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
@@ -112,7 +112,7 @@ public function getValidator();
* Returns the currently validated object.
*
* If the validator is currently validating a class constraint, the
- * object of that class is returned. If it is a validating a property or
+ * object of that class is returned. If it is validating a property or
* getter constraint, the object that the property/getter belongs to is
* returned.
*
diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php
index cef4782e0f82d..edaa7fa50d6b0 100644
--- a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php
+++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php
@@ -53,4 +53,59 @@ public function testToStringHandlesArrayRoots()
$this->assertSame($expected, (string) $violation);
}
+
+ public function testToStringHandlesCodes()
+ {
+ $violation = new ConstraintViolation(
+ '42 cannot be used here',
+ 'this is the message template',
+ array(),
+ array('some_value' => 42),
+ 'some_value',
+ null,
+ null,
+ 0
+ );
+
+ $expected = <<<'EOF'
+Array.some_value:
+ 42 cannot be used here (code 0)
+EOF;
+
+ $this->assertSame($expected, (string) $violation);
+ }
+
+ public function testToStringOmitsEmptyCodes()
+ {
+ $expected = <<<'EOF'
+Array.some_value:
+ 42 cannot be used here
+EOF;
+
+ $violation = new ConstraintViolation(
+ '42 cannot be used here',
+ 'this is the message template',
+ array(),
+ array('some_value' => 42),
+ 'some_value',
+ null,
+ null,
+ null
+ );
+
+ $this->assertSame($expected, (string) $violation);
+
+ $violation = new ConstraintViolation(
+ '42 cannot be used here',
+ 'this is the message template',
+ array(),
+ array('some_value' => 42),
+ 'some_value',
+ null,
+ null,
+ ''
+ );
+
+ $this->assertSame($expected, (string) $violation);
+ }
}