Skip to content

Commit 2cfe438

Browse files
committed
[Form] Add AsFormType attribute to create FormType directly on model classes
1 parent 298e56a commit 2cfe438

File tree

31 files changed

+1074
-6
lines changed

31 files changed

+1074
-6
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableI
245245
->info('Form configuration')
246246
->{$enableIfStandalone('symfony/form', Form::class)}()
247247
->children()
248+
->booleanNode('use_attribute')
249+
->defaultFalse()
250+
->end()
248251
->arrayNode('csrf_protection')
249252
->treatFalseLike(['enabled' => false])
250253
->treatTrueLike(['enabled' => true])

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
use Symfony\Component\Filesystem\Filesystem;
8383
use Symfony\Component\Finder\Finder;
8484
use Symfony\Component\Finder\Glob;
85+
use Symfony\Component\Form\Attribute\AsFormType;
8586
use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension;
8687
use Symfony\Component\Form\Form;
8788
use Symfony\Component\Form\FormTypeExtensionInterface;
@@ -891,6 +892,18 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont
891892
$container->setParameter('form.type_extension.csrf.enabled', false);
892893
}
893894

895+
if ($config['form']['use_attribute']) {
896+
$loader->load('form_metadata.php');
897+
898+
$container->registerAttributeForAutoconfiguration(AsFormType::class, static function (ChildDefinition $definition, AsFormType $attribute, \ReflectionClass $ref) {
899+
$definition
900+
->addTag('container.excluded.form.metadata.form_type', ['class_name' => $ref->getName()])
901+
->addTag('container.excluded')
902+
->setAbstract(true)
903+
;
904+
});
905+
}
906+
894907
if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) {
895908
$container->removeDefinition('form.type_extension.upload.validator');
896909
}

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

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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\Extension\Metadata\MetadataExtension;
15+
use Symfony\Component\Form\Metadata\Loader\AttributeLoader;
16+
17+
return static function (ContainerConfigurator $container) {
18+
$container->services()
19+
->set('form.metadata.attribute_loader', AttributeLoader::class)
20+
21+
->alias('form.metadata.default_loader', 'form.metadata.attribute_loader')
22+
;
23+
};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ protected static function getBundleDefaultConfig()
751751
'field_attr' => ['data-controller' => 'csrf-protection'],
752752
'token_id' => null,
753753
],
754+
'use_attribute' => false,
754755
],
755756
'esi' => ['enabled' => false],
756757
'ssi' => ['enabled' => false],

src/Symfony/Component/Form/AbstractType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,9 @@ public function getBlockPrefix()
6363
{
6464
return StringUtil::fqcnToBlockPrefix(static::class) ?: '';
6565
}
66+
67+
final public function getClassName(): string
68+
{
69+
return static::class;
70+
}
6671
}
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+
final readonly class AsFormType
21+
{
22+
/**
23+
* @param array<string, mixed> $options
24+
*/
25+
public function __construct(
26+
private array $options = [],
27+
) {
28+
}
29+
30+
public function getOptions(): array
31+
{
32+
return $this->options;
33+
}
34+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
final readonly class Type
21+
{
22+
/**
23+
* @param class-string|null $type
24+
* @param array<string, mixed> $options
25+
*/
26+
public function __construct(
27+
private ?string $type = null,
28+
private array $options = [],
29+
) {
30+
}
31+
32+
/**
33+
* @return array<string, mixed>
34+
*/
35+
public function getOptions(): array
36+
{
37+
return $this->options;
38+
}
39+
40+
/**
41+
* @return class-string|null
42+
*/
43+
public function getType(): ?string
44+
{
45+
return $this->type;
46+
}
47+
}

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'];

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy