framework icon indicating copy to clipboard operation
framework copied to clipboard

Artisan Console Components should use STDERR for auxiliary output

Open Radiergummi opened this issue 4 months ago • 7 comments

Laravel Version

12.21.0

PHP Version

8.4

Database Driver & Version

Description

The Artisan Console Components, which render things like interactive question prompts, progress bars, and so on, use the default Output instance only. Instead, they should—if it is available—use the Error Output to write text not part of the command's output.

For a concrete example, imagine a command with a --json flag that will return some JSON data to be piped to another command or written to a file. The command can take a while though, so it would be great to use the task() helper to show step progress, especially when running in non-JSON mode.
This won't work, though: The task output will be included in the output file and thus break the JSON.

$ php artisan some:command > output.json
$ head -n+3 output.json
Doing work ...................................... 1s DONE
[
  {

If the console components were writing non-output related messages to stderr instead, this would work beautifully: Process logs (like task progress) is rendered on the terminal, while the JSON output gets sent to the pipe:

$ php artisan some:command > output.json
Doing work ...................................... 1s DONE
$ head -n+3 output.json
[
  {
    "nice": "valid JSON!"

If there is a desire to improve this, I'd be willing to create a PR.

Steps To Reproduce

Here's a sample command that shows the behaviour:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;

use function json_encode;
use function sleep;

use const JSON_PRETTY_PRINT;

#[AsCommand('some:command')]
class SomeCommand extends Command
{
    protected $signature = 'some:command {--json}';

    public function handle(): int
    {
        $this->components->task('Doing work', fn () => sleep(1));

        if ($this->option('json')) {
            $this->output->writeln(json_encode([['nice' => 'valid JSON!']], JSON_PRETTY_PRINT));

            return 0;
        }

        $this->table(['Key', 'Value'], [['nice', 'a table!']]);

        return 0;
    }
}

Radiergummi avatar Aug 10 '25 07:08 Radiergummi