> ## 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

> Explica cómo añadir métodos propios a las clases existentes de Laravel usando el trait Illuminate\Support\Traits\Macroable.

## Qué es el trait Macroable

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

## Clases que usan Macroable

Laravel tiene muchas clases compatibles con Macroable.

| Clase                                  | Uso                            |
| -------------------------------------- | ------------------------------ |
| `Illuminate\Support\Collection`        | Operaciones sobre colecciones. |
| `Illuminate\Support\Str`               | Operaciones sobre cadenas.     |
| `Illuminate\Support\Arr`               | Operaciones sobre arrays.      |
| `Illuminate\Http\Request`              | Peticiones HTTP.               |
| `Illuminate\Http\Response`             | Respuestas HTTP.               |
| `Illuminate\Routing\Router`            | Router.                        |
| `Illuminate\Routing\ResponseFactory`   | Factory de respuestas.         |
| `Illuminate\Database\Schema\Blueprint` | Constructor de esquema.        |
| `Illuminate\Pipeline\Pipeline`         | Pipeline.                      |
| `Illuminate\Testing\TestResponse`      | Respuestas en tests.           |

## macro() — añadir un método

Al `macro()` le pasas el nombre del método como primer argumento y un closure como segundo.

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

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

$result = collect(['manzana', 'naranja', 'uva'])->toSentence();
// 'manzana, naranja, uva'
```

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.

## mixin() — añadir varios métodos a la vez

Para registrar muchas macros en bloque, usa `mixin()`. Todos los métodos `public` / `protected` de la clase mixin se registran como 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>
  Los métodos del `mixin()` deben devolver el closure que se registrará como macro. El valor devuelto por el método es la implementación real de la macro.
</Info>

## Registro en un service provider

Las macros deben registrarse durante el arranque de la aplicación. El sitio adecuado es el método `boot()` de `AppServiceProvider`.

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

        // Registro de un mixin completo
        Collection::mixin(new CollectionMixin);

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

## Casos de uso prácticos

### Extensión de Collection

Añadir métodos personalizados a las colecciones es el caso más habitual.

```php theme={null}
// Método estadístico para colecciones numéricas
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 que devuelve la colección paginada
Collection::macro('paginateArray', function (int $perPage = 15, int $page = 1) {
    return $this->slice(($page - 1) * $perPage, $perPage)->values();
});
```

### Extensión de la clase Str

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

// Convertir snake_case a notación con puntos
Str::macro('toDotNotation', function (string $value) {
    return str_replace('_', '.', $value);
});

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

### Extensión de la clase 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 = ['es', '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}
// Uso en un controlador
public function index(Request $request)
{
    if ($request->isFromMobile()) {
        return response()->json($this->getMobileData());
    }

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

### Extensión de Blueprint (migraciones)

Al convertir en macros las definiciones de columnas del esquema, mantienes un diseño de BD consistente.

```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}
// Uso en una migración
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->addTimestampsWithTimezone();
    $table->addUserTracking();
});
```

### Extensión de TestResponse

Puedes añadir métodos de assertion específicos para 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}
// Uso en un test
$this->getJson('/api/posts')->assertPaginated();
$this->postJson('/api/orders', $data)->assertApiSuccess();
```

## hasMacro() — comprobar si existe una macro

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

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

## flushMacros() — resetear las macros

Se usa cuando quieres resetear las macros en un test.

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

// Reset dentro del test
Collection::flushMacros();
```

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

## Macros estáticas

Las macros funcionan también como métodos estáticos, no solo como métodos de instancia. Las gestiona `__callStatic`.

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

// Llamada estática
$hex = Str::randomHex(16);
```

## Usar Macroable en tus propias clases

Puedes incorporar `Macroable` también a tus propias clases.

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

// Ejemplo de uso
$report = app(ReportBuilder::class)
    ->withSummary('Informe mensual')
    ->addSection('data', fn () => ['rows' => 42])
    ->build();
```

## Detalles de la implementación interna

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

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

## Próximos pasos

<Card title="Patrón Pipeline" icon="arrow-right-arrow-left" href="/es/advanced/pipeline">
  Aprende cómo componer varios pasos de procesamiento en serie usando el patrón Pipeline.
</Card>


## Related topics

- [Clase Fluent](/es/advanced/fluent.md)
- [Trait Dumpable](/es/advanced/dumpable.md)
- [Helper tap() y trait Tappable](/es/advanced/tap.md)
- [Trait InteractsWithData](/es/advanced/interacts-with-data.md)
- [Patrón Pipeline](/es/advanced/pipeline.md)
