El trait Macroable es un mecanismo que permite añadir métodos dinámicamente a una clase después de haberla escrito, sin modificarla. Muchas clases del core de Laravel lo utilizan, así que puedes extender sus funcionalidades sin tocar el código del framework.La implementación real del trait está en Illuminate\Support\Traits\Macroable. Internamente guarda las macros registradas en una propiedad estática $macros y las invoca a través de los métodos mágicos __call / __callStatic.
El $this dentro del closure queda vinculado a la instancia que invocó la macro. Así puedes acceder directamente a las propiedades y métodos de la clase.
// Uso en un controladorpublic function index(Request $request){ if ($request->isFromMobile()) { return response()->json($this->getMobileData()); } $locale = $request->preferredLocale(['es', 'en', 'ja']); // ...}
Al convertir en macros las definiciones de columnas del esquema, mantienes un diseño de BD consistente.
use Illuminate\Database\Schema\Blueprint;Blueprint::macro('addTimestampsWithTimezone', function () { /** @var Blueprint $this */ $this->timestampsTz(); $this->softDeletesTz();});Blueprint::macro('addUserTracking', function () { /** @var Blueprint $this */ $this->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete(); $this->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();});
// Uso en una migraciónSchema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body'); $table->addTimestampsWithTimezone(); $table->addUserTracking();});
Se usa cuando quieres resetear las macros en un test.
use Illuminate\Support\Collection;// Reset dentro del testCollection::flushMacros();
flushMacros() elimina todas las macros de esa clase. A veces se llama en tearDown() para mantener aisladas las pruebas, pero también borrará las macros registradas por otros tests, así que hay que tener cuidado.
// Implementación de __call (llamadas a métodos de instancia)public function __call($method, $parameters){ if (! static::hasMacro($method)) { throw new BadMethodCallException(sprintf( 'Method %s::%s does not exist.', static::class, $method )); } $macro = static::$macros[$method]; if ($macro instanceof Closure) { // bindTo vincula $this a la instancia $macro = $macro->bindTo($this, static::class); } return $macro(...$parameters);}
El closure se vincula a la instancia con Closure::bindTo(). Así, dentro de la macro, $this apunta al objeto que la invocó. Si el macro no es un closure (por ejemplo, un objeto invokable), no se realiza el bind.
Para obtener soporte del IDE, define anotaciones de las macros con @mixin en el docblock o utiliza el paquete Laravel IdeHelper para generar los archivos helper automáticamente.