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

时间:2020-05-07 21:35:35

标签: php laravel traits

我有一个特征,专门负责哈希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获取数据(据我所知)。 / p>

我知道我可以创建一个扩展Model并使用MyModel extends BaseModel的新类,但是我不是扩展Model类的忠实拥护者。 (希望这不是唯一的方法)

您还有什么其他想法可以解决?

2 个答案:

答案 0 :(得分:2)

您应该使用雄辩的引导程序/初始化程序。

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

您可以在这里注意到我之前解释的逻辑,调用了引导程序,并注册了初始化程序(但尚未调用)。 然后构造函数调用此

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

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

答案 1 :(得分:1)

为什么不在trait中声明一个函数并在Model的构造函数中调用它,如下所示:

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