在特征中使用构造函数的替代方法

Alternative for using constructor in traits

我有一个特质,全权负责Hashing ID

trait Hasher
{
    protected $hasher;

    public function __construct()
    {
        $salt = $this->hashSalt ?? null;
        $length = $this->hashLength ?? null;
        $chars = $this->hashChars ?? null;

        $this->hasher = new Hashids($salt, $length, $chars);

        parent::__construct(); // I hoped this would trigger MyModel's constructor 
                               // but it doesn't work as expected.
     }
}

我尝试在我的模型中使用它。

class MyModel extends Model
{
    use Hasher;

    private $hashSalt = 'Test';
    private $hashChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    private $hashLength = 6;
}

我的想法是让它可以重用,我不想将所有这些 Hasher 逻辑写入我的模型。

构造函数中 parent::__construct(); 的问题导致 MyModel 的构造函数不触发并且 Model 的构造函数尝试从 Hasher 获取数据(据我了解) .

我知道我可以创建一个新的 class 来扩展模型并使用 MyModel extends BaseModel,但我不太喜欢扩展模型 class。 (希望这不是唯一的方法)

对于解决此问题,您还有哪些其他想法?

为什么不在 trait 中声明一个函数,然后像这样在模型的构造函数中调用它:

trait Hasher
{
    protected $hasher;

    public function hash()
    {
        $salt = $this->hashSalt ?? null;
        $length = $this->hashLength ?? null;
        $chars = $this->hashChars ?? null;

        $this->hasher = new Hashids($salt, $length, $chars);
     }
}

然后在模型的构造函数中:

class MyModel extends Model
{
    use Hasher;

    private $hashSalt = 'Test';
    private $hashChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    private $hashLength = 6;

    public function __construct()
    {
        $this->hash();
    }
}

你应该使用 Eloquent bootstrappers/initializers.

trait Hasher
{
    protected $hasher;

    protected function initializeHasher()
    {
        $salt = $this->hashSalt ?? null;
        $length = $this->hashLength ?? null;
        $chars = $this->hashChars ?? null;

        $this->hasher = new Hashids($salt, $length, $chars);
     }
}

通过在名为 initialize{traitName} 的特征中实现一个方法,Laravel 将自动在构造函数中调用它。如果你实现一个名为 boot{traitName} 的静态方法,也会发生同样的事情,它将在你第一次使用你的模型时被调用。这里有完整的解释

Illuminate\Database\Eloquent\Model::__construct

/**
     * Create a new Eloquent model instance.
     *
     * @param  array  $attributes
     * @return void
     */
    public function __construct(array $attributes = [])
    {
        $this->bootIfNotBooted();

        $this->initializeTraits();

        $this->syncOriginal();

        $this->fill($attributes);
    }

这里有两点很重要,bootIfNotBooted调用间接触发了这个方法

/**
     * Boot all of the bootable traits on the model.
     *
     * @return void
     */
    protected static function bootTraits()
    {
        $class = static::class;

        $booted = [];

        static::$traitInitializers[$class] = [];

        foreach (class_uses_recursive($class) as $trait) {
            $method = 'boot'.class_basename($trait);

            if (method_exists($class, $method) && ! in_array($method, $booted)) {
                forward_static_call([$class, $method]);

                $booted[] = $method;
            }

            if (method_exists($class, $method = 'initialize'.class_basename($trait))) {
                static::$traitInitializers[$class][] = $method;

                static::$traitInitializers[$class] = array_unique(
                    static::$traitInitializers[$class]
                );
            }
        }
    }

你可以注意到我之前解释的逻辑,引导程序被调用并且初始化器被注册(但还没有被调用)。 然后构造函数调用this

/**
     * Initialize any initializable traits on the model.
     *
     * @return void
     */
    protected function initializeTraits()
    {
        foreach (static::$traitInitializers[static::class] as $method) {
            $this->{$method}();
        }
    }

这样,之前注册的每个初始化程序都会被调用。