8000 [Form] Add `MultiStepType` by silasjoisten · Pull Request #59548 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Form] Add MultiStepType #59548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
Next Next commit
Enhancement: Introduce MultiStepType
  • Loading branch information
silasjoisten committed Jan 18, 2025
commit e83ec2a6ea8cfb69d486d92c49cbcc98fd4d848f
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

/*
* 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\Extension\Core\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* @author Silas Joisten <silasjoisten@proton.me>
* @author Patrick Reimers <preimers@pm.me>
* @author Jules Pietri <jules@heahprod.com>
*/
final class MultiStepType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setDefault('current_step_name', static function (Options $options): string {
return array_key_first($options['steps']);
})
->setRequired('steps');
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$currentStep = $options['steps'][$options['current_step_name']];

if (\is_callable($currentStep)) {
$currentStep($builder, $options);
} elseif(\is_string($currentStep)) {
if (!class_exists($currentStep) || !is_a($currentStep, AbstractType::class)) {
throw new \InvalidArgumentException(sprintf('The form class "%s" does not exist.', $currentStep));
}

$builder->add($options['current_step_name'], $currentStep, $options);
}
}

public function buildView(FormView $view, FormInterface $form, array $options): void
{
$view->vars['current_step_name'] = $options['current_step_name'];
$view->vars['steps_names'] = array_keys($options['steps']);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?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\Extension\Core\Type;

use Symfony\Component\Form\Extension\Core\Type\MultiStepType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\Tests\Fixtures\AuthorType;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;

/**
* @author Silas Joisten <silasjoisten@proton.me>
*/
final class MultiStepTypeTest extends TypeTestCase
{
public function testConfigureOptionsWithoutStepsThrowsException(): void
{
self::expectException(MissingOptionsException::class);

$this->factory->create(MultiStepType::class);
}

public function testConfigureOptionsWithStepsSetsDefaultForCurrentStepName(): void
{
$form = $this->factory->create(MultiStepType::class, [], [
'steps' => [
'general' => static function (): void {},
'contact' => static function (): void {},
'newsletter' => static function (): void {},
],
]);

self::assertSame('general', $form->createView()->vars['current_step_name']);
}

public function testBuildViewHasStepNames(): void
{
$form = $this->factory->create(MultiStepType::class, [], [
'steps' => [
'general' => static function (): void {},
'contact' => static function (): void {},
'newsletter' => static function (): void {},
],
]);

self::assertSame(['general', 'contact', 'newsletter'], $form->createView()->vars['steps_names']);
}

public function testFormOnlyHasCurrentStepForm(): void
{
$form = $this->factory->create(MultiStepType::class, [], [
'steps' => [
'general' => static function (FormBuilderInterface $builder): void {
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class);
},
'contact' => static function (FormBuilderInterface $builder): void {
$builder
->add('address', TextType::class)
->add('city', TextType::class);
},
'newsletter' => static function (): void {},
],
]);

self::assertArrayHasKey('firstName', $form->createView()->children);
self::assertArrayHasKey('lastName', $form->createView()->children);
self::assertArrayNotHasKey('address', $form->createView()->children);
self::assertArrayNotHasKey('city', $form->createView()->children);
}

public function testFormStepCanBeClassString(): void
{
$form = $this->factory->create(MultiStepType::class, ['current_step_name' => 'author'], [
'steps' => [
'general' => static function (FormBuilderInterface $builder): void {
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class);
},
'contact' => static function (FormBuilderInterface $builder): void {
$builder
->add('address', TextType::class)
->add('city', TextType::class);
},
'author' => AuthorType::class,
],
]);

self::assertArrayHasKey('author', $form->createView()->children);
}

public function testFormStepWithNormalStringWillThrowException(): void
{
self::expectException(\InvalidArgumentException::class);

$this->factory->create(MultiStepType::class, ['current_step_name' => 'author'], [
'steps' => [
'general' => static function (FormBuilderInterface $builder): void {
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class);
},
'contact' => static function (FormBuilderInterface $builder): void {
$builder
->add('address', TextType::class)
->add('city', TextType::class);
},
'author' => 'hello there',
],
]);
}

public function testFormStepWithClassStringNotExtendingAbstractTypeWillThrowException(): void
{
self::expectException(\InvalidArgumentException::class);

$this->factory->create(MultiStepType::class, ['current_step_name' => 'author'], [
'steps' => [
'general' => static function (FormBuilderInterface $builder): void {
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class);
},
'contact' => static function (FormBuilderInterface $builder): void {
$builder
->add('address', TextType::class)
->add('city', TextType::class);
},
'author' => \stdClass::class,
],
]);
}
}
0