репозиторий обычно возвращает значение как инстанс ActiveRecord
Да, часто именно так и происходит. Потому что так удобно.
Тут вопрос приоритетов: либо нам нужно быстро, тогда ActiveRecord, либо нам нужно надолго, тогда делаем модели пассивными, а вся работа с хранилищем идёт через репозитории.
Так и есть. Поэтому, если мы придерживаемся принципов чистого кода, нам приходится изолировать нашу систему от грязного внешнего мира. А для этого абстракции нам в помощь.
Нет. Я говорю о методах контроллера, о тех, которые action. Вызвать один из другого не предполагается. Они должны работать параллельно, вызывая наш ClientClass. Причем функциональность первого action'а должна остаться как есть, а функциональность второго необходимо расширить.
Вообще наверно наоборот должно быть, старый метод должен вызывать новый, который более гибкий, с нужными настройками.
Это как раз будет нарушением принципа открытости-закрытости, поскольку потребует изменения кода старого метода. В рамках темы этой статьи мы хотели бы этого избежать. Поэтому таких жёстких цепочек вызовов методов вообще неплохо бы избегать, потому что при любом расширении системы цепочки вызовов практически неизбежно приходится менять.
Ну так это ж стандартная настройка ActiveQuery, как в любой форме с фильтрами. Тут не требуется писать поверх этого дополнительную абстракцию.
Дополнительная абстракция в нашем случае необходима, чтобы изолировать изменяемую часть кода (применение дополнительных условий к ActiveQuery) от неизменяемой (код ClientClass). Поэтому мы вносим ее в отдельную группу классов filter.
Имеется две точки входа (два метода контроллеров). Один (он был написан раньше) должен продолжать работать, как есть, без изменения логики. Второй, новый, должен, в зависимости от конфигурации, применять дополнительные условия. Принцип открытости-закрытости даёт нам уверенность, что наше нововведение не повлияет на работу старого метода.
Разумеется, можно передавать и массив имен методов, но такой подход менее гибкий. Во-первых, мы жёстко привязываемся к конкретному классу, в котором будем реализовывать методы. Во-вторых, при необходимости настроить параметры вызовов, параметризовывать методы будет несколько сложнее, чем передавать сконфигурированные экземпляры классов. В-третьих, с методами мы теряем возможность контролировать типы, что снижает надёжность кода. И вообще, тут подойдёт любой довод от сторонников ООП, но это уже попахивает холиваром.
Насколько я вижу, это плюс-минус тот же подход, что и в моем примере, только в вашем случае фильтры представлены в виде функций.
Плюсы:
клиентский код полностью отделен от реализации получения данных
Да, как в вашем примере, так и в моем.
в клиентском коде все фильтры имеют человекопонятные названия, т.е. мы мысли доменными категориями, а не категориями реализации
Да. Классы фильтров тоже можно называть человекопонятно.
иногда фильтров может не хватить. Тогда в класс BookFilters придется чего-нить дописать.
А вот эту граблю мы можем обойти, используя ООП: с классами разделение получается несколько лучше. Но с другой стороны, у вас изоляция на уровне функций, у меня — на уровне классов. Так что по сути, повторюсь, ваш пример почти полностью аналогичен моему.
Внешний код — не большая проблема. Мы всегда можем изолироваться от него. А вот поддержание чистоты в собственной кодовой базе — это уже наша ответственность.
Закрытость — она больше в голове, на уровне соглашений.
В данном случае мы делаем удобнее сделать правильно (написать класс фильтра), чем неправильно (лезть с правками в исходный метод). Но, конечно, при большом желании накосячить никто никому не запретит.
как правильно заполнять filters (например фабричный метод)
Тут есть варианты. Например, в нашем проекте, который я брал за основу, фильтры заполняет именно фабричный метод. Можно конфигурировать через конструктор. Можно сделать сеттеры. Можно сделать абстрактную фабрику и т. д.
как защититься от того, что в него можно добавить экземпляр не реализующий этот интерфейс
Тут опять же есть варианты. Метод типа addFilter(ActiveQueryFilter $filter). Либо сеттер с проверкой типов. Либо проверка предусловий перед применением фильтров. Этот вопрос, пожалуй, мог бы протянуть на отдельную статью.
Да, часто именно так и происходит. Потому что так удобно.
Тут вопрос приоритетов: либо нам нужно быстро, тогда ActiveRecord, либо нам нужно надолго, тогда делаем модели пассивными, а вся работа с хранилищем идёт через репозитории.
Нет. Я говорю о методах контроллера, о тех, которые action. Вызвать один из другого не предполагается. Они должны работать параллельно, вызывая наш ClientClass. Причем функциональность первого action'а должна остаться как есть, а функциональность второго необходимо расширить.
Это как раз будет нарушением принципа открытости-закрытости, поскольку потребует изменения кода старого метода. В рамках темы этой статьи мы хотели бы этого избежать. Поэтому таких жёстких цепочек вызовов методов вообще неплохо бы избегать, потому что при любом расширении системы цепочки вызовов практически неизбежно приходится менять.
Дополнительная абстракция в нашем случае необходима, чтобы изолировать изменяемую часть кода (применение дополнительных условий к ActiveQuery) от неизменяемой (код ClientClass). Поэтому мы вносим ее в отдельную группу классов filter.
Имеется две точки входа (два метода контроллеров). Один (он был написан раньше) должен продолжать работать, как есть, без изменения логики. Второй, новый, должен, в зависимости от конфигурации, применять дополнительные условия. Принцип открытости-закрытости даёт нам уверенность, что наше нововведение не повлияет на работу старого метода.
Разумеется, можно передавать и массив имен методов, но такой подход менее гибкий. Во-первых, мы жёстко привязываемся к конкретному классу, в котором будем реализовывать методы. Во-вторых, при необходимости настроить параметры вызовов, параметризовывать методы будет несколько сложнее, чем передавать сконфигурированные экземпляры классов. В-третьих, с методами мы теряем возможность контролировать типы, что снижает надёжность кода. И вообще, тут подойдёт любой довод от сторонников ООП, но это уже попахивает холиваром.
Предполагается, что код клиентского класса в моем примере как раз работает на уровне репозитория.
Насколько я вижу, это плюс-минус тот же подход, что и в моем примере, только в вашем случае фильтры представлены в виде функций.
Да, как в вашем примере, так и в моем.
Да. Классы фильтров тоже можно называть человекопонятно.
А вот эту граблю мы можем обойти, используя ООП: с классами разделение получается несколько лучше. Но с другой стороны, у вас изоляция на уровне функций, у меня — на уровне классов. Так что по сути, повторюсь, ваш пример почти полностью аналогичен моему.
В данном случае мы делаем удобнее сделать правильно (написать класс фильтра), чем неправильно (лезть с правками в исходный метод). Но, конечно, при большом желании накосячить никто никому не запретит.
Тут есть варианты. Например, в нашем проекте, который я брал за основу, фильтры заполняет именно фабричный метод. Можно конфигурировать через конструктор. Можно сделать сеттеры. Можно сделать абстрактную фабрику и т. д.
Тут опять же есть варианты. Метод типа addFilter(ActiveQueryFilter $filter). Либо сеттер с проверкой типов. Либо проверка предусловий перед применением фильтров. Этот вопрос, пожалуй, мог бы протянуть на отдельную статью.