What happens when a Laravel controller runs `Artisan::call('invoices:resend-failed', [...])`, and when should you use `Artisan::queue`, `$this->call` or `callSilently` instead?
answer
- same process, same request, synchronous
- returns the exit code; Artisan::output()
- exceptions propagate to the caller
- Artisan::queue: a queued job runs it
- $this->call vs callSilently inside commands
basics
~20 sArtisan::call() runs the command synchronously inside the current request, returns its exit code, buffers its output for Artisan::output() and lets exceptions propagate. Artisan::queue() runs it later on a worker; inside a command, $this->call() and callSilently() run another command.
solid answer
~50 s`Artisan::call('invoices:resend-failed', ['customer' => 42, '--dry-run' => true])` runs the command **in the current PHP process** — inside the HTTP request — and returns its integer exit code. Output goes to a buffer you read with `Artisan::output()`. Exceptions are not caught for you: a failure inside the command surfaces in the controller, and an unknown name throws `CommandNotFoundException`. There is no terminal, so any Prompts question returns its default. Arguments are keyed by name, options by `--name`, switches take `true`, array options take arrays, or you pass one string such as `'invoices:resend-failed 42 --dry-run'`. For slow work use `Artisan::queue(...)`, which dispatches a queued job that runs the command on a worker and accepts `onConnection()` and `onQueue()`. Inside a command, `$this->call()` runs another command with output to the same console and `$this->callSilently()` discards it; both return the exit code. Often the better design is calling the service the command wraps.
go deeper
Recall that Artisan::call() runs a command from code with an array of arguments and options and returns its exit code.
Explain that the call is synchronous in the same process, output is buffered for Artisan::output(), exceptions propagate, and how to pass switches and arrays.
Choose between Artisan::call, Artisan::queue, a job and a direct service call for web-triggered work, and handle exit codes and prompts correctly.
Set the boundary between CLI tooling and application services so operational logic has one implementation behind both entry points.
## Running commands from code Artisan commands are usually started from a shell, but Laravel lets code start them too, through the `Artisan` facade and through helpers on the `Command` class. Interviewers ask about this because it is easy to misuse: a command started from a controller behaves differently from one started in a terminal. ## `Artisan::call()` in a web request ```php $exitCode = Artisan::call('invoices:resend-failed', [ 'customer' => $customer->id, '--dry-run' => true, ]); $report = Artisan::output(); ``` What actually happens: - The console kernel is bootstrapped and the command runs **synchronously, in the same PHP process** that is serving the HTTP request. The request waits for it, and web-server and PHP time limits apply. - The **return value is the exit code** — `0` for success. Nothing is thrown just because the code is non-zero, so check it. - Output is written to a **buffer**; `Artisan::output()` returns it as a string for the last call. - **Exceptions propagate.** Artisan's application is configured not to catch exceptions, so an exception inside the command reaches your controller and, unhandled, becomes an error response. - An unknown command name throws `Symfony\Component\Console\Exception\CommandNotFoundException`. - There is **no interactive terminal** — PHP defines no `STDIN` in a web request — so Laravel Prompts return their defaults, and a required prompt without a usable default throws. ## Passing arguments and options | Input | How to pass it | |---|---| | argument | `'customer' => 42` | | value option | `'--since' => '2026-09-01'` | | switch | `'--dry-run' => true` | | array option | `'--invoice' => [17, 18]` | | whole command line | `Artisan::call('invoices:resend-failed 42 --dry-run')` | You may also pass the command's class name instead of its signature name. ## `Artisan::queue()` for slow work `Artisan::queue('invoices:resend-failed', ['customer' => 42])` does not run anything now: it dispatches a queued job that calls the command on a queue worker, and returns immediately. It supports `->onConnection('redis')` and `->onQueue('commands')`. Use it when the request should not wait — with the usual queue caveats: a worker must be running, and the result is not available to the request. ## Calling commands from other commands Inside a command class: 1. `$this->call('invoices:resend-failed', ['customer' => 42])` runs another command with its output going to the **same console**, and returns its exit code. 2. `$this->callSilently(...)` — an alias of `callSilent()` — runs it with output discarded, and also returns the exit code. 3. Both pass along the parent's console context, such as `--no-interaction` and `--quiet`, to the child. A "nightly billing" command that calls several smaller commands in order, stopping when one returns non-zero, is a typical use. ## Choosing the right tool - **Admin button that triggers maintenance** (clearing a cache, rebuilding a small index): `Artisan::call()` is acceptable if it is quick, and you check the exit code. - **Anything slow or user-facing**: `Artisan::queue()`, or better, dispatch a job that calls the underlying service directly. - **Sharing logic between web and CLI**: call the **service** both entry points use. Going through Artisan from a controller adds a second bootstrap, string-typed input, buffered output to parse and prompt behaviour you have to think about. - **Composing commands**: `$this->call()` when the operator should see the child's output, `callSilently()` when it would be noise. ## Timeouts and long work Because `Artisan::call()` runs inside the request, the command inherits the request's limits: the PHP-FPM or web-server timeout, `max_execution_time` where it applies, and a user who is watching a spinner. A command that re-sends a few hundred emails can easily outlast them, leaving the work half done with no clear record of where it stopped. Queue it, or dispatch a job per unit of work, and let the request return at once with a message that the work has started. ## Common mistakes - Treating `Artisan::call()` as asynchronous and wondering why the request is slow. - Ignoring the returned exit code, so a failed run looks like success to the controller. - Calling a command that prompts, from the web, and getting its default answer. - Parsing `Artisan::output()` for data instead of calling a service that returns it.
- Why would you prefer calling the service behind a command over `Artisan::call()` from a controller?The service returns typed results and throws meaningful exceptions directly, with no second console bootstrap, no string-typed input, no buffered output to parse and no prompt defaults to worry about. The command stays a thin CLI wrapper over the same service, so both entry points share one tested implementation.
- In a Laravel command that runs three sub-commands, how would you stop the sequence when one fails?Check each return value: `$this->call()` and `callSilently()` return the child's exit code. If it is not `self::SUCCESS`, print a message and return `self::FAILURE` (or call `$this->fail()`), so the parent's own exit code tells cron or CI that the sequence stopped.
saying these in an interview costs you the question
- Artisan::call() runs the command in the background and returns immediately.
- Artisan::call() throws whenever the command returns a non-zero exit code.
- Commands called from a web request can still prompt the user interactively.
- callSilently() suppresses the exit code as well as the output.
- Artisan::queue() runs the command even when no queue worker is running.