Table of Contents
When building Artisan commands that orchestrate other commands—batch processors, deployment scripts, or data pipelines—you often don’t want child command output cluttering your carefully formatted progress bars.
Laravel gives you callSilently() for exactly this:
use Illuminate\Console\Command;
class ProcessQueueCommand extends Command
{
protected $signature = 'queue:process-all {--batch=}';
public function handle(): int
{
$jobs = $this->option('batch')
? Job::where('batch_id', $this->option('batch'))->get()
: Job::pending()->get();
$this->info("Processing {$jobs->count()} jobs...");
$this->output->progressStart($jobs->count());
$failed = 0;
foreach ($jobs as $job) {
// Call another command without its output
$exitCode = $this->callSilently('job:process', [
'id' => $job->id,
'--force' => true,
]);
if ($exitCode !== 0) {
$failed++;
$this->warn("Job {$job->id} failed");
}
$this->output->progressAdvance();
}
$this->output->progressFinish();
$this->info("Done. {$failed} failures.");
return $failed > 0 ? 1 : 0;
}
}
call() vs callSilently()
Both methods invoke another Artisan command programmatically, but they differ in output handling:
call(): Passes through all output from the child command to your terminal. Good when you want the user to see what’s happening.callSilently(): Suppresses all child output completely. Exit codes still work for error handling.
// User sees everything
$this->call('db:seed', ['--class' => 'UserSeeder']);
// Silent execution, only exit code returned
$exitCode = $this->callSilently('db:seed', ['--class' => 'UserSeeder']);
if ($exitCode !== 0) {
$this->error('Seeding failed');
}
When to Use Each
Use call() when:
- You’re delegating to a command that should show its own progress
- Debugging—you want to see what the child is doing
- The child command has important user-facing messages
Use callSilently() when:
- Building batch processors or orchestrators
- Your parent command has its own progress UI
- Child command output would duplicate information or clutter the terminal
- You only care about success/failure (exit code)
Combining with Progress Bars
Progress bars + callSilently() = clean batch operations:
$bar = $this->output->createProgressBar($items->count());
$bar->setFormat('Processing: %current%/%max% [%bar%] %percent:3s%% %message%');
foreach ($items as $item) {
$bar->setMessage("Processing {$item->name}...");
$exitCode = $this->callSilently('item:sync', ['id' => $item->id]);
if ($exitCode === 0) {
$bar->setMessage("✓ {$item->name}");
} else {
$bar->setMessage("✗ {$item->name}");
}
$bar->advance();
}
$bar->finish();
$this->newLine();
Testing Commands That Use callSilently()
In tests, both methods work the same way. You can assert on exit codes:
public function test_batch_processor_handles_failures()
{
// Simulate a failing job
Job::factory()->create(['id' => 999, 'status' => 'broken']);
$this->artisan('queue:process-all')
->expectsOutput('Processing 1 jobs...')
->expectsOutput('Done. 1 failures.')
->assertExitCode(1);
}
Alternative: Output Buffering
If you need to capture child output for logging (not display), use output buffering instead:
use Symfony\Component\Console\Output\BufferedOutput;
$buffer = new BufferedOutput();
$exitCode = $this->call('some:command', [], $buffer);
$output = $buffer->fetch(); // Get all output as string
Log::debug('Command output', ['output' => $output]);
This is useful for debugging or auditing what child commands did, without showing it to users.
The Bottom Line
callSilently() keeps orchestrator commands clean. Use it when your parent command owns the UI—the user doesn’t need to see every detail of what child processes are doing, just the overall progress and results.
Leave a Reply