adrianb93/mixable

为 Laravel Macroable 类提供更干净的混入。

支持包维护!
adrianb93

dev-main 2023-07-03 01:15 UTC

README

⚠️ 这仍然处于开发中。 ⚠️

  • 完成 Mixable 测试。
  • 宏需要支持通过引用传递参数。
  • 博客文章。
  • 完成那个梗。

Mixable

为 Laravel 中的 Macroable 类提供更优雅的混入。

[ TODO: 链接到解释 Macorable 特性、混入方式以及该包如何不同的博客文章。 ]

Macroable Mixins vs. Mixable Mixins

安装

您可以通过 composer 安装此包

composer require adrianb93/mixable

用法

此包中有两个特质,MixinMixable。它们使公开方法可用于 Macroable 类。

  • Mixin 用于普通 PHP 类。您指定将其混入哪些 Macroable 类。
  • Mixable 用于 Macroable 的子类。它将其混入所扩展的 Macroable 类。

注册宏

您可以在 AppServiceProvider 中这样注册混入

// 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();

混入

AdrianBrown\Mixable\Mixin 用于普通 PHP 类。它将公开方法宏扩展到指定的 Macroable 类。

示例混入

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;
    }
}

如果混入是 Macroable 的子类,则包将抛出异常。它将指导您使用 Mixable 特性。

混入快速事实

  • Mixin 特性是 Macroable 的装饰器。方法调用和属性获取和设置对私有、受保护和公开可见性都可行。

  • 当返回 $this(混入)时,注册的宏将返回值切换到 Macroable。

  • 装饰器大部分感觉像 Macroable,但它在它的作用域之外。如果您需要处于 Macroable 的作用域,可以使用 $this->inScope($callback)。示例

    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)
            });
        }
    }

可混入

AdrianBrown\Mixable\Mixable 用于 Macroable 的子类。它将公开方法宏扩展到父类。

示例可混入

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;
    }
}

如果可混入不是 Macroable 的子类,则包将抛出异常。它将指导您使用 Mixin 特性。

可混入快速事实

当您调用 "可混入宏" 时

  1. 子类(可混入)实例化时没有构造函数。
  2. 父类(Macroable)的状态复制到子类(可混入)。
  3. 调用宏的子类方法。
  4. 注册的宏有返回值
    1. 子类(可混入)的状态复制到父类(Macroable)。
    2. 如果返回值是 $this(可混入),则将其切换到父类(Macroable)。
  5. 注册的宏返回值。

当您在子类实例(可混入)上调用方法时(不是通过 "可混入宏")

  • Mixable 特性不会做任何事情。您处于一个普通的实例。

调用的方法在任一情况下都限定于子类(可混入)。如果父类(Macroable)的作用域很重要,则可以使用 $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);
    }
}

当方法从父类实例(Macroable)调用时

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

当方法从子类实例(可混入)调用时

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

如果没有父类(Macroable),则 inScope($callback) 不会更改回调的作用域。

更多关于注册的信息

注册可混入

可宏扩展的类是Mixable,它将宏注册到这个类上。您可以在AppServiceProvider中这样注册Mixable

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

注册混入

在您的AppServiceProvider中,对使用Mixin特性的每个类调用mix()函数。

use App\Mixins\LoggerMixin;

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

    // or

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

您也可以将宏扩展保留在使用Mixin特性的类内部。在AppServiceProvider中,您只需这样做

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

...然后混入可以持有它应该注册自己的宏扩展

class LoggerMixin
{
    use Mixin;

    public $macroable = Collection::class;

    // or

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

    ...
}

故障排除

[Mixable] 我的混入没有返回父类(宏扩展)的实例,而是返回子类/子类(Mixable)的实例。

一个很好的例子是不可变的宏扩展,如Illuminate\Support\Collection。大多数方法都返回一个新的集合实例。这不是同一个实例。

如果返回值与初始宏扩展实例不是同一个实例,则我们不会将其值复制到宏扩展实例,也不会将返回值交换到宏扩展。

[Mixin] PHP警告:间接修改重载属性没有效果

Mixin是一个装饰器,意味着它使用魔法方法__get()__set()来与宏扩展的类属性交互。当将属性传递给接受值引用的函数时,您会遇到这个警告,即引用是间接修改。

有几个方法可以解决这个问题

  1. 使用$this->inScope($callback)将您的代码放在回调中。回调内的属性获取和设置是直接在宏扩展上进行的。

  2. 将属性复制到局部变量中,然后将该局部变量设置为属性。

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

[Mixable] 当子类构造函数中有逻辑没有被触发时。

当从宏/宏扩展调用Mixable时,Mixable会实例化子类/Mixable(没有构造函数)。然后将父类的状态复制到子实例。

如果您在构造函数中有逻辑没有被触发,那么这里有一些解决方案

  1. 您可以在构造函数中添加一个bootMixable()方法来触发相同的设置代码。

  2. 覆盖Mixable实现的“in”和“out”方法,并按您的方式执行。以下是如何使用另一个Eloquent查询构建器实例来创建Eloquent查询构建器实例的示例。

    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. 使用一个Mixin。对于您要扩展的MacroableMixable可能不是合适的选择。

[Mixin] [Mixable] static::class不是我所期望的。

  • Mixin是宏扩展的装饰器。它是一个不同的类。
  • Mixable是宏扩展的子类。它是一个不同的类。

如果您需要static::class提供宏扩展类,则使用$this->inScope($callback)

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

[Mixin] [Mixable] 我将$this传递给另一个类,但它没有匹配类型提示。

  • Mixin是宏扩展的装饰器。它是一个不同的类。
  • Mixable是宏扩展的子类。它是一个不同的类。

如果您需要$this是宏扩展实例,则使用$this->inScope($callback)

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

测试

composer test

变更日志

有关最近更改的更多信息,请参阅变更日志

贡献

有关详细信息,请参阅贡献指南

安全漏洞

有关如何报告安全漏洞,请参阅我们的安全策略

鸣谢

我喜欢使用为模型定制的查询构建器和集合。我有一个基础集合和查询构建器,它们具有一些很棒的方法。我在它们上使用了Mixable特质,并因此创建了此包。您可以在Tim MacDonald的博客上了解更多关于为Eloquent模型定制的集合和查询构建器的信息。

许可证

MIT许可证(MIT)。请参阅许可证文件以获取更多信息。