Skip to content

[make:webhook] Fix the crash under --no-interaction - #1817

Merged
GromNaN merged 1 commit into
symfony:1.xfrom
wachterjohannes:fix/make-webhook-non-interactive
Sep 7, 2026
Merged

GromNaN merged 1 commit into
symfony:1.xfrom
wachterjohannes:fix/make-webhook-non-interactive

Conversation

@wachterjohannes

@wachterjohannes wachterjohannes commented Aug 31, 2026 •

Copy link
Copy Markdown
Member
Q A
Bug fix? yes
New feature? no
Deprecations? no
License MIT

make:webhook already declares its name argument, and already marks it non-interactive, so it looks scriptable. It is not:

$ php bin/console make:webhook remote_service --no-interaction
Typed property Symfony\Bundle\MakerBundle\Maker\MakeWebhook::$name must not be accessed
before initialization

Cause

interact() read the argument and copied it onto the maker, and generate() read it back from there. Command::run() skips interact() under --no-interaction, so the copy never happened. $io was stashed on the maker in the same way.

Fix

generate() reads the argument it was given. The name check that interact() applied moved next to that read, so a name with illegal characters is rejected on both paths rather than only when someone is watching. $io is passed to the one private method that asks anything, and both properties are gone.

symfony/webhook was installed from interact(), which is the one step that does not always run, and the generated request parser extends AbstractRequestParser from that package. The install moved to generate(), which always runs, rather than being called from both: a second composer require in the same process rebuilds the container underneath the running command, and the run then dies on a stale cache file. The one visible change interactively is that the install now happens after the questions instead of before.

Why

Part of a series making the makers usable by coding agents, which cannot answer prompts. Same framing as #1814 and #1815.

@wachterjohannes
wachterjohannes force-pushed the fix/make-webhook-non-interactive branch from 15359ec to f56af5c Compare August 31, 2026 17:37
The name argument was already declared, and already marked
non-interactive, but interact() copied it onto the maker and generate()
read it back from there. Command::run() skips interact() without a
terminal, so the copy never happened:

    $ php bin/console make:webhook remote_service --no-interaction
    Typed property MakeWebhook::$name must not be accessed before
    initialization

generate() now reads the argument it was given. The name check that
interact() applied moved next to it, so a name with illegal characters
is rejected on both paths instead of only when someone is watching.

symfony/webhook was installed from interact(), which is the one place
that does not always run, and the generated parser extends a class from
that package. The install moved to generate(), the step that always
runs, rather than being called from both: a second composer require in
the same process rebuilds the container underneath the command and the
run dies afterwards on a stale cache file.

$io was being stashed on the maker as well. It is passed to the one
private method that asks anything, and the property is gone along with
the one holding the name.
@wachterjohannes
wachterjohannes force-pushed the fix/make-webhook-non-interactive branch from f56af5c to e93df0d Compare September 3, 2026 20:37
@wachterjohannes
wachterjohannes marked this pull request as ready for review September 3, 2026 20:37
@GromNaN

GromNaN commented Sep 7, 2026

Copy link
Copy Markdown
Member

Thank you @wachterjohannes.

@GromNaN
GromNaN merged commit d58c15c into symfony:1.x Sep 7, 2026
9 checks passed
@wachterjohannes
wachterjohannes deleted the fix/make-webhook-non-interactive branch September 7, 2026 08:50
@GromNaN GromNaN added this to the v1.68.0 milestone Sep 7, 2026
GromNaN added a commit that referenced this pull request Sep 10, 2026
…moifr)

This PR was squashed before being merged into the 1.x branch.

Discussion
----------

[make:command] Ask for the arguments and the options

Closes #1699.

The first half of that issue landed with #1746: `make:command` generates an `__invoke()` method. This is the second half, asking for the arguments and the options so the signature comes out ready rather than as a sample to edit.

```
$ php bin/console make:command app:send-report

 New argument name (press <return> to stop adding arguments):
 > recipient
 Type [string]:
 Description [Recipient]: Who to send the report to
 Is it required? (yes/no) [no]: yes

 New argument name (press <return> to stop adding arguments):
 >

 New option name (press <return> to stop adding options):
 > dry-run
 Type [bool]:
 Description [Dry Run]: Do not send anything
```

```php
public function __invoke(
    SymfonyStyle $io,
    #[Argument('Who to send the report to')] string $recipient,
    #[Option('Do not send anything')] bool $dryRun = false,
): int {
```

The name is typed the way it reads on the command line, so `dry-run` becomes `$dryRun`. Besides the scalar types and `array`, the type can be a backed enum.

They can also be passed on the command line (thanks `@GromNaN`). The type defaults to `string` for an argument and `bool` for an option, the short name of an enum is enough when it is unique within `src/`, a `?` after the name makes an argument optional as in a PHP array shape, and passing any of them skips the questions:

```
$ php bin/console make:command app:send-report --argument=recipient --argument=cc?:array --option=dry-run --option=format:Format
```

A few decisions worth flagging:

* **Answering nothing keeps the sample argument and option**, so `--no-interaction`, where `interact()` never runs, and the person who just presses return through the questions still get a command that shows both attributes at work.
* **What the console component would refuse is refused up front.** A required argument after an optional one, any argument after an array one, a parameter named `io` (taken by `SymfonyStyle $io`), an argument and an option sharing a name, and an enum that is not backed all produce a command that fails to load. The questions skip or ask again, and the command line gets an error before anything is written.
* **An enum named like an import is aliased**: an `App\Enum\Option` comes in as `EnumOption`, since `Option` is the attribute.
* **The notes use `var_export()`**, so every type, enums included, is shown the same way.

I saw the run of `--no-interaction` fixes that just landed (#1816, #1817, #1818), and this maker does collect state in `interact()` and read it in `generate()`, which is the shape those removed. The properties are initialised, so there is nothing to crash on, but `it_makes_a_command_non_interactively` pins that down rather than leaving it to reading.

#### Testing

Seven functional tests, up from three: the questions, the command line (enums included), and the definitions it rejects. `MakeCommandTest` green, PHPStan green with the CI tooling.

Commits
-------

25d53f4 [make:command] Ask for the arguments and the options
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants