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

# Macroable-trait

> Leer hoe je met de trait Illuminate\Support\Traits\Macroable eigen methodes toevoegt aan bestaande Laravel-klassen.

## Wat is de Macroable-trait

De `Macroable`-trait is een mechanisme waarmee je achteraf dynamisch methodes aan een klasse kunt toevoegen zonder de klasse zelf te wijzigen. Veel coreklassen van Laravel gebruiken deze trait, zodat je functionaliteit kunt uitbreiden zonder aan de corecode te komen.

De trait zelf staat in `Illuminate\Support\Traits\Macroable`. Intern worden geregistreerde macro's opgeslagen in de statische property `$macros` en aangeroepen via de magic methods `__call` / `__callStatic`.

## Klassen die Macroable gebruiken

Laravel heeft veel klassen die Macroable ondersteunen.

| Klasse                                 | Doel                 |
| -------------------------------------- | -------------------- |
| `Illuminate\Support\Collection`        | Collectiebewerkingen |
| `Illuminate\Support\Str`               | Stringbewerkingen    |
| `Illuminate\Support\Arr`               | Arraybewerkingen     |
| `Illuminate\Http\Request`              | HTTP-requests        |
| `Illuminate\Http\Response`             | HTTP-responses       |
| `Illuminate\Routing\Router`            | Router               |
| `Illuminate\Routing\ResponseFactory`   | Responsefactory      |
| `Illuminate\Database\Schema\Blueprint` | Schemabuilder        |
| `Illuminate\Pipeline\Pipeline`         | Pipeline             |
| `Illuminate\Testing\TestResponse`      | Testresponse         |

## macro() — een methode toevoegen

Aan `macro()` geef je als eerste argument de methodenaam en als tweede argument een closure mee.

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

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

$result = collect(['Appel', 'Sinaasappel', 'Druif'])->toSentence();
// 'Appel, Sinaasappel, Druif'
```

`$this` binnen de closure wordt gebonden aan de instantie die de macro aanroept. Daardoor heb je rechtstreeks toegang tot de properties en methodes van de klasse.

## mixin() — meerdere methodes tegelijk toevoegen

Wil je veel macro's in één keer registreren, dan gebruik je `mixin()`. Alle `public` / `protected` methodes van de mixinklasse worden als macro geregistreerd.

```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>
  De methodes van een `mixin()` moeten de closure teruggeven die als macro wordt geregistreerd. De returnwaarde van de methode zelf wordt de implementatie van de macro.
</Info>

## Registreren in de service provider

Macro's moeten worden geregistreerd bij het opstarten van de applicatie. De `boot()`-methode van de `AppServiceProvider` is daarvoor de juiste plek.

```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
    {
        // Een enkele macro registreren
        Collection::macro('toSentence', function (string $separator = ', ') {
            return $this->implode($separator);
        });

        // Een mixin in één keer registreren
        Collection::mixin(new CollectionMixin);

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

## Praktische use cases

### Collecties uitbreiden

Eigen methodes toevoegen aan collecties is het meest voorkomende gebruik.

```php theme={null}
// Statistische methode voor numerieke collecties
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 die een collectie met paginatie-informatie teruggeeft
Collection::macro('paginateArray', function (int $perPage = 15, int $page = 1) {
    return $this->slice(($page - 1) * $perPage, $perPage)->values();
});
```

### De Str-klasse uitbreiden

```php theme={null}
// Japanse tekens tellen (multibyte-ondersteuning)
Str::macro('mbLength', function (string $value) {
    return mb_strlen($value, 'UTF-8');
});

// Snake case omzetten naar dotnotatie
Str::macro('toDotNotation', function (string $value) {
    return str_replace('_', '.', $value);
});

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

### De Request-klasse uitbreiden

```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 = ['ja', '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}
// Gebruik in een controller
public function index(Request $request)
{
    if ($request->isFromMobile()) {
        return response()->json($this->getMobileData());
    }

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

### Blueprint uitbreiden (migraties)

Door kolomdefinities van je schema in macro's te bundelen, houd je een consistent databaseontwerp.

```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}
// Gebruik in een migratie
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->addTimestampsWithTimezone();
    $table->addUserTracking();
});
```

### Testresponses uitbreiden

Je kunt assertion-methodes speciaal voor tests toevoegen.

```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}
// Gebruik in tests
$this->getJson('/api/posts')->assertPaginated();
$this->postJson('/api/orders', $data)->assertApiSuccess();
```

## hasMacro() — controleren of een macro bestaat

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

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

## flushMacros() — macro's resetten

Gebruik je wanneer je macro's in tests wilt resetten.

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

// Resetten binnen een test
Collection::flushMacros();
```

<Warning>
  `flushMacros()` verwijdert alle macro's van die klasse. Soms roep je hem aan in `tearDown()` om tests onafhankelijk van elkaar te houden, maar let op: ook macro's die in andere tests zijn geregistreerd verdwijnen dan.
</Warning>

## Statische macro's

Macro's werken niet alleen als instantiemethodes, maar ook als statische methodes. Ze worden afgehandeld door `__callStatic`.

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

// Statische aanroep
$hex = Str::randomHex(16);
```

## De Macroable-trait in je eigen klassen gebruiken

Je kunt `Macroable` ook inbouwen in klassen die je zelf hebt gemaakt.

```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}
// Uitbreiden in een service provider
ReportBuilder::macro('withSummary', function (string $title) {
    /** @var ReportBuilder $this */
    return $this->addSection('summary', fn () => [
        'title' => $title,
        'generated_at' => now()->toIso8601String(),
    ]);
});

// Voorbeeldgebruik
$report = app(ReportBuilder::class)
    ->withSummary('Maandrapport')
    ->addSection('data', fn () => ['rows' => 42])
    ->build();
```

## Details van de interne implementatie

```php theme={null}
// Implementatie van __call (aanroep als instantiemethode)
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) {
        // Met bindTo wordt $this aan de instantie gebonden
        $macro = $macro->bindTo($this, static::class);
    }

    return $macro(...$parameters);
}
```

Closures worden via `Closure::bindTo()` aan de instantie gebonden. Daardoor wijst `$this` naar het object dat de macro aanroept. Bij iets anders dan een closure (zoals een invokable object) vindt geen binding plaats.

<Tip>
  Voor IDE-ondersteuning kun je annotaties voor je macro's definiëren in een docblock met `@mixin`, of met het Laravel IdeHelper-pakket automatisch een helperbestand genereren.
</Tip>

## Volgende stap

<Card title="Pipeline-patroon" icon="arrow-right-arrow-left" href="/nl/advanced/pipeline">
  Leer hoe je met het pipeline-patroon meerdere verwerkingsstappen serieel samenstelt.
</Card>


## Related topics

- [Dumpable-trait](/nl/advanced/dumpable.md)
- [ForwardsCalls-trait](/nl/advanced/forwards-calls.md)
- [De tap()-helper en de Tappable-trait](/nl/advanced/tap.md)
- [InteractsWithData-trait](/nl/advanced/interacts-with-data.md)
- [Het Pipeline-patroon](/nl/advanced/pipeline.md)
