Files
archived-form/Tests/DependencyInjection/FormPassTest.php
Nicolas Grekas dfe3a515cd Merge branch '6.4' into 7.4
* 6.4:
  [Form] Add resource tracking for type extension classes in FormPass
2026-02-23 13:14:34 +01:00

402 lines
15 KiB
PHP

<?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\DependencyInjection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Command\DebugCommand;
use Symfony\Component\Form\DependencyInjection\FormPass;
use Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension;
use Symfony\Component\Form\FormRegistry;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormPassTest extends TestCase
{
public function testDoNothingIfFormExtensionNotLoaded()
{
$container = $this->createContainerBuilder();
$container->compile();
$this->assertFalse($container->hasDefinition('form.extension'));
}
public function testDoNothingIfDebugCommandNotLoaded()
{
$container = $this->createContainerBuilder();
$container->compile();
$this->assertFalse($container->hasDefinition('console.command.form_debug'));
}
public function testAddTaggedTypes()
{
$container = $this->createContainerBuilder();
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type')->setPublic(true);
$container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type')->setPublic(true);
$container->compile();
$extDefinition = $container->getDefinition('form.extension');
$locator = $extDefinition->getArgument(0);
$this->assertTrue($locator->isPrivate());
$this->assertEquals(
(new Definition(ServiceLocator::class, [[
__CLASS__.'_Type1' => new ServiceClosureArgument(new Reference('my.type1')),
__CLASS__.'_Type2' => new ServiceClosureArgument(new Reference('my.type2')),
]]))->addTag('container.service_locator'),
$locator
);
}
public function testAddTaggedTypesToDebugCommand()
{
$container = $this->createContainerBuilder();
$container->register('form.registry', FormRegistry::class);
$commandDefinition = new Definition(DebugCommand::class, [new Reference('form.registry')]);
$commandDefinition->setPublic(true);
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->setDefinition('console.command.form_debug', $commandDefinition);
$container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type')->setPublic(true);
$container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type')->setPublic(true);
$container->compile();
$cmdDefinition = $container->getDefinition('console.command.form_debug');
$this->assertEquals(
[
'Symfony\Component\Form\Extension\Core\Type',
__NAMESPACE__,
],
$cmdDefinition->getArgument(1)
);
}
public function testAddTaggedTypesToCsrfTypeExtension()
{
$container = $this->createContainerBuilder();
$container->register('form.registry', FormRegistry::class);
$container->register('form.type_extension.csrf', FormTypeCsrfExtension::class)
->setArguments([null, true, '_token', null, 'validator.translation_domain', null, [], null])
->setPublic(true);
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type', ['csrf_token_id' => 'the_token_id']);
$container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type');
$container->compile();
$csrfDefinition = $container->getDefinition('form.type_extension.csrf');
$this->assertSame([__CLASS__.'_Type1' => 'the_token_id'], $csrfDefinition->getArgument(7));
}
#[DataProvider('addTaggedTypeExtensionsDataProvider')]
public function testAddTaggedTypeExtensions(array $extensions, array $expectedRegisteredExtensions, array $parameters = [])
{
$container = $this->createContainerBuilder();
foreach ($parameters as $name => $value) {
$container->setParameter($name, $value);
}
$container->setDefinition('form.extension', $this->createExtensionDefinition());
foreach ($extensions as $serviceId => $config) {
$container->register($serviceId, $config['class'])->addTag('form.type_extension', $config['tag']);
}
$container->compile();
$extDefinition = $container->getDefinition('form.extension');
$this->assertEquals($expectedRegisteredExtensions, $extDefinition->getArgument(1));
}
public static function addTaggedTypeExtensionsDataProvider()
{
return [
[
[
Type1TypeExtension::class => [
'class' => Type1TypeExtension::class,
'tag' => ['extended_type' => 'type1'],
],
Type1Type2TypeExtension::class => [
'class' => Type1Type2TypeExtension::class,
'tag' => ['extended_type' => 'type2'],
],
],
[
'type1' => new IteratorArgument([new Reference(Type1TypeExtension::class)]),
'type2' => new IteratorArgument([new Reference(Type1Type2TypeExtension::class)]),
],
],
[
[
Type1TypeExtension::class => [
'class' => Type1TypeExtension::class,
'tag' => [],
],
Type1Type2TypeExtension::class => [
'class' => Type1Type2TypeExtension::class,
'tag' => [],
],
],
[
'type1' => new IteratorArgument([
new Reference(Type1TypeExtension::class),
new Reference(Type1Type2TypeExtension::class),
]),
'type2' => new IteratorArgument([new Reference(Type1Type2TypeExtension::class)]),
],
],
[
[
'my.type_extension1' => [
'class' => Type1TypeExtension::class,
'tag' => ['extended_type' => 'type1', 'priority' => 1],
],
'my.type_extension2' => [
'class' => Type1TypeExtension::class,
'tag' => ['extended_type' => 'type1', 'priority' => 2],
],
'my.type_extension3' => [
'class' => Type1TypeExtension::class,
'tag' => ['extended_type' => 'type1', 'priority' => -1],
],
'my.type_extension4' => [
'class' => Type2TypeExtension::class,
'tag' => ['extended_type' => 'type2', 'priority' => 2],
],
'my.type_extension5' => [
'class' => Type2TypeExtension::class,
'tag' => ['extended_type' => 'type2', 'priority' => 1],
],
'my.type_extension6' => [
'class' => Type2TypeExtension::class,
'tag' => ['extended_type' => 'type2', 'priority' => 1],
],
],
[
'type1' => new IteratorArgument([
new Reference('my.type_extension2'),
new Reference('my.type_extension1'),
new Reference('my.type_extension3'),
]),
'type2' => new IteratorArgument([
new Reference('my.type_extension4'),
new Reference('my.type_extension5'),
new Reference('my.type_extension6'),
]),
],
],
[
[
'my.type_extension1' => [
'class' => '%type1_extension_class%',
'tag' => ['extended_type' => 'type1'],
],
'my.type_extension2' => [
'class' => '%type1_extension_class%',
'tag' => [],
],
],
[
'type1' => new IteratorArgument([
new Reference('my.type_extension1'),
new Reference('my.type_extension2'),
]),
],
[
'type1_extension_class' => Type1TypeExtension::class,
],
],
];
}
public function testAddTaggedFormTypeExtensionWithoutExtendingAnyType()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The getExtendedTypes() method for service "my.type_extension" does not return any extended types.');
$container = $this->createContainerBuilder();
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->register('my.type_extension', WithoutExtendedTypesTypeExtension::class)
->setPublic(true)
->addTag('form.type_extension');
$container->compile();
}
public function testAddTaggedGuessers()
{
$container = $this->createContainerBuilder();
$definition1 = new Definition('stdClass');
$definition1->addTag('form.type_guesser');
$definition2 = new Definition('stdClass');
$definition2->addTag('form.type_guesser');
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->setDefinition('my.guesser1', $definition1)->setPublic(true);
$container->setDefinition('my.guesser2', $definition2)->setPublic(true);
$container->compile();
$extDefinition = $container->getDefinition('form.extension');
$this->assertEquals(
new IteratorArgument([
new Reference('my.guesser1'),
new Reference('my.guesser2'),
]),
$extDefinition->getArgument(2)
);
}
public function testTypeExtensionClassIsTrackedAsResource()
{
$container = $this->createContainerBuilder();
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->register('my.type_extension', Type1TypeExtension::class)
->addTag('form.type_extension');
$container->compile();
$resources = array_map('strval', $container->getResources());
$this->assertContains('reflection.'.Type1TypeExtension::class, $resources);
}
#[DataProvider('privateTaggedServicesProvider')]
public function testPrivateTaggedServices($id, $class, $tagName, callable $assertion, array $tagAttributes = [])
{
$formPass = new FormPass();
$container = new ContainerBuilder();
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->register($id, $class)->setPublic(false)->addTag($tagName, $tagAttributes);
$formPass->process($container);
$assertion($container);
}
public static function privateTaggedServicesProvider()
{
return [
[
'my.type',
'stdClass',
'form.type',
function (ContainerBuilder $container) {
$formTypes = $container->getDefinition('form.extension')->getArgument(0);
self::assertInstanceOf(Reference::class, $formTypes);
$locator = $container->getDefinition((string) $formTypes);
$expectedLocatorMap = [
'stdClass' => new ServiceClosureArgument(new Reference('my.type')),
];
self::assertInstanceOf(Definition::class, $locator);
self::assertEquals($expectedLocatorMap, $locator->getArgument(0));
},
],
[
'my.type_extension',
Type1TypeExtension::class,
'form.type_extension',
function (ContainerBuilder $container) {
self::assertEquals(
['Symfony\Component\Form\Extension\Core\Type\FormType' => new IteratorArgument([new Reference('my.type_extension')])],
$container->getDefinition('form.extension')->getArgument(1)
);
},
['extended_type' => 'Symfony\Component\Form\Extension\Core\Type\FormType'],
],
['my.guesser', 'stdClass', 'form.type_guesser', function (ContainerBuilder $container) {
self::assertEquals(new IteratorArgument([new Reference('my.guesser')]), $container->getDefinition('form.extension')->getArgument(2));
}],
];
}
private function createExtensionDefinition()
{
$definition = new Definition('Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension');
$definition->setPublic(true);
$definition->setArguments([
[],
[],
new IteratorArgument([]),
]);
return $definition;
}
private function createContainerBuilder()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new FormPass());
return $container;
}
}
class Type1TypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return ['type1'];
}
}
class Type2TypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return ['type2'];
}
}
class Type1Type2TypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
yield 'type1';
yield 'type2';
}
}
class WithoutExtendedTypesTypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [];
}
}