Macros in Laravel are one of the framework’s most powerful yet often underutilized features. They allow you to extend Laravel’s core classes with custom functionality without modifying the framework’s source code. In this comprehensive guide, we’ll explore how to create and use macros in Laravel 12, and examine the different types available.
What Are Macros?
Macros leverage PHP’s magic __call method to add custom methods to classes that use the Macroable trait. This means you can inject your own functionality into Laravel’s core components dynamically, making your code more expressive and reusable.
Creating Basic Macros
The most common place to register macros is in a service provider’s boot method. Let’s start with a simple example:
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Str::macro('toSnakeCase', function (string $value) {
return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $value));
});
}
}
Now you can use this macro anywhere in your application:
echo Str::toSnakeCase('HelloWorld'); // outputs: hello_world
Types of Macros
Laravel 12 supports macros across various components. Let’s explore the most commonly used ones:
Response Macros
Response macros allow you to create custom response formats that you can reuse throughout your application:
use Illuminate\Support\Facades\Response;
Response::macro('success', function ($data, $message = 'Success') {
return Response::json([
'success' => true,
'message' => $message,
'data' => $data,
]);
});
Response::macro('error', function ($message, $code = 400) {
return Response::json([
'success' => false,
'message' => $message,
], $code);
});
Usage in controllers:
public function index()
{
$users = User::all();
return response()->success($users, 'Users retrieved successfully');
}
public function show($id)
{
$user = User::find($id);
if (!$user) {
return response()->error('User not found', 404);
}
return response()->success($user);
}
Collection Macros
Collection macros are particularly useful for adding custom data manipulation methods:
use Illuminate\Support\Collection;
Collection::macro('toUpper', function () {
return $this->map(function ($value) {
return strtoupper($value);
});
});
Collection::macro('filterByStatus', function ($status) {
return $this->filter(function ($item) use ($status) {
return $item->status === $status;
});
});
Using collection macros:
$collection = collect(['hello', 'world']);
$result = $collection->toUpper(); // ['HELLO', 'WORLD']
$users = User::all()->filterByStatus('active');
Request Macros
Request macros help you add custom validation or data retrieval methods:
use Illuminate\Support\Facades\Request;
Request::macro('hasAny', function (array $keys) {
foreach ($keys as $key) {
if ($this->has($key)) {
return true;
}
}
return false;
});
Request::macro('validate', function (array $rules) {
return validator($this->all(), $rules)->validate();
});
Query Builder Macros
Extend the query builder with custom query methods:
use Illuminate\Database\Query\Builder;
Builder::macro('whereLike', function ($column, $value) {
return $this->where($column, 'LIKE', "%{$value}%");
});
Builder::macro('whereStatus', function ($status) {
return $this->where('status', $status);
});
Usage:
$users = DB::table('users')->whereLike('name', 'John')->get();
$activeUsers = DB::table('users')->whereStatus('active')->get();
Eloquent Builder Macros
Similar to query builder macros, but specifically for Eloquent:
use Illuminate\Database\Eloquent\Builder;
Builder::macro('active', function () {
return $this->where('status', 'active');
});
Builder::macro('recent', function ($days = 7) {
return $this->where('created_at', '>=', now()->subDays($days));
});
Using Eloquent macros:
$activeUsers = User::active()->get();
$recentPosts = Post::recent(14)->get();
String Macros
Extend the Str helper with custom string manipulation methods:
use Illuminate\Support\Str;
Str::macro('prefix', function ($string, $prefix) {
return $prefix . $string;
});
Str::macro('wrap', function ($string, $wrapper = '"') {
return $wrapper . $string . $wrapper;
});
Advanced Macro Techniques
Macros with Multiple Parameters
Macros can accept multiple parameters and use closures:
Collection::macro('groupByMultiple', function (array $keys) {
return $this->groupBy(function ($item) use ($keys) {
return implode('-', array_map(fn($key) => $item[$key], $keys));
});
});
Macros that Return the Instance
For chainable macros, return $this:
Collection::macro('debug', function () {
dump($this->toArray());
return $this;
});
// Usage
collect([1, 2, 3])
->debug()
->map(fn($n) => $n * 2)
->debug();
Mixin Classes
For organizing multiple related macros, use mixin classes:
namespace App\Mixins;
class ResponseMixin
{
public function success()
{
return function ($data, $message = 'Success') {
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
]);
};
}
public function error()
{
return function ($message, $code = 400) {
return response()->json([
'success' => false,
'message' => $message,
], $code);
};
}
}
Register the mixin in your service provider:
use Illuminate\Support\Facades\Response;
use App\Mixins\ResponseMixin;
public function boot(): void
{
Response::mixin(new ResponseMixin());
}
Best Practices
When working with macros in Laravel 12, keep these best practices in mind:
- Organize Your Macros: Create dedicated service providers for different types of macros to keep your code organized.
- Document Your Macros: Add PHPDoc comments to help IDEs understand your custom methods:
/**
* @method static \Illuminate\Support\Collection toUpper()
* @method static \Illuminate\Support\Collection filterByStatus(string $status)
*/
- Use Mixins for Related Functionality: Group related macros into mixin classes for better organization.
- Test Your Macros: Write unit tests for your custom macros to ensure they work as expected:
public function test_to_upper_macro()
{
$collection = collect(['hello', 'world']);
$result = $collection->toUpper();
$this->assertEquals(['HELLO', 'WORLD'], $result->toArray());
}
- Avoid Naming Conflicts: Be careful not to override existing Laravel methods or create macros with names that might conflict with future Laravel versions.
Conclusion
Macros are a powerful feature in Laravel 12 that allow you to extend the framework’s functionality elegantly. Whether you’re adding custom response formats, collection methods, or query builder helpers, macros help you write cleaner, more expressive code. By understanding the different types of macros and following best practices, you can leverage this feature to create a more maintainable and developer-friendly application.