Декораторы и цепочки методов

Цель: я хочу "украсить" Laravel Query Builder дополнительными функциями (без непосредственного изменения).

Пример проблемы: я постараюсь сделать это очень кратким. Я реализовал в своем декораторе get метод:

public function get($columns = ['*'])
{
    return $this->cache->get(implode('.', $columns), function () use ($columns) {
        return $this->queryBuilder->get($columns);
    });
}

Я также делегирую все вызовы методов, не реализованных в декораторе, построителю запросов.

public function __call($method, $parameters)
{
    return call_user_func_array([$this->queryBuilder, $method], $parameters);
}

Как и следовало ожидать, при прямом вызове декоратора работает нормально. Но почти все привыкли объединять методы в цепочки при использовании Query Builder.

$queryBuilder = (new CachingDecorator( new QueryBuilder , $app['cache.store'] ));

// get all users
$queryBuilder->from('users')->get();

// get one user
$queryBuilder->from('users')->first(); // <-- delegates to get() internally

Проблема: результаты указанного выше вызова не кэшируются. Очевидно, потому что метод from возвращает экземпляр Laravel Query Builder, а не мой декоратор.

Вопрос. Есть ли какой-нибудь полезный шаблон, который поможет решить эту проблему? Или это ограничение паттерна декоратора?

Моей первой мыслью было попытаться привязать $ this к другому объекту, как это можно сделать в Javascript. Я не думаю, что PHP позволяет это.

Лучшее решение, которое я могу придумать, включает в себя класс для сопоставления объекта построителя запросов с его декоратором (ами) и / или какой-то базовый декоратор, который повторно реализует почти каждый метод в объекте построителя запросов (не поклонник этого один, поскольку он полностью выбрасывает принцип СУХОЙ).

Дополнительные примечания. Я знаю, что могу обойти эту проблему, просто не объединяя вызовы методов в цепочку. Ежу понятно, правда? За исключением того, что неразумно просить каждого разработчика в команде избегать объединения своих вызовов вместе. Я бы предпочел решить эту проблему, чем обойти ее стороной.


person Johnny    schedule 09.11.2015    source источник
comment
Есть ли причина, по которой вы не используете систему кеширования laravels?   -  person jfadich    schedule 10.11.2015
comment
@jfadich Я. $this->cache является экземпляром Illuminate\Contracts\Cache\Repository. То же, что и при использовании Cache::get().   -  person Johnny    schedule 10.11.2015


Ответы (1)


Вы должны вернуть свой декоратор из метода __call:

public function __call($method, $parameters)
{
    $result = call_user_func_array([$this->queryBuilder, $method], $parameters);

    return $result === $this->queryBuilder ? $this : $result;
}

Если вы используете PHP 5.6+, вы можете использовать оператор распространения, чтобы немного исправить это:

public function __call($method, $parameters)
{
    $result = $this->queryBuilder->$method(...$parameters);

    return $result === $this->queryBuilder ? $this : $result;
}
person Joseph Silber    schedule 09.11.2015
comment
(фейспалм) Все так просто и понятно. Ржу не могу. Мне нравится твоя голова. Уверен, что это именно то, что мне нужно. Спасибо чувак! - person Johnny; 10.11.2015
comment
Итак, я понимаю, что возвращает __call(), если внедренный / декорированный метод возвращает декорированный объект, то вместо этого будет возвращен объект current (декоратор). В противном случае результатом будет то, что возвращает метод декорированного объекта. Это сделано для помощи в цепочке, иначе попытка цепочки методов в декораторе завершится неудачей после первого метода в цепочке, так как возвращаемый объект перепрыгнет вверх по декорированному стеку объектов (к корневому объекту). Я правильно понимаю? - person Jason; 30.12.2019