throw_unless

Throws the given exception when the condition is falsy, the positive form of a throw_if guard.

Signature
throw_unless(mixed $condition, mixed $exception = 'RuntimeException', mixed ...$parameters): mixed

Parameters

ParameterTypeDescription
$$conditionmixedEvaluated for truthiness; the exception is thrown when it is falsy.
$$exceptionoptional\Throwable|\Closure|class-string|string= 'RuntimeException'An exception instance, a closure building one, a class name to construct, or a message string.
$...$parametersoptionalmixedForwarded 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 falsy; a string that does not name an existing class becomes the message of a RuntimeException.

throw_unless() is literally throw_if(! $condition, $exception, ...$parameters) followed by return $condition. Everything about how the exception argument is resolved is identical to throw_if(): a Closure is invoked with the trailing parameters, an existing class name is constructed with them, any other string becomes a RuntimeException message, and an object is thrown untouched.

When to reach for it

Pick it when the guard reads better stated positively — throw_unless($user->isAdmin(), ...) instead of throw_if(! $user->isAdmin(), ...). There is no behavioural reason to prefer one over the other; it is purely about removing the negation from the reader's path.

What trips people up

The negation is a plain !, so it throws on every falsy value, not just false. 0, '0', '', [] and null all throw. A method that returns an empty collection to mean "nothing matched" will therefore raise here even though the call succeeded — an empty Collection is falsy in a boolean test only via count(), so pass ->isNotEmpty() or another real boolean.

On the non-throwing path it returns the condition, so it doubles as a validate-and-assign: $id = throw_unless($request->input('id'), ...).

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Require an authenticated administrator before running a destructive action.
  • Assert that a lookup returned something before using the result.
  • Validate a positively-phrased business rule at the top of a service method.

Frequently asked questions

What is the difference between throw_if() and throw_unless()?

throw_unless($cond, ...) is exactly throw_if(! $cond, ...) — same exception resolution, same return value, same behaviour. Choose whichever removes the negation from the line you are writing.

Does throw_unless() throw when the condition is 0 or an empty array?

Yes. It negates with PHP's ! operator, so every falsy value throws: false, 0, 0.0, '', '0', [] and null. Pass an explicit boolean when a falsy value is a legitimate result.

What does throw_unless() return when it does not throw?

It returns the condition itself, unchanged, which lets you use it as a validate-and-assign expression such as $id = throw_unless($request->input('id'), 'id is required.').

More examples

Validate and assign in one expression

Because throw_unless() returns the condition untouched when it does not throw, the same expression both guards and assigns. Note that this only works when the condition is the value you want — a boolean comparison would assign true, not the data.

Example · php
<?php

$payload = ['reference' => 'INV-2026-0042'];

// Returns the condition, so it can stand in for the assignment.
$reference = throw_unless(
    $payload['reference'] ?? null,
    InvalidArgumentException::class,
    'A reference is required.'
);

echo $reference, "\n";   // INV-2026-0042

An authorization guard in a controller

Reading the guard as "unless the user can view it, throw" is closer to how the rule is stated in a specification than the negated throw_if form. Laravel's handler turns AuthorizationException into a 403 response.

Example · php
<?php

namespace App\Http\Controllers;

use App\Models\Invoice;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Auth;

class InvoiceController extends Controller
{
    public function download(Invoice $invoice)
    {
        throw_unless(
            Auth::user()->can('view', $invoice),
            AuthorizationException::class,
            'You are not allowed to access this resource.'
        );

        echo "streaming invoice {$invoice->id}\n";

        return response()->download($invoice->path());
    }
}

The empty-collection trap

The first call throws because an empty Collection evaluates as falsy, even though the lookup itself worked. Whenever the condition is an object, an array or a number rather than a boolean, spell out the test — ->isNotEmpty(), count($x) > 0, $x !== null — instead of relying on truthiness.

Example · php
<?php

$matches = collect([]);

// Wrong: an empty Collection is falsy, so this throws on a legitimate
// "nothing matched" result.
try {
    throw_unless($matches, RuntimeException::class, 'Lookup failed.');
} catch (RuntimeException $e) {
    echo 'threw: ', $e->getMessage(), "\n";
}

// Right: decide explicitly what "success" means.
var_dump(throw_unless($matches instanceof \Illuminate\Support\Collection, 'Not a collection.'));

Discussion

  • Be the first to comment on this lesson.