Weave Code
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

Laravel Macroable Models Laravel Package

javoscript/laravel-macroable-models

Adds Macroable support to Eloquent models, letting you define and register reusable model macros for dynamic methods at runtime. Great for extending models cleanly across packages and projects without touching the model class directly.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation Add the package via Composer:

    composer require javoscript/laravel-macroable-models
    

    Publish the config (if needed) and register the service provider in config/app.php under providers:

    Javoscript\MacroableModels\MacroableModelsServiceProvider::class,
    
  2. First Use Case Define a macro on a model to extend its functionality dynamically. For example, add a fullName method to a User model:

    use Javoscript\MacroableModels\Macroable;
    
    class User extends Model implements Macroable
    {
        public static function bootMacroable()
        {
            static::addMacro('fullName', function () {
                return "{$this->first_name} {$this->last_name}";
            });
        }
    }
    

    Now use it in your code:

    $user = User::find(1);
    echo $user->fullName(); // Outputs: "John Doe"
    
  3. Where to Look First

    • Documentation: Check the GitHub repo (if available) or the README.md for examples.
    • Macroable Trait: Review Macroable.php to understand how macros are registered and invoked.
    • Service Provider: Inspect MacroableModelsServiceProvider to see if it adds global macros or modifies Laravel’s behavior.

Implementation Patterns

Usage Patterns

  1. Model-Specific Macros Define macros directly in the model’s bootMacroable() method (as shown above). This keeps logic scoped to the model.

    class Post extends Model implements Macroable
    {
        public static function bootMacroable()
        {
            static::addMacro('isPublished', function () {
                return $this->published_at !== null;
            });
        }
    }
    

    Usage:

    if ($post->isPublished()) { ... }
    
  2. Global Macros Register macros globally in the service provider for reuse across models:

    // MacroableModelsServiceProvider.php
    public function register()
    {
        Model::addGlobalMacro('toArraySafe', function () {
            return $this->toArray() ?? [];
        });
    }
    

    Usage:

    $model = new Model();
    $model->toArraySafe(); // Works on any model
    
  3. Dynamic Macros with Closures Use closures to create flexible macros that accept parameters:

    class Product extends Model implements Macroable
    {
        public static function bootMacroable()
        {
            static::addMacro('applyDiscount', function ($percentage) {
                return $this->price * (1 - $percentage / 100);
            });
        }
    }
    

    Usage:

    $discountedPrice = $product->applyDiscount(20); // 80% of original price
    
  4. Macros for Query Scopes Extend Eloquent queries dynamically:

    class User extends Model implements Macroable
    {
        public static function bootMacroable()
        {
            static::addMacro('scopeActive', function (Builder $query) {
                return $query->where('active', true);
            });
        }
    }
    

    Usage:

    $activeUsers = User::active()->get(); // Equivalent to `User::where('active', true)->get()`
    
  5. Conditional Macros Add logic to macros to conditionally alter behavior:

    class Order extends Model implements Macroable
    {
        public static function bootMacroable()
        {
            static::addMacro('getStatus', function () {
                if ($this->completed_at) {
                    return 'completed';
                }
                return 'pending';
            });
        }
    }
    

Workflow Integration Tips

  • Testing Macros: Use PHPUnit to test macros in isolation:
    public function test_user_full_name_macro()
    {
        $user = new User(['first_name' => 'Jane', 'last_name' => 'Doe']);
        $this->assertEquals('Jane Doe', $user->fullName());
    }
    
  • IDE Support: Annotate macros with PHPDoc to enable autocompletion:
    /**
     * @return string
     */
    public function fullName() { ... }
    
  • Performance: Avoid heavy computations in macros called frequently (e.g., in loops). Cache results if needed:
    static::addMacro('expensiveCalculation', function () {
        return cache()->remember("expensive_{$this->id}", now()->addHours(1), function () {
            return $this->price * 1.2; // Simulate expensive logic
        });
    });
    
  • Migration Safety: Macros are runtime additions—migrations won’t break if macros are removed. Useful for experimental features.

Gotchas and Tips

Pitfalls

  1. Macro Overwriting If two macros share the same name, the last one registered will overwrite the previous. Avoid naming conflicts:

    // Bad: Both define 'isActive'
    static::addMacro('isActive', fn() => true);
    static::addMacro('isActive', fn() => false); // Overwrites the first
    
  2. Scope Macros vs. Global Macros

    • Scope macros (e.g., scopeActive) must follow Eloquent’s naming convention (scope*) to work with query builders.
    • Global macros apply to all models but may lead to unintended side effects if not namespaced carefully.
  3. Late Static Binding in Macros Macros use static:: for method calls, which can cause issues if the model is extended or replaced. Ensure macros are defined in the base model class:

    // Good: Defined in User model
    class User extends Model { ... }
    
    // Bad: Defined in a trait or extended class (may break static binding)
    
  4. Serialization Issues Macros are not serialized with the model. If you serialize a model (e.g., for caching), macros won’t persist. Avoid relying on macros in serialized contexts.

  5. Debugging Macro Calls Macros don’t appear in dd($model) output. Use get_method() to inspect available macros:

    dd(get_class_methods($user)); // Check if 'fullName' is listed
    

    Or log macro invocations:

    static::addMacro('debugMacro', function () {
        \Log::info("Macro called on ID: {$this->id}");
        return $this->id;
    });
    

Debugging Tips

  • Enable Macro Logging: Add a global macro to log all macro calls:
    Model::addGlobalMacro('__debugMacroCall', function ($macroName) {
        \Log::debug("Macro '$macroName' called on " . static::class);
        return $this;
    });
    
    Usage:
    $user->__debugMacroCall('fullName')->fullName();
    
  • Check for Typos: Macros are case-sensitive. Ensure the method name matches exactly when calling:
    // Fails if macro is 'fullName' but called as 'FullName'
    $user->FullName(); // Error: Method does not exist
    
  • Clear Cached Macros: If macros aren’t working, clear Laravel’s compiled classes:
    php artisan optimize:clear
    

Extension Points

  1. Custom Macro Storage Override the macro storage mechanism by extending the Macroable trait or implementing your own macro registry:

    class CustomMacroable
    {
        protected static $macros = [];
    
        public static function addMacro($name, $macro)
        {
            static::$macros[$name] = $macro;
        }
    
        public function __call($method, $parameters)
        {
            if (isset(static::$macros[$method])) {
                return call_user_func_array(static::$macros[$method], [$this, ...$parameters]);
            }
            throw new \BadMethodCallException("Method {$method} does not exist.");
        }
    }
    
  2. Macro Events Trigger events when macros are called (e.g., for analytics):

    static::addMacro('trackMacro', function ($eventName) {
        event(new MacroCalled($this, $eventName));
        return $this;
    });
    
  3. Macro Validation Add validation to macros to ensure they’re used correctly:

    static::addMacro('safeDivide', function ($divisor) {
        if ($divisor === 0) {
            throw new \InvalidArgumentException("Divisor cannot be zero.");
        }
        return $this->value / $divisor;
    });
    
  4. **

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.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky