Le trait Macroable permet d’ajouter dynamiquement des méthodes à une classe après coup, sans la modifier. Beaucoup de classes du cœur de Laravel l’utilisent, ce qui vous permet d’étendre les fonctionnalités sans toucher au code du cœur.L’implémentation réside dans Illuminate\Support\Traits\Macroable. En interne, les macros enregistrées sont stockées dans la propriété statique $macros, et sont invoquées via les méthodes magiques __call / __callStatic.
Pour enregistrer beaucoup de macros à la fois, utilisez mixin(). Toutes les méthodes public / protected de la classe de mixin sont enregistrées comme macros.
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] ?? ''); }; }}
Les méthodes de mixin() doivent renvoyer la closure à enregistrer comme macro. C’est la valeur de retour de la méthode qui devient l’implémentation de la macro.
// Compter les caractères en tenant compte du multi-octetStr::macro('mbLength', function (string $value) { return mb_strlen($value, 'UTF-8');});// Convertir un snake_case en notation pointéeStr::macro('toDotNotation', function (string $value) { return str_replace('_', '.', $value);});$length = Str::mbLength('こんにちは'); // 5$dot = Str::toDotNotation('user_profile_name'); // 'user.profile.name'
Regrouper des définitions de colonnes sous forme de macros permet de garder une conception de base de données cohérente.
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();});
// Utilisation dans une migrationSchema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body'); $table->addTimestampsWithTimezone(); $table->addUserTracking();});
À utiliser pour réinitialiser les macros dans les tests.
use Illuminate\Support\Collection;// Réinitialisation dans un testCollection::flushMacros();
flushMacros() supprime toutes les macros de la classe. On peut l’appeler dans tearDown() pour préserver l’indépendance des tests, mais attention : les macros enregistrées par d’autres tests sont aussi supprimées.
// Implémentation de __call (appel de méthode d'instance)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) { // Liaison de $this à l'instance via bindTo $macro = $macro->bindTo($this, static::class); } return $macro(...$parameters);}
La closure est liée à l’instance via Closure::bindTo(). Ainsi, $this pointe vers l’objet appelant. Pour d’autres types (objets invokables, par exemple), aucune liaison n’est effectuée.
Pour obtenir la prise en charge de l’IDE, vous pouvez déclarer les macros via une annotation @mixin dans un bloc doc, ou utiliser le package Laravel IdeHelper pour générer automatiquement un fichier d’aide.