Courses

PHP for Laravel Developers

Callback Functions or Closures

Summary of this lesson:
- Learn about callback function syntax
- Understand closure parameter and use mechanisms
- Explore short arrow function syntax
- Examine practical uses of anonymous functions

So-called callback functions, "callables" or "closures" are used in Laravel very often, so we need to understand all the details behind their syntax.

Take a look at these Laravel examples.

You can provide the route functionality in a callback function without any Controller:

Route::get('/greeting', function () {
return 'Hello World';
});

You can use Collections to map through data with your custom logic defined in a callback function:

$socialLinks = collect([
'Twitter' => $user->link_twitter,
'Facebook' => $user->link_facebook,
'Instagram' => $user->link_instagram,
])
->filter()
->map(fn ($link, $network) => '<a href="' . $link . '">' . $network . '</a>')
->implode(' | ');

In Eloquent, you may use the chunk() method with a closure, too:

use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;
 
Flight::chunk(200, function (Collection $flights) {
foreach ($flights as $flight) {
// ...
}
});

So, what are the main things we need to know about them?


When Can We Use Them As Parameters?

When the method is defined with the parameters types as callable.

So, if you take a look at the Laravel framework core, you will see these examples:

src/Illuminate/Database/Concerns/BuildsQueries.php

public function chunk($count, callable $callback)
{
// ...

Another one:

src/Illuminate/Collections/Arr.php

public static function map(array $array, callable $callback)
{
// ...

Also, remember that callable may be only one of the accepted parameter types. There can be others:

src/Illuminate/Collections/Arr.php

/**
* Sort the array using the given callback or "dot" notation.
*
* @param array $array
* @param callable|array|string|null $callback
* @return array
*/
public static function sort($array, $callback = null)
{
return Collection::make($array)->sortBy($callback)->all();
}

Parameters VS "use" Variables from Outside

Let me show you the most typical mistake developers make with closures.

When using some variable in a closure, many starting developers think that variable is accessible in a closure by default, but it isn't.

To access the variable in a closure, it must be added in a use.

Here is a typical example from open-source financial freedom project using database transactions.

app/Actions/Fortify/CreateNewUser.php:

public function create(array $input)
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => $this->passwordRules(),
])->validate();
 
return DB::transaction(function () use ($input) {
return tap(User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]), function (User $user) {
$this->createTeam($user);
});
});
}

If you forget to add a variable in a use, you will receive a typical error message:

Undefined variable $input

Also, your IDE should inform you about the undefined variable.


Short Syntax

When using functions, you can use a shorter version called the arrow function.

use Illuminate\Database\Eloquent\Builder;
 
public function table(Table $table): Table
{
return $table
->modifyQueryUsing(fn (Builder $query) => $query->withoutGlobalScopes());
}

But sometimes, using short closure can be hard to read, and it isn't that short.

Forms\Components\TextInput::make('name')
->afterStateUpdated(fn (string $operation, $state, Forms\Set $set) => $operation === 'create' ? $set('slug', Str::slug($state)) : null),

Writing it as a normal closure would be more readable in this case.

Forms\Components\TextInput::make('name')
->afterStateUpdated(function(string $operation, $state, Forms\Set $set) {
if ($operation === 'create') {
$set('slug', Str::slug($state));
}
})

Assign Function to a Variable

There might be places where you need to use some variable in more than one place, but that variable could have a value based on some condition.

In this case, assigning a variable to a closure is called an anonymous function.

In this example from Filament, a variable is assigned based on condition.

packages/support/resources/views/components/grid/column.blade.php:

$getSpanValue = function ($span): string {
if ($span === 'full') {
return '1 / -1';
}
 
return "span {$span} / span {$span}";
};

Then, this variable is used in more than one place.

packages/support/resources/views/components/grid/column.blade.php:

<div
{{
$attributes
// ...
->style([
"--col-span-default: {$getSpanValue($default)}" => $default,
"--col-span-sm: {$getSpanValue($sm)}" => $sm,
"--col-span-md: {$getSpanValue($md)}" => $md,
"--col-span-lg: {$getSpanValue($lg)}" => $lg,
"--col-span-xl: {$getSpanValue($xl)}" => $xl,
"--col-span-2xl: {$getSpanValue($twoXl)}" => $twoXl,
"--col-start-default: {$defaultStart}" => $defaultStart,
"--col-start-sm: {$smStart}" => $smStart,
"--col-start-md: {$mdStart}" => $mdStart,
"--col-start-lg: {$lgStart}" => $lgStart,
"--col-start-xl: {$xlStart}" => $xlStart,
"--col-start-2xl: {$twoXlStart}" => $twoXlStart,
])
}}
>
{{ $slot }}
</div>
Previous: Static, New Object and Laravel Auto-Resolving
avatar

In the filament example, what benefit do we get from having a function inside a var? We can just have a helper function and get the same result.

avatar

What do you mean by function inside a var? I'm not sure I understood what are you referring to here

avatar

"Assign Function to a Variable" it's from the article...

avatar

Helper is when you use it in many places through your project. In this case it's only used in one file.

avatar

I mean what's the benifit of writing $getSpanValue = function ... instead of just writing function getSpanValue(){}

avatar
You can use Markdown
avatar
You can use Markdown