Macro — runtime extension of a class without modifying its source
Concept
Macro — in Laravel, a runtime extension added to a class without modifying its source code. Macros let you add methods to core Laravel classes from your service providers.
What "macro" means: The word comes from macro-programming — a way to extend a system at a meta-level. In Laravel, it's the Macroable trait that powers this.
How it works: Classes that use the Macroable trait (e.g., Request, Response, Collection, Builder, Str) expose macro() and mixin() static methods. When you call SomeClass::macro('methodName', fn), Laravel stores the closure. When $instance->methodName() is called later, __call() intercepts it and invokes the stored closure with $this bound to the instance.
Where to register macros: In a service provider's boot() method. Always use boot(), not register() — boot() runs after all providers are registered.
What classes are Macroable in Laravel:
Illuminate\Support\CollectionIlluminate\Http\RequestIlluminate\Http\ResponseIlluminate\Database\Query\BuilderIlluminate\Database\Eloquent\BuilderIlluminate\Support\StrIlluminate\Support\ArrIlluminate\Routing\Router
mixin(): Registers all public methods of a mixin class as macros at once. Useful when you have many related macros to add.
Trade-offs:
- Great for adding methods to framework classes in packages.
- Not statically analyzed — IDEs and Psalm/PHPStan won't know about macro methods without stubs.
- Use sparingly in application code — creating a custom class is often clearer.
Code Example
<?php
// In AppServiceProvider::boot() — register macros here
use Illuminate\Support\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
// Collection macro — adds toAssoc() method
Collection::macro('toAssoc', function (string $key): Collection {
return $this->keyBy($key); // $this = the Collection instance
});
$users = collect([
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob'],
]);
$byId = $users->toAssoc('id'); // [1 => [...], 2 => [...]]
// Request macro — adds a helper for getting the API version
Request::macro('apiVersion', function (): int {
$path = $this->path(); // $this = the Request instance
if (preg_match('/^api\/v(\d+)/', $path, $m)) return (int) $m[1];
return 1;
});
Route::get('/api/v2/users', function (Request $request) {
$version = $request->apiVersion(); // 2 — from the URL path
});
// Str macro — adds a helper not in Laravel core
Str::macro('initials', function (string $name): string {
return collect(explode(' ', $name))
->map(fn($part) => strtoupper($part[0]))
->implode('');
});
echo Str::initials('Alice Smith'); // 'AS'
echo Str::initials('John Paul Jones'); // 'JPJ'
// mixin() — register many macros from a class
class CollectionMixins
{
public function second(): \Closure
{
return function () { return $this->skip(1)->first(); };
}
public function third(): \Closure
{
return function () { return $this->skip(2)->first(); };
}
public function sumBy(): \Closure
{
return function (string $key): int|float {
return $this->sum($key);
};
}
}
Collection::mixin(new CollectionMixins());
collect([10, 20, 30])->second(); // 20
collect([10, 20, 30])->third(); // 30
// IDE helpers for macros — use a stub file or PHPDoc
/** @mixin \Illuminate\Support\Collection */
class CollectionIdeHelper
{
public function toAssoc(string $key): \Illuminate\Support\Collection {}
}