value

Invoke the argument if it is a closure, otherwise return it unchanged.

Signature
value(mixed $value, mixed ...$args): mixed

Parameters

ParameterTypeDescription
$valuemixedA Closure to invoke, or any other value to return unchanged.
$argsoptionalmixedVariadic. Forwarded as arguments to the closure. Ignored entirely when the first argument is not a Closure.

Return value

mixedThe result of invoking $value when it is a Closure, otherwise $value itself.

value() is a two-line helper with an outsized role in the framework. Its body is literally $value instanceof Closure ? $value(...$args) : $value, and that single check is what lets dozens of Laravel APIs accept "a value, or a closure that produces one" without every one of them writing the branch.

When to reach for it

Reach for it when you are writing that kind of API yourself. Accept mixed $default, run it through value() at the point of use, and callers get lazy defaults for free: an expensive lookup passed as a closure is skipped entirely on the happy path. data_get(), Arr::get(), Collection::when() and rescue() all work this way.

Extra arguments

Anything after the first argument is forwarded to the closure. When the first argument is not a closure the extras are silently discarded, so passing them is always safe.

Only Closures count

The check is instanceof Closure, not is_callable(). A callable string such as 'strtoupper', an array callable like [$obj, 'method'], and an object with __invoke() are all returned untouched. If you need those invoked, wrap them: value(fn () => $callable(...)).

Example

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

When to use it

  • Accept either a literal default or a lazily evaluated closure in your own helper's signature.
  • Skip an expensive fallback lookup unless the primary source actually missed.
  • Normalise a config-style option that users may supply as a static value or a callback.

Frequently asked questions

What does value() do with a non-closure argument?

It returns it unchanged, and silently discards any extra arguments you passed. That is what makes it safe to wrap any parameter in value() without knowing what the caller supplied.

Does value() call a callable string like 'strtoupper'?

No — the check is instanceof Closure, not is_callable(), so callable strings, array callables and invokable objects are all returned as-is. Wrap them in a closure if you want them executed.

Why would I use value() instead of just calling the closure?

Because you usually do not know whether the caller gave you a closure or a plain value. value() collapses that branch into one call, which is how Laravel's own APIs offer lazy defaults everywhere without duplicating the check.

More examples

Writing an API that accepts either

One line of value() gives the function both calling styles. The closure receives the key because setting() forwards it, which is how data_get-style helpers pass context into a lazy default.

Example · php
<?php

function setting(array $config, string $key, mixed $default = null): mixed
{
    // The closure only runs when the key is genuinely absent.
    return array_key_exists($key, $config)
        ? $config[$key]
        : value($default, $key);
}

$config = ['theme' => 'dark'];

var_dump(setting($config, 'theme', 'light'));
// string(4) "dark"

var_dump(setting($config, 'locale', fn (string $k) => "missing:$k"));
// string(14) "missing:locale"

Laziness that actually saves work

The closure body ran exactly once, for the key that missed. Had the caller passed the computed string directly instead of a closure, the work would have happened for both lookups.

Example · php
<?php

$calls = 0;

$expensive = function () use (&$calls) {
    $calls++;

    return 'computed';
};

$cache = ['a' => 'cached'];

$a = array_key_exists('a', $cache) ? $cache['a'] : value($expensive);
$b = array_key_exists('b', $cache) ? $cache['b'] : value($expensive);

echo $a, ' ', $b, ' calls=', $calls, "\n";
// cached computed calls=1

Where the framework uses it

The first pair shows the practical difference: the eager form calls expensiveLookup() even when the path resolves, the lazy form does not. Recognising value() in a signature tells you a closure is accepted there.

Example · php
<?php

// data_get, Arr::get and Collection::get all pass their default
// through value(), so these two calls are equivalent in result
// but not in cost:
$eager = data_get($payload, 'a.b.c', expensiveLookup());
$lazy  = data_get($payload, 'a.b.c', fn () => expensiveLookup());

// Collection::when() resolves its condition the same way.
$result = collect([1, 2, 3])
    ->when(fn ($c) => $c->count() > 2, fn ($c) => $c->take(2))
    ->all();

print_r($result);
// Array ( [0] => 1 [1] => 2 )

Discussion

  • Be the first to comment on this lesson.