Der Trait Macroable erlaubt es, einer Klasse nachträglich dynamisch Methoden hinzuzufügen, ohne sie zu verändern. Viele Kernklassen von Laravel setzen diesen Trait ein — Sie können also Funktionalität erweitern, ohne den Framework-Code selbst anzufassen.Die eigentliche Implementierung liegt in Illuminate\Support\Traits\Macroable. Intern werden registrierte Macros in der statischen Property $macros gespeichert und über die Magic Methods __call und __callStatic aufgerufen.
Innerhalb der Closure ist $this an die Instanz gebunden, auf der das Macro aufgerufen wurde. Damit können Sie direkt auf Klassen-Properties und -Methoden zugreifen.
mixin() — mehrere Methoden gleichzeitig hinzufügen
Wenn Sie viele Macros gemeinsam registrieren möchten, nutzen Sie mixin(). Alle public- und protected-Methoden der Mixin-Klasse werden als Macros registriert.
namespace App\Mixins;class CollectionMixin{ public function toCsv(): Closure { return function (string $separator = ',') { /** @var \Illuminate\Support\Collection $this */ return $this->map(function ($item) use ($separator) { return is_array($item) ? implode($separator, $item) : $item; })->implode("\n"); }; } public function filterEmpty(): Closure { return function () { /** @var \Illuminate\Support\Collection $this */ return $this->filter(fn ($item) => ! empty($item))->values(); }; } public function groupByFirst(): Closure { return function (string $key) { /** @var \Illuminate\Support\Collection $this */ return $this->groupBy(fn ($item) => $item[$key][0] ?? ''); }; }}
Die Methoden für mixin() müssen eine Closure zurückgeben, die als Macro registriert wird. Der Rückgabewert der Methode selbst ist die eigentliche Macro-Implementierung.
// Einsatz im Controllerpublic function index(Request $request){ if ($request->isFromMobile()) { return response()->json($this->getMobileData()); } $locale = $request->preferredLocale(['ja', 'en', 'zh']); // ...}
Wenn Sie Spaltendefinitionen als Macro bündeln, bleiben Ihre Datenbankdesigns konsistent.
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();});
// Einsatz in der MigrationSchema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body'); $table->addTimestampsWithTimezone(); $table->addUserTracking();});
Nützlich, wenn Sie Macros in einem Test zurücksetzen möchten.
use Illuminate\Support\Collection;// Reset innerhalb eines TestsCollection::flushMacros();
flushMacros() löscht sämtliche Macros dieser Klasse. Ein Aufruf in tearDown() isoliert Tests voneinander, entfernt aber auch Macros, die andere Tests registriert haben — Vorsicht.
// Implementierung von __call (Instanzmethodenaufruf)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) { // $this über bindTo an die Instanz binden $macro = $macro->bindTo($this, static::class); } return $macro(...$parameters);}
Die Closure wird mittels Closure::bindTo() an die Instanz gebunden. Dadurch zeigt $this auf das Objekt, auf dem das Macro aufgerufen wurde. Andere Werte (etwa invokable Objekte) werden nicht gebunden.
Damit die IDE-Unterstützung funktioniert, definieren Sie Macro-Annotationen entweder per Docblock mit @mixin oder generieren mit dem Paket Laravel IdeHelper automatisch entsprechende Helper-Dateien.