> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Trait Macroable

> Découvrez comment ajouter des méthodes personnalisées aux classes Laravel existantes grâce au trait Illuminate\Support\Traits\Macroable.

## Qu'est-ce que le trait Macroable

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`.

## Classes utilisant Macroable

Laravel comporte de nombreuses classes compatibles Macroable.

| Classe                                 | Rôle                        |
| -------------------------------------- | --------------------------- |
| `Illuminate\Support\Collection`        | Manipulation de collections |
| `Illuminate\Support\Str`               | Manipulation de chaînes     |
| `Illuminate\Support\Arr`               | Manipulation de tableaux    |
| `Illuminate\Http\Request`              | Requêtes HTTP               |
| `Illuminate\Http\Response`             | Réponses HTTP               |
| `Illuminate\Routing\Router`            | Routeur                     |
| `Illuminate\Routing\ResponseFactory`   | Fabrique de réponses        |
| `Illuminate\Database\Schema\Blueprint` | Schema Builder              |
| `Illuminate\Pipeline\Pipeline`         | Pipeline                    |
| `Illuminate\Testing\TestResponse`      | Réponse de test             |

## macro() — ajouter une méthode

Passez le nom de la méthode en premier argument et une closure en second à `macro()`.

```php theme={null}
use Illuminate\Support\Collection;

Collection::macro('toSentence', function (string $separator = ', ') {
    /** @var Collection $this */
    return $this->implode($separator);
});

$result = collect(['pomme', 'orange', 'raisin'])->toSentence();
// 'pomme, orange, raisin'
```

Le `$this` de la closure est lié à l'instance qui a appelé la macro. Vous pouvez ainsi accéder directement aux propriétés et méthodes de la classe.

## mixin() — ajouter plusieurs méthodes d'un coup

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.

```php theme={null}
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] ?? '');
        };
    }
}
```

<Info>
  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.
</Info>

## Enregistrement dans un service provider

Les macros doivent être enregistrées au démarrage de l'application. La méthode `boot()` d'`AppServiceProvider` est l'endroit approprié.

```php theme={null}
namespace App\Providers;

use App\Mixins\CollectionMixin;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Http\Request;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Enregistrer une seule macro
        Collection::macro('toSentence', function (string $separator = ', ') {
            return $this->implode($separator);
        });

        // Enregistrer un mixin en une passe
        Collection::mixin(new CollectionMixin);

        // Macro sur Str
        Str::macro('initials', function (string $name) {
            return collect(explode(' ', $name))
                ->map(fn ($word) => strtoupper($word[0]))
                ->implode('');
        });
    }
}
```

## Cas d'utilisation pratiques

### Étendre les collections

Ajouter des méthodes personnalisées aux collections est l'usage le plus courant.

```php theme={null}
// Méthode statistique pour une collection de nombres
Collection::macro('median', function () {
    $sorted = $this->sort()->values();
    $count = $sorted->count();

    if ($count === 0) {
        return null;
    }

    $middle = (int) floor($count / 2);

    if ($count % 2 === 0) {
        return ($sorted->get($middle - 1) + $sorted->get($middle)) / 2;
    }

    return $sorted->get($middle);
});

$median = collect([3, 1, 4, 1, 5, 9, 2, 6])->median();
// 3.5

// Macro renvoyant une collection paginée
Collection::macro('paginateArray', function (int $perPage = 15, int $page = 1) {
    return $this->slice(($page - 1) * $perPage, $perPage)->values();
});
```

### Étendre la classe Str

```php theme={null}
// Compter les caractères en tenant compte du multi-octet
Str::macro('mbLength', function (string $value) {
    return mb_strlen($value, 'UTF-8');
});

// Convertir un snake_case en notation pointée
Str::macro('toDotNotation', function (string $value) {
    return str_replace('_', '.', $value);
});

$length = Str::mbLength('こんにちは'); // 5
$dot = Str::toDotNotation('user_profile_name'); // 'user.profile.name'
```

### Étendre la classe Request

```php theme={null}
use Illuminate\Http\Request;

Request::macro('isFromMobile', function () {
    /** @var Request $this */
    $userAgent = $this->userAgent() ?? '';

    return preg_match('/Mobile|Android|iPhone|iPad/i', $userAgent) === 1;
});

Request::macro('preferredLocale', function (array $available = ['fr', 'en']) {
    /** @var Request $this */
    foreach ($this->getLanguages() as $lang) {
        $short = substr($lang, 0, 2);
        if (in_array($short, $available)) {
            return $short;
        }
    }

    return $available[0] ?? 'en';
});
```

```php theme={null}
// Utilisation dans un contrôleur
public function index(Request $request)
{
    if ($request->isFromMobile()) {
        return response()->json($this->getMobileData());
    }

    $locale = $request->preferredLocale(['fr', 'en', 'zh']);
    // ...
}
```

### Étendre Blueprint (migrations)

Regrouper des définitions de colonnes sous forme de macros permet de garder une conception de base de données cohérente.

```php theme={null}
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();
});
```

```php theme={null}
// Utilisation dans une migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->addTimestampsWithTimezone();
    $table->addUserTracking();
});
```

### Étendre les réponses de test

Vous pouvez ajouter des méthodes d'assertion propres aux tests.

```php theme={null}
use Illuminate\Testing\TestResponse;

TestResponse::macro('assertPaginated', function () {
    /** @var TestResponse $this */
    return $this->assertJsonStructure([
        'data',
        'meta' => ['current_page', 'last_page', 'per_page', 'total'],
        'links' => ['first', 'last', 'prev', 'next'],
    ]);
});

TestResponse::macro('assertApiSuccess', function () {
    /** @var TestResponse $this */
    return $this->assertOk()->assertJsonPath('success', true);
});
```

```php theme={null}
// Utilisation dans les tests
$this->getJson('/api/posts')->assertPaginated();
$this->postJson('/api/orders', $data)->assertApiSuccess();
```

## hasMacro() — vérifier l'existence d'une macro

```php theme={null}
use Illuminate\Support\Collection;

if (Collection::hasMacro('toSentence')) {
    $result = collect(['a', 'b'])->toSentence();
}
```

## flushMacros() — réinitialiser les macros

À utiliser pour réinitialiser les macros dans les tests.

```php theme={null}
use Illuminate\Support\Collection;

// Réinitialisation dans un test
Collection::flushMacros();
```

<Warning>
  `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.
</Warning>

## Macros statiques

Les macros fonctionnent aussi bien comme méthodes d'instance que comme méthodes statiques. C'est `__callStatic` qui les gère.

```php theme={null}
Str::macro('randomHex', function (int $length = 8) {
    return substr(bin2hex(random_bytes($length)), 0, $length);
});

// Appel statique
$hex = Str::randomHex(16);
```

## Utiliser le trait Macroable dans vos propres classes

Vous pouvez également intégrer `Macroable` à vos classes.

```php theme={null}
namespace App\Services;

use Illuminate\Support\Traits\Macroable;

class ReportBuilder
{
    use Macroable;

    protected array $sections = [];

    public function addSection(string $name, callable $content): static
    {
        $this->sections[$name] = $content;

        return $this;
    }

    public function build(): array
    {
        return array_map(fn ($fn) => $fn(), $this->sections);
    }
}
```

```php theme={null}
// Extension dans un service provider
ReportBuilder::macro('withSummary', function (string $title) {
    /** @var ReportBuilder $this */
    return $this->addSection('summary', fn () => [
        'title' => $title,
        'generated_at' => now()->toIso8601String(),
    ]);
});

// Exemple d'utilisation
$report = app(ReportBuilder::class)
    ->withSummary('Rapport mensuel')
    ->addSection('data', fn () => ['rows' => 42])
    ->build();
```

## Détails de l'implémentation interne

```php theme={null}
// 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.

<Tip>
  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.
</Tip>

## Étapes suivantes

<Card title="Pattern Pipeline" icon="arrow-right-arrow-left" href="/fr/advanced/pipeline">
  Découvrez comment enchaîner en série plusieurs étapes de traitement grâce au pattern pipeline.
</Card>


## Related topics

- [Classe Fluent](/fr/advanced/fluent.md)
- [Trait Dumpable](/fr/advanced/dumpable.md)
- [Helper tap() et trait Tappable](/fr/advanced/tap.md)
- [Trait InteractsWithData](/fr/advanced/interacts-with-data.md)
- [Pattern Pipeline](/fr/advanced/pipeline.md)
