Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Macroable Laravel Package

illuminate/macroable

The Illuminate Macroable trait lets you add custom methods to a class at runtime without modifying its source. Register macros once and call them like native methods, making Laravel components and your own classes easily extensible.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: there is no meaningful standalone install here. The trait you actually use is Illuminate\Support\Traits\Macroable, which ships inside illuminate/support — i.e. it is already in every Laravel app via laravel/framework. Do not add a composer require illuminate/macroable step to your setup docs or onboarding; it's a subtree-split artifact of the monorepo, not a dependency you adopt. If you're on Laravel, you have it.

Minimal setup, three steps:

  1. Read the trait itself — it's ~100 lines. Open vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php and look at macro(), mixin(), hasMacro(), flushMacros(), __call(), __callStatic(). Everything below follows from those six methods.
  2. Pick a macroable target. You don't add Macroable to Laravel's own classes — Str, Stringable, Collection, Request, Response, Rule, Blueprint, Carbon, and Eloquent factories are already macroable. You only add the trait to your own classes:
    use Illuminate\Support\Traits\Macroable;
    
    class Invoice
    {
        use Macroable;
    }
    
  3. Register your first macro in a service provider's boot(), not inline at call sites:
    // app/Providers/AppServiceProvider.php
    public function boot(): void
    {
        Str::macro('initials', function (string $name): string {
            return collect(explode(' ', $name))
                ->map(fn ($part) => Str::upper(Str::substr($part, 0, 1)))
                ->implode('');
        });
    }
    
    Then Str::of('Ada Lovelace')->initials() works anywhere in the app.

First real use case: a formatting/domain helper you'd otherwise duplicate. Instead of a Helpers.php full of static functions nobody owns, you hang Response::macro('apiOk', ...) or Collection::macro('activeUsers', ...) off the class it conceptually belongs to. That's the whole value proposition: discoverable, namespaced, testable, no inheritance.

Where to look first when something's off: Laravel's docs page on Extending Laravel / Macros, plus debug_backtrace() inside the closure. Your IDE will not find macro methods — plan for that on day one (see Gotchas).

Implementation Patterns

Dedicated macro service provider. Stop stuffing macros into AppServiceProvider past the second one. Create App\Providers\MacroServiceProvider (or one per surface: StrMacros, CollectionMacros, ResponseMacros) and group them by the class they extend. This gives reviewers one place to grep and one place to delete.

Mixin classes for grouped registration. When you have 5+ macros on one target, register them via a mixin object. Each public method must return a closure:

namespace App\Support\Macros;

class StrMacros
{
    public function initials(): callable
    {
        return function (string $name): string {
            return collect(explode(' ', $name))
                ->map(fn ($p) => Str::upper(Str::substr($p, 0, 1)))
                ->implode('');
        };
    }
}

// in the provider
Str::mixin(new StrMacros, replace: false);

Pass replace: false in app code so a package or a teammate can't silently clobber your macro.

Instance vs. static semantics. Macros are bound to $this when called on an instance, and to a fresh instance when called statically. So write macros that read instance state on instances (Collection::macro), and macros that take all their input as arguments when you expect static-style usage (Str::macro).

Guard against collisions and double-registration. macro() silently overwrites and there is no error. Wrap idempotent registrations:

if (! Collection::hasMacro('activeUsers')) {
    Collection::macro('activeUsers', fn () => $this->where('status', 'active'));
}

This matters because in Octane/queued workers your provider's booted() can run more than once per process lifetime depending on setup.

Use it at the HTTP boundary for API envelopes. A classic day-to-day win — one macro, consistent JSON everywhere:

Response::macro('api', function (mixed $data = null, int $status = 200, array $meta = []) {
    return response()->json(['data' => $data, 'meta' => $meta], $status);
});

Schema/rule macros for project conventions. Blueprint::macro('tenantColumns', ...) and Rule::macro('phoneNumber', ...) keep migrations and validation from re-deriving the same rules per table/form.

Test macros like first-class code. They are functions; give them a test file. Use flushMacros() in tearDown() (or a Pest afterEach) so one test's macro can't leak into another and hide a missing registration:

protected function tearDown(): void
{
    Str::flushMacros();
    Collection::flushMacros();
    parent::tearDown();
}

Prefer a macro over a static helper when: the method reads naturally as a verb on the object ($order->totalWithTax()), the target class is already macroable, and it's used in more than one place. Prefer a plain class/service when: the logic is heavy, needs DI (DB, HTTP client), or you want static analysis. A macro is a shorthand, not an architecture.

Gotchas and Tips

It isn't a package — don't pin it. Adding illuminate/macroable to composer.json is either a no-op from the split repo or a version-drift hazard against laravel/framework. If you need the trait, you need illuminate/support, and you already have it. This is the single most common misstep when this "package" gets proposed in a ticket.

Overwrites are silent. macro('foo', ...) twice = last one wins, no warning, no exception. Combined with mixin()'s $replace = true default, a package can quietly redefine your Collection::sum-adjacent helper. Always hasMacro()-guard in app code; always pass replace: false to mixin().

Collision with a future framework method is the real long-term risk. __call only fires when the method doesn't exist. If Laravel ships a real Str::initials() in a later release, your macro becomes dead code and behavior changes with zero errors. Mitigate by prefixing business macros (acmeInitials) or at least keeping a test that asserts the expected output — that test will fail loudly on the upgrade.

$this is bound with the class scope, not just the instance. The closure is bound to static::class, which means a macro can touch protected members it has no business touching. Convenient, and a great way to couple your helper to internals that get refactored. Treat macros as public API of the target class.

Static invocation gives you a throwaway instance. MyClass::someMacro() binds to new static, so any macro reading instance properties ($this->subtotal) will blow up or return nonsense in static context. If a class has a required constructor, don't offer static-style macros on it.

IDE and static analysis blindness. No autocomplete, no @method inference, no unused-method detection. Fix it once, properly:

  • add @method annotations to your own macroable classes,
  • for framework classes, add a .phpstorm.meta.php or a PHPStan baseline/stub, or use a Laravel-aware plugin (Laravel Idea, larastan's macro support),
  • and add a hasMacro() assertion in the test suite so a deleted registration fails CI rather than 500ing in prod.

Octane and long-running workers. Macros live in a protected static $macros array for the lifetime of the PHP process. In Octane, that's the worker's lifetime, not the request's. Never capture request-scoped objects (Request, models, user instances, DB connections) in a macro closure registered at boot — you'll leak memory across requests. Register only stateless closures in boot(); if you genuinely need request data, pass it as an argument.

Queues inherit the same rule. A job running on another worker only has the macro if the provider booted there too. Keep macro registration in boot(), never in a controller, route file, or test bootstrap — otherwise jobs and artisan commands behave differently from HTTP.

mixin() reflects everything visible. It registers all public and protected methods of the mixin class, so a stray helper method or a constructor on your mixin becomes a bogus macro. Keep mixin classes to nothing but closure-returning methods; don't give them constructors or shared state.

Trait conflicts. If a class uses Macroable and another trait with macro()/hasMacro()/__call(), you get a fatal error — resolve with insteadof, and never define your own __call() alongside the trait. You also can't override the trait's methods meaningfully; wrap or compose instead.

Debugging a "method does not exist" that clearly exists. Three usual causes, in order: (1) the provider didn't boot in that context (console vs. HTTP, or a package provider ordering issue), (2) you called it before boot() ran — e.g. inside register() of another provider, (3) the name is already a real method so __call never fires. Check with Class::hasMacro('name') first; it instantly separates 1–2 from 3.

Performance is usually fine, but not for tight loops. Each call does a macro lookup plus a closure bind. That's negligible for request-level helpers and genuinely wasteful if invoked per-row over 100k rows — inline those or use a real collection callback.

Extension point: flushMacros() is your escape hatch. Besides tests, it's how you swap macro sets at runtime (feature flags, multi-tenant behavior differences) — flush, then re-register the tenant's variant inside a scoped block, and flush again in a finally.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify