Laravel如何调用不在class和parrent中的方法

dohp0rv5  于 2023-10-22  发布在  其他
关注(0)|答案(1)|浏览(83)

我想知道laravel如何调用一个在类中和父类中都不存在的方法。
例如,我查看了雄辩模型类,但从未在其中看到hydrate方法。模型类没有父类,但是当你调用hydrate方法时,它会填充模型的attribute属性。在挖掘源代码后,我在Builder.phpclass中找到了水合物类。那么laravel是如何设法在另一个类中调用这个方法的呢?是某种方法注射吗?

njthzxwz

njthzxwz1#

PHP提供了一个神奇的方法,名为__call。如果调用了类中不存在的方法,则__call方法被触发。Laravel利用这种机制来模拟从另一个类调用方法,就好像它们是当前类的方法一样,这可以比作继承,但在实现上是不同的。这是一种微妙的方法,需要清楚地理解类中的方法实现。
在以前的Laravel版本中,这是在Eloquent抽象类中使用的:

public function __call($method, $parameters)
{
    if (in_array($method, ['increment', 'decrement'])) {
        return $this->$method(...$parameters);
    }

    return $this->newQuery()->$method(...$parameters);
}

在上面的代码中,如果调用hydrate方法,并且在当前类中找不到它,Laravel会求助于查询构建器类并在那里调用该方法。
从Laravel 5.5版本开始,这个策略被修改了。引入了一个名为Illuminate\Support\Traits\ForwardsCalls的特性。这个trait包括forwardCallTo()方法:

protected function forwardCallTo($object, $method, $parameters)
{
    try {
        return $object->{$method}(...$parameters);
    } catch (Error | BadMethodCallException $e) {
        $pattern = '~^Call to undefined method (?P<class>[^:]+)::(?P<method>[^\(]+)\(\)$~';
        if (!preg_match($pattern, $e->getMessage(), $matches)) {
            throw $e;
        }
        if ($matches['class'] != get_class($object) || $matches['method'] != $method) {
            throw $e;
        }
        static::throwBadMethodCallException($method);
    }
}

forwardCallTo()方法本质上是将一个类的方法调用委托给另一个类。因此,__call方法适用于:

public function __call($method, $parameters)
{
    if (in_array($method, ['increment', 'decrement'])) {
        return $this->$method(...$parameters);
    }
    return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}

以下是这种架构选择的一个关键优势:假设您正在使用一个模型,比如User模型,它是从Eloquent\Model扩展而来的。虽然您可能希望允许开发人员重写某些方法而不将其变为私有,但此结构对可以重写的内容提供了一定程度的控制。
举例来说,虽然User模型可以覆盖Eloquent\Model中的任何受保护或公共方法,但开发人员实际上只能访问和覆盖hydrate()方法,而不是getModels()方法,因为builder类中有以下代码:

public function getModels($columns = ['*'])
{
    return $this->model->hydrate(
        $this->query->get($columns)->all()
    )->all();
}

虽然hydrate()方法主要位于Builder.php而不是Model.php中,但由于__call的魔力,它可以通过Model类访问。
澄清一下,这并不意味着您不能在User模型中重写getModels()方法。可以,而且如果您直接通过User模型示例调用它,它将遵守您的重写。然而,在Eloquent的数据获取过程中,它不会在构建器类中被覆盖,因为它没有通过模型路由。
简单地说,只有通过模型类调用构建器方法时,才可以重写模型中的构建器方法。

相关问题