Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony Console Form Laravel Package

matthiasnoback/symfony-console-form

Symfony Console Form lets you reuse Symfony's Form component to build interactive console commands, rendering fields as questions and validating user input. It simplifies collecting multiple values with form types, constraints, and data mapping in CLI applications.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with Composer:

    composer require matthiasnoback/symfony-console-form
    
  • In a Symfony application, register Matthias\SymfonyConsoleForm\Bundle\SymfonyConsoleFormBundle in config/bundles.php if your app does not auto-register bundles. In a standalone Console application, wire the Symfony Form factory and the package’s FormHelper manually.

  • Start with one small, flat form: a text field, a choice field, and a checkbox. Do not begin with nested forms or collections.

  • The first useful use case is an interactive admin/provisioning command where a developer would otherwise write a chain of $io->ask() calls:

    final class RegistrationType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options): void
        {
            $builder
                ->add('email', EmailType::class)
                ->add('role', ChoiceType::class, [
                    'choices' => [
                        'Administrator' => 'admin',
                        'Regular user' => 'user',
                    ],
                ])
                ->add('sendWelcomeEmail', CheckboxType::class, [
                    'required' => false,
                ]);
        }
    }
    
    use Matthias\SymfonyConsoleForm\Console\Command\FormBasedCommand;
    
    final class RegisterUserCommand extends FormBasedCommand
    {
        protected function configure(): void
        {
            $this->setName('app:register-user');
        }
    
        protected function getFormType(): string
        {
            return RegistrationType::class;
        }
    
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $data = $this->getFormData();
    
            // $data is an array unless the form defines a data_class.
            // Persist the user, dispatch a message, etc.
    
            return self::SUCCESS;
        }
    }
    
  • Look first at FormBasedCommand, FormHelper, SymfonyStyleFormatter, and the package tests. The tests are the most reliable documentation for supported field types and exact interaction behavior.

  • Prefer a DTO with data_class when the command has more than two or three fields. It gives execute() a typed object instead of an untyped array.

Implementation Patterns

  • Use the form type as the single source of truth. Share the same FormType between the web form and the console command. Constraints, choices, labels, and transformation logic then stay consistent. Put validation constraints on the DTO rather than duplicating them in the command.
  • Map to a DTO, not an array. Configure data_class and read typed properties/getters in execute():
    protected function getFormOptions(): array
    {
        return [
            'data_class' => RegistrationData::class,
            'csrf_protection' => false,
        ];
    }
    
  • Prefill from command options or environment. Use getFormOptions() with a data object, or form field defaults, so the command can be used repeatedly with sensible defaults:
    protected function getFormOptions(): array
    {
        return [
            'data' => new RegistrationData(defaultRole: 'user'),
        ];
    }
    
  • Build hybrid commands for automation. A command that is pleasant interactively is often painful in CI. Add InputOptions for the critical fields and skip the form when those options are supplied. If the base class always prompts, either override interact() carefully or use the package’s FormHelper directly after checking $input->isInteractive() and the supplied options.
  • Keep complex input out of interactive questions. For large collections, bulk imports, or machine-generated payloads, accept a JSON file/option and use the form only for small interactive confirmations. Symfony Console choice questions do not scale to hundreds of options.
  • Use ChoiceType with a curated list, not EntityType, unless you really need it. EntityType is web/Doctrine-oriented and can load too much data or behave unexpectedly in a console context. For selecting an existing entity, consider an ID argument plus a small confirmation form.
  • Reuse the helper outside FormBasedCommand. The package is essentially a bridge between Console questions and Symfony Form. If a command needs custom control flow — conditional prompts, retries, or non-interactive fallbacks — inject/use the FormHelper directly and call its form-asking method instead of forcing everything through the base command.
  • Separate input collection from side effects. Put “create user”, “import data”, “provision resource” logic in a service. The command should collect form data, confirm with SymfonyStyle, call the service, and render the result. This makes the workflow testable without simulating every console question.
  • Test the form type and the service separately. Use CommandTester::setInputs() for one happy-path command test, but do not make it the primary test strategy: question order is coupled to form field order and becomes brittle.
  • Use SymfonyStyle after the form. The form handles structured input; SymfonyStyle is still the right tool for section output, confirmations, tables, and progress bars after submission.

Gotchas and Tips

  • interact() is skipped with --no-interaction. Symfony Console does not call interact() when input is non-interactive. If FormBasedCommand collects data there, getFormData() may be empty or null in CI, cron, or deployment scripts. Always guard and provide an option-driven or argument-driven path.
  • Do not assume the web validation pipeline runs. Symfony Form submission and validation are separate concerns. Verify whether your version of the package calls validation for the mapped object; if not, validate the DTO explicitly with the Validator before executing side effects.
  • Disable CSRF for console forms. There is no request/session in a normal command. Leaving CSRF enabled causes confusing failures or unnecessary configuration.
  • Not every Symfony form type maps cleanly to a console question. Scalar types, ChoiceType, and checkboxes are straightforward. CollectionType, RepeatedType, FileType, and EntityType often need custom handling or should be replaced with simpler command options.
  • Question order matters in tests. CommandTester::setInputs() feeds answers in the order questions are asked. Adding a field to the form can silently break tests. Prefer testing the execution service and using one narrow command test for smoke coverage.
  • Watch out for large choice lists. Symfony’s console choice question renders all choices. A ChoiceType backed by thousands of entities is unusable. Use an ID option, a search step, or a small filtered list.
  • Rendering form errors can be rough. Console form error output is less polished than web form rendering. Confirm behavior with invalid input, and use -vvv when debugging exceptions. If the default output is insufficient, customize the question/formatter layer rather than catching and hiding errors.
  • Check bundle and command registration. The command must be tagged as console.command in a Symfony app, and any form type that depends on services should itself be a service. Clear the cache after changing bundle or service configuration.
  • Mind Symfony version constraints. This is a small package with a narrow integration surface. Check composer why-not against your Symfony Console/Form versions before upgrading a major Symfony release, and read the package tests for supported behavior.
  • Extension points are limited, so wrap rather than fork. Customize through getFormOptions(), form type extensions, a custom DTO, or a wrapper service around the form helper. Forking the package for one command usually creates more maintenance than writing a small console-specific input layer.
  • Low dependent count means fewer examples. With a small ecosystem, expect to read the source and tests when behavior is unclear. Budget time for a spike on one real command before standardizing the pattern across a codebase.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin