Search
Write a publication
Pull to refresh

Comments 24

Думаю что Вы не совсем разобрались с этим паттерном. Суть в том, что прокси должен иметь тот же интерфейс как и реальный объект.

Я посоветую доклад Marco Pivetta, с которым он уже неоднократно выступал на конференциях.
По факту, за счет магического метода __call() интерфейс прокси будет автоматически совпадать с реальным объектом. Но проблема может быть в том, что $news в нашем примере стал объектом другого типа, и если где-то в коде есть проверки типа (например instanceof или тип данных в параметрах метода), то эти проверки сломаются. Чтобы этого избежать надо просто наследовать \Cache\Proxy от \Storage, разумеется универсальность класса в этом случае снизится.
они схожи между собой, три шаблона: адаптер, декоратор и прокси. Общая идея — выступить контейнером для реального объекта, а дальше, адаптер — транслирует один интерфейс в другой, декоратор — добавляет новую функциональность без наследования, а прокси сохраняет интерфейс, но между вызовами делает что-то ещё, кэширует, контролирует доступ и т.д.
Я подразумевал тип в том числе.
Позвольте немного комментариев:

Если уж говорить о «решении в лоб», то достаточно встроить кэш на пути к Storage для всех сущностей.

1. Смешивание логики.

Его не будет, если главной сущностью News сделать класс, отвечающий за возврат данных о новостях. А в нем уже делегировать запрос в Storage или Cache (или куда угодно). Причем эти Storage и Cache лучше всего вообще сделать сервисами. Т.е. раз уж у нас возникла ситуация, что хранилищ стало больше, чем 1, то от них нужно абстрагироваться.
Очень высокая трудоемкость (добавить 50 новых методов, в нашем примере) + заменить везде в приложении вызовы старых методов, на новые, а если в будущем придется кэширование выпиливать, то еще и повторить все действия назад.

В вашем примере, везде в приложении, менять старые вызовы придется точно так же — это плата за плохую изначальную абстракцию. И если нет желания переделывать нормально, то лучше просто воспользоваться моим «решением в лоб».

И да, прокси должен быть того же типа — иначе ваш объект \Storage\News нельзя будет передать, как параметр, а это означает, что и создание его через конструкцию «new \Storage\News» вам не нужно, в таком случае, лучше воспользоваться Service Locator.
Большое спасибо за комментарий.
достаточно встроить кэш на пути к Storage для всех сущностей.

но тогда мы лишаемся возможности обратиться к живым данным минуя кэш?
но тогда мы лишаемся возможности обратиться к живым данным минуя кэш?

Нужно понять, кто способен отвечать за эту возможность:
1. Например, отдаем страницу полностью из живой базы. Тогда просто отключаем кэш для всех (пресловутый параметр nocache).
2. Сама сущность (News) регулирует когда отдавать живую или кэш. Для этого контроллеры не должны знать о способах хранения модели (как я описала в пред. комментарии).
3. Контроллер запрашивает данные именно этой сущности и требует, чтобы не было кэша. С трудом представляю почему контроллер должен решать это. Если это какой-то исключительный случай, можно выключить кэш до вызова сущности и включить после.

Если у вас в контроллере нужно часто решать — использовать кэш или нет, то давайте рассмотрим вашу задачу подробнее — мне в голову не приходит зачем это может быть нужно.
Если у вас в контроллере нужно часто решать — использовать кэш или нет, то давайте рассмотрим вашу задачу подробнее — мне в голову не приходит зачем это может быть нужно.

это не конкретный реальный проект, сборки из разных опытов.
Если для конкретики, например, есть CMS frontend и backend, которой используют один слой модели (коробочное решение, залил на хостинг и работает) при этом frontend должен работать через кэш, а backend с живыми данными, как в этом случае быть?
Ну отличный пример, разделение на кэш/не кэш ведь не на уровне конкретной сущности, значит и настраивать можно разом для всех.

if (frontend)
включить кэш для всех
else
выключить
И тут я вспоминаю статьи про АОП ( Аспектно ориентированное программирование ) там это делается вообще в виде нотации в phpdoc
вот не знаю, может я не в тренде, но претит как-то идея программировать на комментариях, на субъективном уровне
АОП как по мне имеет смысл только в контексте контрактного программирования. Использовать его для логгирования и/или кеширования можно, но лучше обернуть все в прокси или в декоратор.
кто минусует, напишите в комментариях что-нибудь по делу, всем же будет полезно.
Лично мне Ваш способ показался тем же решением «в лоб», только не в модели, а в контроллере. Open/Closed принцип такой подход тоже кстати нарушает. Люди, пишущие работающие проекты тоже вряд ли увидили в статье открытие Америки.
habrahabr.ru/post/240557/#comment_8068645 — этот способ имхо намного грамотнее
Какой смысл кеширование или логирование делать такими вещами? Прокси намного лучше и намного более явно.
Если вы внимательно посмотрели данную статью, то должны были заметить, почему прокси не решает вопрос с кэшированием правильно: нарушается принцип LSP, так как прокси не является потомком исходного класса. Если же делать правильный прокси или декоратор чтобы его можно было подсунуть вместо оригинального класса, то это потребует генерацию однотипного кода для каждого кэшируемого метода, а в случае с прокси, еще и для всех методов.

Также остается вопрос с тем, что такие прокси надо обновлять при добавлении новых методов и т.д. а это можно делать с помощью спец. софта, например ProxyManager. Надеюсь, вы с этим фактом согласны? Если да, то обрадую вас — АОП в данном случае ничем не отличается, и генерит те же самые прокси, но оптимизированные. И они точно такие же явные классы прокси, как прокси сгенерированные любым другим способом.

Более того, делать кэширование и логирование правильно с помощью АОП, так как это специальный инструмент для решения проблем с сквозной функциональностью.
Если же делать правильный прокси или декоратор чтобы его можно было подсунуть вместо оригинального класса, то это потребует генерацию однотипного кода для каждого кэшируемого метода, а в случае с прокси, еще и для всех методов.
Зачем? в этой статье как раз приводится работающий пример, который опровергает это утверждение.
С типом — да, если у вас производится где-то проверка типа, прокси придется наследовать от реального объекта. Но здесь есть два но: во-первых, далеко не всегда есть проблема с проверкой типа, например, когда вы создаете локальную переменную, которая уничтожается, сразу после выхода из метода, обернуть её универсальным прокси быстро и удобно. Во-вторых, если проверка типа всё таки имеется, то как правило проверяется не конкретный тип, а базовый и опять же надо сделать всего один класс прокси наследующий — наследующий от базового типа.

Также остается вопрос с тем, что такие прокси надо обновлять при добавлении новых методов
не надо! в этом и был смысл статьи, в PHP для этого есть магический метод __call()

Более того, делать кэширование и логирование правильно с помощью АОП,
Мэтт Зандстра описывает, как использовать для этого Decorator.
LSP нарушается только при наследовании, а генерация однотипного кода не проблема. Мне не нравится подход с __call и я бы предпочел именно генерацию кода. Благо инструменты для этого есть.

Мне просто не нравится сама идея АОП в контексте PHP и конкретно в вашей реализации. Не поймите не правильно, это мое личное мнение и вы вправе не согласиться. Как по мне использование аннотаций в phpdoc для таких критичных вещей как кеширование или логирование, проверка аунтефикации пользователей и т.д. это не слишком хорошая идея. Как минимум этот код невозможно будет прогнать через анализаторы, повышается вероятность ошибок и т.д. Для простых проектов и для быстрой разработки возможно это сойдет, но повышаются риски. Единственный пример применения вашей библиотеки это контрактное программирование.

Что до «решения проблем со сквозной функциональностью» я согласен. Но взгляните на какой JAspect и вашу реализацию.

В любом случае кеширование при помощи прокси я бы реализовывал в том случае, если бы имел дело с библиотеками сторонних разработчиков, которые не позаботились об этом. В этом случае менять код библиотеки не стоит, а сделать прокси — не проблема. В других случаях я бы не стал вообще делать подобного. Пробовал, всегда всплывают проблемы при усложнении логики.
Как по мне использование аннотаций в phpdoc для таких критичных вещей как кеширование или логирование, проверка аунтефикации пользователей и т.д. это не слишком хорошая идея. Как минимум этот код невозможно будет прогнать через анализаторы, повышается вероятность ошибок и т.д.

Согласен на 1000%, не понимаю откуда пошла такая мода писать бинес-логику в комментариях, Обфускацию кода не сделаешь, с opcache разных производителей бывают проблемы (opcache.save_comments и opcache.load_comments) ну и Reflection использовать в каждом запросе на нагруженных проектах — очень сомнительная вещь.
opcache не проблема, я сам аннотациями пользуюсь но только для конфигурации. По сути goaop использует аннотации для конфигурации и потом генерит те же прокси насколько я понял, так что проблем с производительностью нету. Но тогда это ни разу не АОП.
Кажется, вы разобрались как это работает ) И это как раз и есть АОП, все происходит прозрачно, классы и методы декорируются без единой правки в исходном коде. Но все это заточено под максимальную производительность — никаких runtime-проверок и рефлексии в коде, поддержка опкод-кэшеров в боевом режиме (когда весь код отдается сразу из памяти, без ненужных eval-ов и прочей ерунды).

Специально для тех, кто не любит аннотации, есть возможность писать советы с помощью замыканий. Так что выбор за разработчиком. Аннотация — это только возможность удобной конфигурации. Можно также скрестить с symfony/config и грузить хоть из YAML, хоть из PHP, хоть из XML.
Sign up to leave a comment.

Articles