Repository: symfony/symfony
File: src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php (PHP)

Code before:

use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormRendererInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class FormTypeValidatorExtension extends BaseValidatorExtension
{
    private ViolationMapperInterface $violationMapper;

    public function __construct(
        private ValidatorInterface $validator,
        private bool $legacyErrorMessages = true,
        ?FormRendererInterface $formRenderer = null,
        ?TranslatorInterface $translator = null,
        ?ViolationMapperInterface $violationMapper = null,
    ) {
        if (null === $violationMapper) {
            trigger_deprecation('symfony/form', '8.1', \sprintf(
                'Not passing the "%s" in constructor\'s arguments is deprecated. It will be the default in Symfony 9.0.',
                ViolationMapperInterface::class
            ));
        }
        $this->violationMapper = $violationMapper ?? new ViolationMapper($formRenderer, $translator);
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper));
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        // Constraint should always be converted to an array
        $constraintsNormalizer = static fn (Options $options, $constraints) => \is_object($constraints) ? [$constraints] : (array) $constraints;

        $resolver->setDefaults([
            'error_mapping' => [],
            'constraints' => [],

Review comment (suggestion):
adding it *after* the form renderer and the translator does not make sense, because it means we cannot remove those parameters from the signature once they become unused. And it also means that once it becomes a mandatory argument in next major version, other parameters will become mandatory as well.
You need to combine the old signature and the new signature instead (which will involve some union types for argument positions that change meaning, and some re-assignment of parameters in the constructor to detect the meaning of those arguments, with deprecations when using them the old way)

Also, as the form renderer and translator were optional before, I'm wondering whether the ViolationMapper should also be optional in the new signature (corresponding to the case where they were omitted)

Code after:

use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormRendererInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class FormTypeValidatorExtension extends BaseValidatorExtension
{
    private readonly ViolationMapperInterface $violationMapper;

    public function __construct(
        private ValidatorInterface $validator,
        bool|ViolationMapperInterface|null $violationMapper = null,
        ?FormRendererInterface $formRenderer = null,
        ?TranslatorInterface $translator = null,
    ) {
        if (\is_bool($violationMapper)) {
            trigger_deprecation('symfony/form', '8.1', \sprintf('Passing a boolean as a second argument of "%s"\'s constructor is deprecated; pass a "%s" instead.', self::class, ViolationMapperInterface::class));
            $violationMapper = null;
        }
        $this->violationMapper = $violationMapper ?? new ViolationMapper($formRenderer, $translator);
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper));
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        // Constraint should always be converted to an array
        $constraintsNormalizer = static fn (Options $options, $constraints) => \is_object($constraints) ? [$constraints] : (array) $constraints;

        $resolver->setDefaults([
            'error_mapping' => [],
            'constraints' => [],
            'invalid_message' => 'This value is not valid.',
            'invalid_message_parameters' => [],
            'allow_extra_fields' => false,