Macros en Laravel: Extiende el Framework sin Modificar el Código
Macros en Laravel: Extiende el Framework sin Modificar el Código
Los macros son uno de los patrones más poderosos de Laravel para extender la funcionalidad del framework sin necesidad de modificar su código fuente. Si alguna vez has sentido que te falta un método en Eloquent, el Query Builder o cualquier otra clase del framework, los macros son tu solución.
En este artículo aprenderás qué son los macros, cómo crearlos, dónde registrarlos y verás casos de uso prácticos que puedes implementar en tus proyectos hoy mismo.
¿Qué son los Macros en Laravel?
Un macro es un método dinámico que añades a una clase existente del framework sin modificarla directamente. Laravel utiliza el patrón Macroable para permitir esto de manera elegante y segura.
Cuando creas un macro, estás diciendo: “Quiero que esta clase tenga este nuevo método con esta funcionalidad”, y Laravel se encarga de agregarlo en tiempo de ejecución.
Los macros son especialmente útiles para:
- Reutilizar lógica personalizada en múltiples lugares de tu aplicación
- Mejorar la legibilidad del código al crear métodos más específicos del dominio
- Mantener la compatibilidad con futuras versiones del framework
- Evitar herencia innecesaria de clases del framework
Clases que Soportan Macros en Laravel
No todas las clases del framework pueden tener macros. Necesitan implementar el trait Macroable. Las más comunes son:
- Eloquent\Builder: Para los queries de modelos
- Query\Builder: Para queries directos
- Request: Para acceder a la solicitud HTTP
- Response: Para respuestas HTTP
- Collection: Para trabajar con colecciones
- Str y Arr: Para manipulación de strings y arrays
Cómo Crear tu Primer Macro
Los macros se registran típicamente en un Service Provider. Vamos a crear un ejemplo simple.
Paso 1: Crear un Service Provider
php artisan make:provider MacroServiceProvider
Paso 2: Registrar el Macro
Abre el archivo app/Providers/MacroServiceProvider.php y añade tu primer macro:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Builder;
class MacroServiceProvider extends ServiceProvider
{
public function boot(): void
{
Builder::macro('whereLike', function ($column, $value) {
return $this->where($column, 'like', "%{$value}%");
});
}
}
Paso 3: Registrar el Service Provider
En config/app.php, añade el provider a la lista:
'providers' => [
// ... otros providers
App\Providers\MacroServiceProvider::class,
],
Paso 4: Usar el Macro
Ahora puedes usar whereLike en cualquier query de Eloquent:
$users = User::whereLike('name', 'john')->get();
// Equivalente a: User::where('name', 'like', '%john%')->get();
Macros Prácticos para Query Builder
Veamos algunos macros útiles que puedes implementar en tus proyectos:
Macro para búsqueda en múltiples columnas
Builder::macro('whereAnyLike', function ($columns, $value) {
return $this->where(function ($query) use ($columns, $value) {
foreach ($columns as $column) {
$query->orWhere($column, 'like', "%{$value}%");
}
});
});
Uso:
$results = User::whereAnyLike(['name', 'email', 'phone'], 'search term')->get();
Macro para ordenar por relevancia
Builder::macro('orderByRelevance', function ($searchTerm, $columns) {
$relevanceCase = implode(' + ', array_map(function ($column) {
return "CASE WHEN {$column} LIKE ? THEN 3 WHEN {$column} LIKE ? THEN 1 ELSE 0 END";
}, $columns));
$bindings = [];
foreach ($columns as $column) {
$bindings[] = $searchTerm;
$bindings[] = "{$searchTerm}%";
}
return $this->selectRaw("*, ({$relevanceCase}) as relevance", $bindings)
->orderByDesc('relevance');
});
Uso:
$results = User::whereAnyLike(['name', 'email'], 'john')
->orderByRelevance('john', ['name', 'email'])
->get();
Macro para paginación con información útil
Builder::macro('smartPaginate', function ($perPage = 15, $columns = ['*']) {
$total = $this->count();
$paginated = $this->paginate($perPage, $columns);
return $paginated->appends(request()->query())->additional([
'meta' => [
'total' => $total,
'has_more' => $paginated->hasMorePages(),
'pages' => ceil($total / $perPage),
],
]);
});
Macros para Colecciones
Las colecciones son especialmente útiles para macros. Aquí hay algunos ejemplos:
Macro para agrupar por clave y contar
use Illuminate\Support\Collection;
Collection::macro('groupByAndCount', function ($key) {
return $this->groupBy($key)->map(function ($items) {
return [
'count' => $items->count(),
'items' => $items,
];
});
});
Uso:
$grouped = collect($users)->groupByAndCount('department');
// [
// 'engineering' => ['count' => 5, 'items' => [...]],
// 'sales' => ['count' => 3, 'items' => [...]],
// ]
Macro para filtrar valores nulos
Collection::macro('filterNotNull', function () {
return $this->filter(function ($value) {
return !is_null($value);
});
});
Uso:
$clean = collect($data)->filterNotNull();
Macros para la Clase Request
Puedes extender el objeto Request para añadir métodos útiles:
Macro para obtener solo inputs específicos
use Illuminate\Http\Request;
Request::macro('onlyFilled', function (...$keys) {
return $this->only($keys)
->filter(fn ($value) => !empty($value))
->toArray();
});
Uso:
$filtered = $request->onlyFilled('name', 'email', 'phone');
// Solo retorna los campos que tengan valores
Macro para validación condicional rápida
Request::macro('validateIfPresent', function ($field, $rules) {
return $this->when($this->has($field), function ($request) use ($field, $rules) {
return $request->validate([$field => $rules]);
});
});
Estructura Recomendada para Macros
Cuando tus macros crecen, es buena idea organizarlos. Crea una carpeta app/Macros:
app/
└── Macros/
├── BuilderMacros.php
├── CollectionMacros.php
├── RequestMacros.php
└── StringMacros.php
Ejemplo de app/Macros/BuilderMacros.php:
<?php
namespace App\Macros;
use Illuminate\Database\Eloquent\Builder;
class BuilderMacros
{
public static function register(): void
{
Builder::macro('whereLike', function ($column, $value) {
return $this->where($column, 'like', "%{$value}%");
});
Builder::macro('whereAnyLike', function ($columns, $value) {
return $this->where(function ($query) use ($columns, $value) {
foreach ($columns as $column) {
$query->orWhere($column, 'like', "%{$value}%");
}
});
});
// Más macros...
}
}
Luego, en tu Service Provider:
public function boot(): void
{
BuilderMacros::register();
CollectionMacros::register();
RequestMacros::register();
}
Debugging de Macros
Si algo no funciona correctamente, puedes verificar si un macro existe:
if (method_exists(Builder::class, 'yourMacroName')) {
// El macro existe
}
// O usando Tinker
php artisan tinker
> User::hasMacro('whereLike')
Mejores Prácticas
1. Documentación clara
/**
* Busca en una columna usando LIKE
*
* @param string $column
* @param string $value
* @return \Illuminate\Database\Eloquent\Builder
*/
Builder::macro('whereLike', function ($column, $value) {
return $this->where($column, 'like', "%{$value}%");
});
2. Evita nombres genéricos
// ❌ Malo - muy genérico
Builder::macro('search', function ($value) { ... });
// ✅ Bueno - específico del dominio
Builder::macro('searchByNameOrEmail', function ($value) { ... });
3. Retorna el objeto para encadenamiento
// ✅ Permite: User::whereLike('name', 'john')->paginate()
Builder::macro('whereLike', function ($column, $value) {
return $this->where($column, 'like', "%{$value}%");
});
4. Ten cuidado con las actualizaciones del framework
Si Laravel agrega un método con el mismo nombre en una versión futura, tu macro será ignorado. Usa namespacing en los nombres si es necesario.
Conclusión
Los macros en Laravel son una herramienta poderosa para crear código más limpio, mantenible y reutilizable. Te permiten extender el framework sin modificar su código fuente, lo que facilita las actualizaciones y mantiene tu proyecto organizado.
Comienza con casos simples como whereLike y, conforme te sientas cómodo, crea macros más complejos que reflejen la lógica específica de tu dominio. Recuerda documentar bien tus macros y organizarlos de manera coherente en tu proyecto.
Puntos clave
- Los macros permiten añadir métodos dinámicamente a clases de Laravel sin modificarlas
- Se registran en Service Providers usando el patrón Macroable
- Las clases más comunes para macros son Builder, Collection, Request y Response
- Los macros deben retornar
$thispara permitir encadenamiento de métodos - Organiza tus macros en archivos separados por tipo para mejor mantenibilidad
- Documenta bien tus macros para que otros desarrolladores entiendan su propósito
- Usa nombres específicos del dominio, no genéricos
- Los macros no interfieren con actualizaciones del framework
- Verifica si un macro existe antes de usarlo con
hasMacro()si es necesario - Los macros mejoran la legibilidad y reutilización del código en grandes proyectos