Магия Laravel: динамические where
https://laravelinfo.com
Laravel предоставляет нам возможность получать данные из базы вызывая методы класса Illuminate\Database\Query\Builder
на наших моделях. Сегодня речь пойдёт об одном из таких методов, а именно о динамическом where
. Благодаря нему мы можем выбирать данные, фильтруя их по различным атрибутам нашей модели. Например:
$activeUsers = User::whereActive(true)->get();
$publishedPosts = Post::wherePublished(true)->get();
$activeMenuProducts = Product::whereActiveAndShowInMenu(true, true)->get();
Разберёмся, что за магия здесь происходит и откуда берутся эти методы. Для начала посмотрим в наш класс Illuminate\Database\Eloquent\Model
, так как его наследуют все наши модели. Ничего похожего на методы выше в нём нет, но есть магические методы __call
и __callStatic
:
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return call_user_func_array([$this, $method], $parameters);
}
$query = $this->newQuery();
return call_user_func_array([$query, $method], $parameters);
}
public static function __callStatic($method, $parameters)
{
$instance = new static;
return call_user_func_array([$instance, $method], $parameters);
}
При обращении к несуществующим статическим методам (а в примерах выше именно они и есть) нашей модели, вызывается метод __callStatic
. Он создаёт экземпляр класса в котором мы вызвали метод и пытается вызвать его в нём. Тут в игру вступает метод __call
, потому как и в экземпляре нашего класса тоже нет динамических методов where
. Он создаёт экземпляр класса Illuminate\Database\Eloquent\Builder
в котором также нет нашего динамического where
. Но, зато в нём есть ещё один магический метод __call
, который и приводит нас к конечному классу, который нас интересует, а именно Illuminate\Database\Query\Builder
. В нём, как вы уже наверное догадались, нас ждёт ещё один метод __call
, который и помогает нам использовать динамические методы начинающиеся с where
:
public function __call($method, $parameters)
{
if (Str::startsWith($method, 'where')) {
return $this->dynamicWhere($method, $parameters);
}
$className = get_class($this);
throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
}
Если наш метод начинается с where
, то вызывается метод dynamicWhere
. В нём заботливо написаны комментарии на английском, которые я ниже распишу на русском:
public function dynamicWhere($method, $parameters)
{
$finder = substr($method, 5);
$segments = preg_split('/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE);
$connector = 'and';
$index = 0;
foreach ($segments as $segment) {
if ($segment != 'And' && $segment != 'Or') {
$this->addDynamic($segment, $connector, $parameters, $index);
$index++;
}
else {
$connector = $segment;
}
}
return $this;
}
Дальше происходит различная магия Eloquent для создания и подготовки запроса и тд, но где преобразуются наши динамические where в обычные, мы нашли.