PHPackages                             adrianb93/mixable - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. adrianb93/mixable

ActiveLibrary

adrianb93/mixable
=================

Cleaner mixins for Laravel Macroable classes.

252.4k↓26.5%[3 PRs](https://github.com/adrianspacely/mixable/pulls)PHPCI passing

Since Nov 21Pushed 1y ago1 watchersCompare

[ Source](https://github.com/adrianspacely/mixable)[ Packagist](https://packagist.org/packages/adrianb93/mixable)[ RSS](/packages/adrianb93-mixable/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (4)Used By (0)

⚠️ **This is still in development.** ⚠️

- Finish Mixable tests.
- Macros need to support arguments by reference.
- Blog post.
- Finish that meme.

Mixable
=======

[](#mixable)

Nicer mixins for `Macroable` classes in Laravel.

*\[ TODO: Link to blog post explaining the `Macorable` trait; how it does mixins; and what this package does differently. \]*

[![Macroable Mixins vs. Mixable Mixins](art/meme.jpeg)](art/meme.jpeg)

Installation
------------

[](#installation)

You can install this package via composer:

```
composer require adrianb93/mixable
```

Usage
-----

[](#usage)

There are two traits in this package, `Mixin` and `Mixable`. They make public methods available to `Macroable` classes.

- `Mixin` is used on a plain PHP class. You specify which `Macroable` classes it mixes into.
- `Mixable` is for a subclass of a `Macroable`. It mixes into the `Macroable` class it extends.

### Registering the Macros

[](#registering-the-macros)

You register the mixins in your `AppServiceProvider` like this:

```
// Mixin: You specify which Macroable classes it mixes into.
\App\Mixins\CollectionMixin::mix([
    \Illuminate\Support\Collection::class,
]);

// Mixable: It mixes into the Macroable class it extends.
\App\Models\Builders\Builder::mix();
```

### Mixins

[](#mixins)

`AdrianBrown\Mixable\Mixin` is used on a plain PHP class. It macros public methods into Macroable classes you specify.

**Example Mixin:**

```
namespace App\Mixins;

use AdrianBrown\Mixable\Mixin;

class LoggerMixin
{
    use Mixin;

    /**
     * Logs $this then returns $this.
     *
     * @param array $context
     * @return $this
     */
    public function info($context = [])
    {
        $message = match (true) {
            method_exists($this, 'toSql') => $this->toSql(),
            method_exists($this, 'toArray') => $this->toArray(),
            default => $this,
        };

        logger()->info($message, $context);

        return $this;
    }
}
```

The package will throw an exception if the Mixin is a subclass of the Macroable. It will instruct you to use the `Mixable` trait instead.

**Quick Mixin Facts:**

- The `Mixin` trait is a decorator for the Macroable. Methods calls and attributes gets and sets are possible for private, protected, and public visibility.
- When returning `$this` (Mixin), the registered macro switches the return value to the Macroable.
- A decorator on the most part feels like it is Macroable, but it’s not in it’s scope. If you need to be in the Macroable scope, you can use `$this->inScope($callback)`. Example:

    ```
    LoggerMixin::mix(Collection::class)

    class LoggerMixin
    {
        use Mixin;

        public function whoami(): string
        {
            static::class;
            // => "App\Mixins\LoggerMixin" (the mixin)

            return $this->inScope(function () {
                return static::class;
                // => "Illuminate\Support\Collection" (the macroable)
            });
        }
    }
    ```

### Mixables

[](#mixables)

`AdrianBrown\Mixable\Mixable` is for a subclass of a `Macroable`. It macros public methods into the parent class.

**Example Mixable:**

```
namespace App\Models\Collections;

use AdrianBrown\Mixable\Mixable;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;

class Collection extends EloquentCollection
{
    use Mixable;

    public function whereBelongsTo($related, $relationshipName = null)
    {
        ...

        return $this;
    }
}
```

The package will throw an exception if the Mixable is not a subclass of the Macroable. It will instruct you to use the `Mixin` trait instead.

**Quick Mixable Facts:**

When you call a "Mixable macro":

1. The subclass (Mixable) is instantiated without a constructor.
2. The state of the parent (Macroable) is copied to the subclass (Mixable).
3. The macro’d subclass method is called.
4. The registered macro has the return value:
    1. The state of the subclass (Mixable) is copied to the parent (Macroable).
    2. If the return value is `$this` (Mixable), it is switched to the parent (Macroable).
5. The registered macro returns the value.

When you call a method on an instance of the subclass (Mixable) (not via a "Mixable macro"):

- The `Mixable` trait does nothing. You’re in a normal ol’ instance.

The called method is scoped to the subclass (Mixable) in either case. If parent (Macroable) scope matters, then you can use `$this->inScope($callback)`:

```
\App\Models\Collections\Collection::mix();

namespace \App\Models\Collections;

class Collection extends EloquentCollection
{
    use Mixable;

    public function whoami()
    {
        static::class; // => "App\Models\Collections\Collection" (the mixable)

        return $this->inScope(fn () => static::class);
    }
}
```

When the method is called from a parent instance (Macroable):

```
\Illuminate\Database\Eloquent\Collection::make()->whoami();
// => "Illuminate\Database\Eloquent\Collection"
```

When the method is called from a subclass instance (Mixable):

```
\App\Models\Collections\Collection::make()->whoami();
// => "App\Models\Collections\Collection"
```

If there is no parent (Macroable), `inScope($callback)` will not change the scope of the callback.

### More on Registering

[](#more-on-registering)

**Registering Mixables**

The Macroable class the Mixable extends is what it registers the macros to. You register the Mixable in your `AppServiceProvider` like this:

```
\App\Models\Collections\Collection::mix();
```

**Registering Mixins**

In your `AppServiceProvider`, call the `mix()` function on each class that uses the `Mixin` trait.

```
use App\Mixins\LoggerMixin;

public function register()
{
    LoggerMixin::mix(Collection::class);

    // or

    LoggerMixin::mix([
        Builder::class,
        Request::class,
        Collection::class,
    ]);
}
```

You can also keep the Macroables inside the class which uses the `Mixin` trait. All you would need to do in `AppServiceProvider` is:

```
public function register()
{
    LoggerMixin::mix();
}
```

...and the Mixin can hold the Macroables it should register itself onto:

```
class LoggerMixin
{
    use Mixin;

    public $macroable = Collection::class;

    // or

    public $macroable = [
        Builder::class,
        Request::class,
        Collection::class,
    ];

    ...
}
```

Troubleshooting
---------------

[](#troubleshooting)

### \[Mixable\] My Mixin isn’t returning an instance of the parent (Macroable), it is returning an instance of the child/subclass (Mixable).

[](#mixable-my-mixin-isnt-returning-an-instance-of-the-parent-macroable-it-is-returning-an-instance-of-the-childsubclass-mixable)

A good example of this is an immutable Macroable like `Illuminate\Support\Collection`. Most methods return a new Collection instance. This is not the same instance.

If the return value is not the same instance as the initial Mixable instance, then we do not copy its values to the Macroable instance and we do not swap the return value to the Macroable.

### \[Mixin\] PHP Warning: Indirect modification of overloaded property has no effect

[](#mixin-php-warning-indirect-modification-of-overloaded-property-has-no-effect)

`Mixin` is a decorator meaning it uses the magic methods `__get()` and `__set()` to interact with the Macroable’s class attributes. When passing an attribute to a function that accepts a reference to a value, you run into this warning that the reference is indirect modification.

There are a couple of ways around this issue:

1. Use `$this->inScope($callback)` placing your code within the callback. Property gets and sets within the callback are directly on the Macroable.
2. Copy the attribute to a local variable, then set that local variable to the attribute.

    ```
    $items = $this->items;
    array_walk($items, fn (&$item) => $items = $item * 2);
    $this->items = $items;
    ```

### \[Mixable\] When the subclass constructor has logic that is not getting triggered.

[](#mixable-when-the-subclass-constructor-has-logic-that-is-not-getting-triggered)

A Mixable, when called from a macro/Macroable, instantiates the subclass/Mixable **without a constructor**. The parent’s state is then copied to the child instance.

If you had logic in your constructor that is not getting triggered, then here are some solutions:

1. You could add a `bootMixable()` method to trigger the same setup code you do in your constructor.
2. Override the “in” and “out” methods `Mixable` implements and do it your way. The following example is how to make an eloquent query builder instance using another eloquent query builder instance.

    ```
    protected static function newMixableInstance($parent): self
    {
        // IN: Create an instance of the mixable subclass which has the methods
        //     we mixed into the parent class.
        return (new \App\Models\Builders\Builder($parent->getQuery()))
            ->setModel($parent->getModel())
            ->mergeConstraintsFrom($parent);
    }

    public function newMacroableInstance(): BaseBuilder
    {
        // OUT: Return the macroable instance which the macro was called from.
        //      You could also return `$this` if you're fine with switching
        //      to an instance of the mixable subclass.
        return (new \Illuminate\Database\Eloquent\Builder($this->getQuery()))
            ->setModel($this->getModel())
            ->mergeConstraintsFrom($this);
    }
    ```
3. Use a `Mixin` instead. `Mixable` might not be the right fit for the `Macroable` you’re extending.

### \[Mixin\] \[Mixable\] `static::class` isn’t what I expected.

[](#mixin-mixable-staticclass-isnt-what-i-expected)

- `Mixin` is a decorator of the Macroable. It is a different class.
- `Mixable` is a subclass of the Macroable. It is a different class.

If you need `static::class` to give you the Macroable class, then use `$this->inScope($callback)`.

```
public function whoami(): string
{
    // Before: return static::class;
    return $this->inScope(fn () => static::class);
}
```

### \[Mixin\] \[Mixable\] I passed `$this` to another class and it didn’t match the type hint.

[](#mixin-mixable-i-passed-this-to-another-class-and-it-didnt-match-the-type-hint)

- `Mixin` is a decorator of the Macroable. It is a different class.
- `Mixable` is a subclass of the Macroable. It is a different class.

If you need `$this` to be the Macroable instance, then use `$this->inScope($callback)`.

```
public function notify(): void
{
    // Before: ExampleNoticiation::notify($this);
    $this->inScope(fn () => ExampleNoticiation::notify($this));
}
```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Adrian Brown](https://github.com/adrianb93)
- [All Contributors](../../contributors)

I like to use dedicated query builders and collections for my models. I have a base collection and query builder with some awesome methods. I use the `Mixable` trait on them and created this package because of them. You can learn more about dedicated collections and query builders for eloquent models on [Tim MacDonald's](https://twitter.com/timacdonald87) blog:

- [Follow the Eloquent road](https://timacdonald.me/follow-the-eloquent-road-laracon-talk/)
- [Giving collections a voice](https://timacdonald.me/giving-collections-a-voice/)
- [Dedicated query builders for Eloquent models](https://timacdonald.me/giving-collections-a-voice/)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

28

—

LowBetter than 54% of packages

Maintenance32

Infrequent updates — may be unmaintained

Popularity32

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity28

Early-stage or recently created project

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/334e96531ab0327945c476ec4d8909badd0f648e30f81d7400c728f179ac77ba?d=identicon)[adrianb93](/maintainers/adrianb93)

---

Top Contributors

[![adrianspacely](https://avatars.githubusercontent.com/u/3690710?v=4)](https://github.com/adrianspacely "adrianspacely (18 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (11 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (10 commits)")

---

Tags

laravelmacroablemixin

### Embed Badge

![Health badge](/badges/adrianb93-mixable/health.svg)

```
[![Health](https://phpackages.com/badges/adrianb93-mixable/health.svg)](https://phpackages.com/packages/adrianb93-mixable)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
