Как Laravel вызывает метод, который не в классе и parrent - PullRequest
1 голос
/ 16 апреля 2019

Интересно, как laravel вызывает метод, которого нет ни в классе, ни в родительском классе.

Например, я посмотрел на класс модели eloquent и никогда не видел в нем метода гидрат . класс модели не имеет родителя, но когда вы вызываете метод гидрата, он заполняет свойство атрибута модели. После поиска источника я обнаружил класс гидратов в классе Builder.php . Так как же laravel удается вызвать этот метод в другом классе? Это какой-то метод инъекции?

1 Ответ

5 голосов
/ 16 апреля 2019

Php имеет магический метод под названием "__call". Когда вы сначала вызываете метод внутри класса, он вызывает метод вызова. Laravel использует этот метод для загрузки другого класса в классе. Таким образом, вы можете смоделировать вызов метода, как если бы вы помещали в него другие методы классов (что-то вроде наследования) Это сложно сделать, если у вас нет надежного способа реализации методов в классах.

В более ранних версиях Laravel этот метод использовался в красноречивом абстрактном классе:

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

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

Видите ли, когда вы вызываете метод гидрата, он копается в классе, а когда метод не существует, он загружает класс построителя запросов и вызывает метод внутри этого класса.

После Laravel 5.5 они меняют эту стратегию и добавляют черту "Illuminate \ Support \ Traits \ ForwardsCalls" . Эта черта имеет метод с именем 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);
}

После этого объяснения я хочу указать на преимущества этого способа кодирования. Допустим, у вас есть модель, которая выходит за пределы eloquent / model. Вы хотите дать возможность разработчикам переопределять метод, а не другие методы, но вы не хотите делать метод закрытым. Модель, например, Модель пользователя расширяется от Eloquent / Model, верно? поэтому он может переопределять все защищенные и публичные методы. Но в этом случае разработчик получает доступ только к методу hydrate (), а не к методу getModels (). потому что в классе строителя у нас есть этот кусок кода:

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

В этом методе Laravel вызывает метод модели класса hydrate (). Но метод hydrate () на самом деле является методом в Builder.php, а не в Model.php. Короче говоря, после того процесса, который я объяснил ранее, разработчик может переопределить метод гидрата, а не, например, метод getModels (). Потому что через Model вызывается только метод гидратов.

Не поймите меня неправильно, я не говорю, что невозможно переопределить getModels () в модели User. Я просто говорю, что вы не можете изменить это через красноречивый процесс извлечения данных. Если вы попытаетесь переопределить метод getModels () в модели User, а после этого, если вы создадите новый экземпляр модели User и попытаетесь вызвать метод getModels () через модель , он будет переопределен. Но он не будет переопределен в модели и не в классе Builder. Вы можете переопределить метод только в Builder, если этот метод вызывается из Model;

...