I have a page where users are allowed to execute Artisan commands using a UI. My problem is getting the output of long-running commands to stream to the browser and not just show it after the command is finished.
Here is my current implementation (code simplified for brevity)
public string $output = '';
public function runCommand(): void
{
$outputBuffer = new BufferedConsoleOutput();
Artisan::call('list', outputBuffer: $outputBuffer);
$this->output = $outputBuffer->fetch();
}
The problem with this is that the output is only displayed when the command is done and the page/UI is unresponsive until then.
Here is what I tried to do using Livewire's stream functionality
use Symfony\Component\Console\Output\StreamOutput;
public function streamTest()
{
$stream = fopen('php://output', 'w');
$outputBuffer = new StreamOutput($stream);
Artisan::call('list', outputBuffer: $outputBuffer);
$this->stream(
to: 'output',
content: $this->output,
replace: true,
);
while ($content = stream_get_contents($outputBuffer->getStream())) {
$this->output .= $content;
}
}
The above doesn't work and I don't know why. I have very limited experience with using streams so there are probably some glaring mistakes in that example, but I couldn't find anything similar online.
FWIW, this is the Blade template for the page
<div>
<button wire:click="streamTest">Stream</button>
Output: <pre wire:stream="output">{{ $output }}</pre>
</div>
I have a page where users are allowed to execute Artisan commands using a UI. My problem is getting the output of long-running commands to stream to the browser and not just show it after the command is finished.
Here is my current implementation (code simplified for brevity)
public string $output = '';
public function runCommand(): void
{
$outputBuffer = new BufferedConsoleOutput();
Artisan::call('list', outputBuffer: $outputBuffer);
$this->output = $outputBuffer->fetch();
}
The problem with this is that the output is only displayed when the command is done and the page/UI is unresponsive until then.
Here is what I tried to do using Livewire's stream functionality
use Symfony\Component\Console\Output\StreamOutput;
public function streamTest()
{
$stream = fopen('php://output', 'w');
$outputBuffer = new StreamOutput($stream);
Artisan::call('list', outputBuffer: $outputBuffer);
$this->stream(
to: 'output',
content: $this->output,
replace: true,
);
while ($content = stream_get_contents($outputBuffer->getStream())) {
$this->output .= $content;
}
}
The above doesn't work and I don't know why. I have very limited experience with using streams so there are probably some glaring mistakes in that example, but I couldn't find anything similar online.
FWIW, this is the Blade template for the page
<div>
<button wire:click="streamTest">Stream</button>
Output: <pre wire:stream="output">{{ $output }}</pre>
</div>
Share
Improve this question
asked Jan 30 at 23:01
SPRTKSPRTK
475 bronze badges
1 Answer
Reset to default 1I think you can't stream the output in real-time from a single Livewire call because Artisan::call
is blocking and only returns its output when it's fully done. A simple workaround is to run the Artisan command in the background(for example, via a queued job) and write the output to storage (like a file or the database). Then, have your Livewire component poll or listen (e.g. using Laravel Echo or SSE) for new output and display it as it arrives. I don't know what the overall implementation idea is in your project but if you just want a rough idea of how this might look, you could have a job like this:
public function handle()
{
$process = new Process(['php', 'artisan', 'list']);
$process->setTimeout(0);
$process->start();
foreach ($process as $type => $data) {
// Append $data to a file or a database record
}
}
Then in your Livewire component, poll or subscribe to updates, read new output, and append it to the UI. This way you're not blocking the main request and you'll see the output as it's generated.