throw_if
Throws the given exception when the condition is truthy, collapsing a guard clause to one line.
throw_if(mixed $condition, mixed $exception = 'RuntimeException', mixed ...$parameters): mixedParameters
| Parameter | Type | Description |
|---|---|---|
$$condition | mixed | Evaluated for truthiness; the exception is thrown when it is truthy. |
$$exceptionoptional | \Throwable|\Closure|class-string|string= 'RuntimeException' | An exception instance, a closure building one, a class name to construct, or a message string. |
$...$parametersoptional | mixed | Forwarded to the exception constructor, or to the closure when one is given. |
Return value
mixedThe condition itself when nothing is thrown, so the call can be used inline.
Errors
Throws the resolved exception whenever the condition is truthy; a string that does not name an existing class becomes the message of a RuntimeException.
See also
throw_if() replaces the four-line if (...) { throw new ...; } block with one expression. The second argument can be an exception instance, a class-string, a plain message string, or a Closure that builds the exception. Everything after it is forwarded to the exception constructor, or to the closure.
How the second argument is resolved
- A
Closureis called with the trailing parameters and its return value is treated as the exception. - A string naming an existing class is instantiated with the trailing parameters.
- Any other string becomes the message of a
RuntimeException. - An object is thrown as-is; trailing parameters are ignored.
What trips people up
The condition is tested for truthiness, not identity. throw_if('0') does not throw, because '0' is falsy in PHP, while throw_if('false') does, because a non-empty string is truthy. Cast to bool when the source might return something odd.
The default exception is RuntimeException with no message, so throw_if($cond) on its own throws something a reader cannot diagnose. Always give it a class or a message.
Example
When to use it
- Reject an unauthorized action at the top of a controller method.
- Guard against an impossible state in a service before doing any work.
- Fail fast on a malformed payload inside a queued job's handle().
Frequently asked questions
How do I pass a custom message to throw_if()?
Pass the exception class as the second argument and the message as the third: throw_if($cond, AuthorizationException::class, 'Not allowed.'). Every argument after the second is forwarded to the exception's constructor, so you can also pass a code and a previous exception.
What does throw_if() return when it does not throw?
It returns the condition unchanged, which lets you use it as an expression — for example $ok = throw_if($problem, ...) or chaining it inside a larger statement. On the throwing path nothing is returned.
Why did throw_if() throw a RuntimeException instead of my exception class?
Because the string you passed does not name a class that exists, so throw_if() treated it as a message and wrapped it in a RuntimeException. This is almost always a typo or a missing import — use MyException::class instead of a quoted string so the mistake becomes a compile-time error.
More examples
Building the exception with a closure
The closure form is the one to use when the message needs interpolation, because the closure is only invoked when the condition is truthy — the string concatenation costs nothing on the happy path. The trailing arguments are passed to the closure, not to a constructor.
<?php
class QuotaExceeded extends RuntimeException {}
$used = 120;
$limit = 100;
try {
throw_if(
$used > $limit,
fn (int $used, int $limit) => new QuotaExceeded("Used {$used} of {$limit} credits."),
$used,
$limit
);
} catch (QuotaExceeded $e) {
echo $e->getMessage(), "\n"; // Used 120 of 100 credits.
}An authorization guard in a controller
AuthorizationException is rendered as a 403 by Laravel's exception handler, so this one line produces the correct HTTP response without an if block or a manual abort(). The guard sits at the top of the method where a reader looks for preconditions.
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Auth;
class PostController extends Controller
{
public function destroy(Post $post)
{
throw_if(
$post->user_id !== Auth::id(),
AuthorizationException::class,
'You are not allowed to delete this post.'
);
$post->delete();
echo "deleted post {$post->id}\n";
return response()->noContent();
}
}The class-name typo trap
Because class_exists() returns false for the misspelled name, the string falls through to the message branch and you get a RuntimeException whose message is the class name you meant to throw. Nothing warns you, so a catch block written for the real class silently stops matching. Always write ExceptionClass::class rather than a quoted string.
<?php
try {
throw_if(true, 'App\Exceptions\QuotaExceded'); // note the missing 'e'
} catch (Throwable $e) {
echo get_class($e), "\n"; // RuntimeException
echo $e->getMessage(), "\n"; // App\Exceptions\QuotaExceded
}
Discussion