feature #24387 [FORM] Prevent forms from extending itself as a parent (pierredup)

This PR was squashed before being merged into the 3.4 branch (closes #24387).

Discussion
----------

[FORM] Prevent forms from extending itself as a parent

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | N/A
| License       | MIT
| Doc PR        | N/A

If a form type defines itself as a parent, it results in an endless recursive loop in the form registry, so throw an exception instead.

Commits
-------

2ee6fd5 [FORM] Prevent forms from extending itself as a parent
This commit is contained in:
Nicolas Grekas 2017-10-10 12:07:23 +02:00
commit f76ea8016a
6 changed files with 146 additions and 10 deletions

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Form;
use Symfony\Component\Form\Exception\ExceptionInterface;
use Symfony\Component\Form\Exception\LogicException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\InvalidArgumentException;
@ -44,6 +45,8 @@ class FormRegistry implements FormRegistryInterface
*/
private $resolvedTypeFactory;
private $checkedTypes = array();
/**
* @param FormExtensionInterface[] $extensions An array of FormExtensionInterface
* @param ResolvedFormTypeFactoryInterface $resolvedTypeFactory The factory for resolved form types
@ -106,18 +109,29 @@ class FormRegistry implements FormRegistryInterface
$parentType = $type->getParent();
$fqcn = get_class($type);
foreach ($this->extensions as $extension) {
$typeExtensions = array_merge(
$typeExtensions,
$extension->getTypeExtensions($fqcn)
);
if (isset($this->checkedTypes[$fqcn])) {
$types = implode(' > ', array_merge(array_keys($this->checkedTypes), array($fqcn)));
throw new LogicException(sprintf('Circular reference detected for form "%s" (%s).', $fqcn, $types));
}
return $this->resolvedTypeFactory->createResolvedType(
$type,
$typeExtensions,
$parentType ? $this->getType($parentType) : null
);
$this->checkedTypes[$fqcn] = true;
try {
foreach ($this->extensions as $extension) {
$typeExtensions = array_merge(
$typeExtensions,
$extension->getTypeExtensions($fqcn)
);
}
return $this->resolvedTypeFactory->createResolvedType(
$type,
$typeExtensions,
$parentType ? $this->getType($parentType) : null
);
} finally {
unset($this->checkedTypes[$fqcn]);
}
}
/**

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Fixtures;
use Symfony\Component\Form\AbstractType;
class FormWithSameParentType extends AbstractType
{
public function getParent()
{
return self::class;
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Fixtures;
use Symfony\Component\Form\AbstractType;
class RecursiveFormTypeBar extends AbstractType
{
public function getParent()
{
return RecursiveFormTypeBaz::class;
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Fixtures;
use Symfony\Component\Form\AbstractType;
class RecursiveFormTypeBaz extends AbstractType
{
public function getParent()
{
return RecursiveFormTypeFoo::class;
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Fixtures;
use Symfony\Component\Form\AbstractType;
class RecursiveFormTypeFoo extends AbstractType
{
public function getParent()
{
return RecursiveFormTypeBar::class;
}
}

View File

@ -16,6 +16,10 @@ use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\FormTypeGuesserChain;
use Symfony\Component\Form\ResolvedFormType;
use Symfony\Component\Form\ResolvedFormTypeFactoryInterface;
use Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType;
use Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBar;
use Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBaz;
use Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo;
use Symfony\Component\Form\Tests\Fixtures\FooSubType;
use Symfony\Component\Form\Tests\Fixtures\FooType;
use Symfony\Component\Form\Tests\Fixtures\FooTypeBarExtension;
@ -156,6 +160,36 @@ class FormRegistryTest extends TestCase
$this->assertSame($resolvedType, $this->registry->getType(get_class($type)));
}
/**
* @expectedException \Symfony\Component\Form\Exception\LogicException
* @expectedExceptionMessage Circular reference detected for form "Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType" (Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType > Symfony\Component\Form\Tests\Fixtures\FormWithSameParentType).
*/
public function testFormCannotHaveItselfAsAParent()
{
$type = new FormWithSameParentType();
$this->extension2->addType($type);
$this->registry->getType(FormWithSameParentType::class);
}
/**
* @expectedException \Symfony\Component\Form\Exception\LogicException
* @expectedExceptionMessage Circular reference detected for form "Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo" (Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo > Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBar > Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeBaz > Symfony\Component\Form\Tests\Fixtures\RecursiveFormTypeFoo).
*/
public function testRecursiveFormDependencies()
{
$foo = new RecursiveFormTypeFoo();
$bar = new RecursiveFormTypeBar();
$baz = new RecursiveFormTypeBaz();
$this->extension2->addType($foo);
$this->extension2->addType($bar);
$this->extension2->addType($baz);
$this->registry->getType(RecursiveFormTypeFoo::class);
}
/**
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
*/