Skip to content

Commit c3c026f

Browse files
committed
[Form] Add AsFormType attribute to create FormType directly on model classes
1 parent eb611de commit c3c026f

File tree

32 files changed

+1146
-7
lines changed

32 files changed

+1146
-7
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableI
252252
->info('Form configuration')
253253
->{$enableIfStandalone('symfony/form', Form::class)}()
254254
->children()
255+
->booleanNode('use_attribute')
256+
->defaultTrue()
257+
->end()
255258
->arrayNode('csrf_protection')
256259
->treatFalseLike(['enabled' => false])
257260
->treatTrueLike(['enabled' => true])

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
use Symfony\Component\Filesystem\Filesystem;
7979
use Symfony\Component\Finder\Finder;
8080
use Symfony\Component\Finder\Glob;
81+
use Symfony\Component\Form\Attribute\AsFormType;
8182
use Symfony\Component\Form\Form;
8283
use Symfony\Component\Form\FormTypeExtensionInterface;
8384
use Symfony\Component\Form\FormTypeGuesserInterface;
@@ -849,6 +850,14 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont
849850
$container->setParameter('form.type_extension.csrf.enabled', false);
850851
}
851852

853+
if ($config['form']['use_attribute']) {
854+
$loader->load('form_metadata.php');
855+
856+
$container->registerAttributeForAutoconfiguration(AsFormType::class, static function (ChildDefinition $definition) {
857+
$definition->addResourceTag('form.metadata.form_type');
858+
});
859+
}
860+
852861
if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) {
853862
$container->removeDefinition('form.type_extension.upload.validator');
854863
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
[], // All type extensions are stored here by FormPass
337337
[], // All type guessers are stored here by FormPass
338338
service('debug.file_link_formatter')->nullOnInvalid(),
339+
[], // All metadata form types are stored here by FormPass
339340
])
340341
->tag('console.command')
341342

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\Form\Metadata\Loader\AttributeLoader;
15+
16+
return static function (ContainerConfigurator $container) {
17+
$container->services()
18+
->set('form.metadata.attribute_loader', AttributeLoader::class)
19+
20+
->alias('form.metadata.default_loader', 'form.metadata.attribute_loader')
21+
;
22+
};

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ protected static function getBundleDefaultConfig()
728728
'field_attr' => ['data-controller' => 'csrf-protection'],
729729
'token_id' => null,
730730
],
731+
'use_attribute' => true,
731732
],
732733
'esi' => ['enabled' => false],
733734
'ssi' => ['enabled' => false],
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Attribute;
13+
14+
/**
15+
* Register a model class (e.g. DTO, entity, model, etc...) as a FormType.
16+
*
17+
* @author Benjamin Georgeault <git@wedgesama.fr>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS)]
20+
class AsFormType
21+
{
22+
/**
23+
* @param array<string, mixed> $options
24+
*/
25+
public function __construct(
26+
private readonly array $options = [],
27+
) {
28+
}
29+
30+
public function getOptions(): array
31+
{
32+
return $this->options;
33+
}
34+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Attribute;
13+
14+
/**
15+
* Add an AsFormType class property as a FormType's field.
16+
*
17+
* @author Benjamin Georgeault <git@wedgesama.fr>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
20+
class Type
21+
{
22+
/**
23+
* @param class-string|null $type the FormType class name to use for this field
24+
* @param array<string, mixed> $options your form options
25+
* @param string|null $name change the form view field's name
26+
*/
27+
public function __construct(
28+
private ?string $type = null,
29+
private array $options = [],
30+
private ?string $name = null,
31+
) {
32+
}
33+
34+
/**
35+
* @return array<string, mixed>
36+
*/
37+
public function getOptions(): array
38+
{
39+
return $this->options;
40+
}
41+
42+
/**
43+
* @return class-string|null
44+
*/
45+
public function getType(): ?string
46+
{
47+
return $this->type;
48+
}
49+
50+
public function getName(): ?string
51+
{
52+
return $this->name;
53+
}
54+
}

src/Symfony/Component/Form/Command/DebugCommand.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function __construct(
4242
private array $extensions = [],
4343
private array $guessers = [],
4444
private ?FileLinkFormatter $fileLinkFormatter = null,
45+
private array $metadataTypes = [],
4546
) {
4647
parent::__construct();
4748
}
@@ -95,6 +96,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9596
$object = null;
9697
$options['core_types'] = $this->getCoreTypes();
9798
$options['service_types'] = array_values(array_diff($this->types, $options['core_types']));
99+
$options['metadata_types'] = $this->metadataTypes;
98100
if ($input->getOption('show-deprecated')) {
99101
$options['core_types'] = $this->filterTypesByDeprecated($options['core_types']);
100102
$options['service_types'] = $this->filterTypesByDeprecated($options['service_types']);
@@ -150,7 +152,7 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, strin
150152
if (0 === $count = \count($classes)) {
151153
$message = \sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces));
152154

153-
$allTypes = array_merge($this->getCoreTypes(), $this->types);
155+
$allTypes = array_merge($this->getCoreTypes(), $this->types, $this->metadataTypes);
154156
if ($alternatives = $this->findAlternatives($shortClassName, $allTypes)) {
155157
if (1 === \count($alternatives)) {
156158
$message .= "\n\nDid you mean this?\n ";
@@ -238,7 +240,7 @@ private function findAlternatives(string $name, array $collection): array
238240
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
239241
{
240242
if ($input->mustSuggestArgumentValuesFor('class')) {
241-
$suggestions->suggestValues(array_merge($this->getCoreTypes(), $this->types));
243+
$suggestions->suggestValues(array_merge($this->getCoreTypes(), $this->types, $this->metadataTypes));
242244

243245
return;
244246
}

src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ protected function describeDefaults(array $options): void
2525
{
2626
$data['builtin_form_types'] = $options['core_types'];
2727
$data['service_form_types'] = $options['service_types'];
28+
$data['metadata_form_types'] = $options['metadata_types'];
2829
if (!$options['show_deprecated']) {
2930
$data['type_extensions'] = $options['extensions'];
3031
$data['type_guessers'] = $options['guessers'];

src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ protected function describeDefaults(array $options): void
4444
$this->output->listing(array_map($this->formatClassLink(...), $options['service_types']));
4545
}
4646

47+
if ($options['metadata_types']) {
48+
$this->output->section('Metadata form types');
49+
$this->output->listing(array_map($this->formatClassLink(...), $options['metadata_types']));
50+
}
51+
4752
if (!$options['show_deprecated']) {
4853
if ($options['extensions']) {
4954
$this->output->section('Type extensions');

0 commit comments

Comments
 (0)