Процедурный код или наследование? :)

SiZE

Новичок
Меня мучает вопрос про наследование методов с запросами к БД...

Бывают модели со сложными запросами на несколько экранов, с джойнами на 100 таблиц, в которых может разобраться только автор запроса. Я запишу покороче для наглядности.
PHP:
class Model extends BaseModel {
  public function getInformation(){
     $sql = "SELECT id, title, date FROM table WHERE data>NOW() ORDER BY date";
     $result = $this->db->findAll( $sql );
     return $result;
  }
}
И тут приходит задача, что в этом запросе, для города Москва, например, надо сделать сортировку не по дате, а по названию.

В рамках проекта реализовано наследование модели для города. И вот тут возникает дилемма.

Процедурить
PHP:
class Model extends BaseModel {
  public function getInformation(){
     if ( REGION == 'moscow' ) {
        $orderby = 'title';
     } else {
        $orderby = 'date';
     }
     $sql = "SELECT id, title, date FROM table WHERE data>NOW() ORDER BY ".$orderby;
     $result = $this->db->findAll( $sql );
     return $result;
  }
}
или ооопэшить?
PHP:
class Model_Moscow extends Model {
  public function getInformation(){
     $sql = "SELECT id, title, date FROM table WHERE data>NOW() ORDER BY title";
     $result = $this->db->findAll( $sql );
     return $result;
  }
}
Т.е. в первом случае мы получаем абсолютно нерассширяемую и неудобочитаемую конструкцию. Но которую легко поддерживать в будущем, т.е. правки происходят в одном месте и если общий функционал измениться, не надо помнить о том что его надо еще поправить и для каждого города.

А во втором случае все ясно и понятно, но если общая логика измениться, я должен вспомнить, что у меня есть дочерняя модель, в которой мне надо тоже что-то поправить. А таких моделей может быть много и придется тогда тестировать функционал в каждом городе.

Что выбрать? Может есть какой третий вариант?
 

Вурдалак

Продвинутый новичок
У тебя неправильное понимание слова «модель».

Если у тебя появляются сложные запросы, то тебе нужен DataMapper. И, возможно, Criteria pattern.

Допустим, твоя модель — это «Статья» (Article). Тогда это выглядело бы как-то так:
PHP:
$criteria = Criteria::create()->where(Criteria::expr()->lte('date', $this->clock->getNow()));

if ($currentRegion->getId() === Region::MOSCOW_ID) {
    $criteria->orderBy(['title' => Criteria::ASC]);
} else {
    $criteria->orderBy(['date' => Criteria::ASC]);
}

$articles = $articleMapper->findByCriteria($criteria);
 
Последнее редактирование:

SiZE

Новичок
Я опустил все подробности с конструктором запросов, потому что мой вопрос относился к этой части:
PHP:
if ($currentRegion->getId() === Region::MOSCOW_ID) {
    $criteria->orderBy(['title' => Criteria::ASC]);
} else {
    $criteria->orderBy(['date' => Criteria::ASC]);
}
А она все равно получается процедурной. Множество if для каждого региона.
 

Вурдалак

Продвинутый новичок
Множество if для каждого региона.
Потому что так выражается бизнес-логика, в ней есть условия для «каждого региона». Своё «наследование» без явного или неявного if/switch ты тоже не сделаешь: тебе нужно будет как-то определить, что для такого-то региона нужен такой-то класс. Другое дело, что само определение Criteria для каждого региона можно инкапсулировать каждый в свои классы (MoscowArticleCriteriaProvider и т.д.) и сделать фабрику, которая по region id отдаст нужный ArticleCriteria, дегелировав её одному из таких criteria provider'ов:
PHP:
$articles = $articleMapper->findByCriteria($articleCriteriaFactory->createByRegion($currentRegion));
 
Последнее редактирование:
  • Like
Реакции: SiZE

Активист

Активист
Команда форума
PHP:
<?php
class foo_base {

    public function getSqlOrder()
    {
        return "date";
    }

    public function getInformation()
    {
        $sql = "SELECT id, title, date FROM table WHERE data>NOW() ORDER BY ".$this->getSqlOrder();
        return $this->db->findAll( $sql );
    }
}


class foo_top extends foo_base {

    public function getSqlOrder()
    {
        return "title";
    }
}
В зависимости от того, какой отображается регион, вид использует нужную модель. Нужную модель отдает фабрика. Фабрика - абсолютно свободный по реализации класс, сегодня как критерий вы используете регионы, завтра количество просмотров или скажем федеральный округ, в общем полная свобода действий.
 
Последнее редактирование:
  • Like
Реакции: SiZE

fixxxer

К.О.
Партнер клуба
PHP:
abstract class AbstractUserMapper {

     public function get() {
           $userRow = $this->db->getRow("SELECT * FROM users WHERE id = ?", $this->getId());
           return $userRow ? new User($userRow) : null;
     }

     abstract protected function getId();

}

class User1Mapper extends AbstractUserMapper {

     protected function getId() {
         return 1;
     }

}
!!1111
 

Активист

Активист
Команда форума

Вурдалак

Продвинутый новичок
Я обо всем слышал, и многое использовал, причем не только в PHP и в очень сложных приложениях.
SOLID — это не библиотека, которую время от времени используют, это фактически критерий правильности кода с точки зрения ООП. Так вот, регулярно нарушать SOLID и постоянно употреблять слова типа «фабрика», «модель» и другие около-программерские слова — это какой-то культ карго. Или даже «программерское фричество».
 
Последнее редактирование:

Активист

Активист
Команда форума
SOLID — это не библиотека, которую время от времени используют, это фактически критерий правильности кода с точки зрения ООП. Так вот, регулярно нарушать SOLID и постоянно употреблять слова типа «фабрика», «модель» и другие около-программерские слова — это какой-то культ карго. Или даже «программерское фричество».
Рекомендую от корки до корки книгу 4-ки, что бы не было каши в голове, и крайне желательно реализовать примеры на чем-то ином, чем скриптовый язык для web.
 

Absinthe

жожо
Рекомендую от корки до корки книгу 4-ки, что бы не было каши в голове, и крайне желательно реализовать примеры на чем-то ином, чем скриптовый язык для web.
Я думаю, что обвинять Вурдалака в каше в голове - свинство. Его можно обвинять в фанатизме энтерпрайз-подхода (даже для не энтерпрайз-задач) и упрямстве, но не в отсутствии знаний.
 

Активист

Активист
Команда форума
Я думаю, что обвинять Вурдалака в каше в голове - свинство. Его можно обвинять в фанатизме энтерпрайз-подхода (даже для не энтерпрайз-задач) и упрямстве, но не в отсутствии знаний.
Причем тут метод отбора однотипных объектов ( указанный Criteria pattern ), ORM архитектура (он был назван Data Mapper) когда автор спрашивает "как мне быть, что бы изменять базовый SQL запрос костыли в виде IF или все же заюзать что-то иное.". Т.е. речь идет об изменении алгоритма, и тогда используют стратегию.
 

fixxxer

К.О.
Партнер клуба
Активист, стратегия как раз тут и полезна. В качестве criteria builder. Можно в стратегию унести и весь код - создание маппера, формирование criteria и возврат коллекции - но стратегия только на criteria гибче, т.к. позволит добавить любые дополнительные условия, не зависящие от региона, не загаживая реализацию стратегии ерундой. Наследование же моделей в таком виде выглядит глупо - я это наглядно выше продемонстрировал через reductio ad absurdum; и к strategy pattern таковое наследование никакого отношения не имеет.

А еще странно, что никто не обратил внимание на "getInformation". Это же жесть! :)
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Активист, чувачок, Criteria может маппиться на SQL-код. Вручную или автоматически. У автора проблема формирования критерия выборки, который зависит от региона пользователя и ещё хрен знает чего.
 
Последнее редактирование:

Активист

Активист
Команда форума
Активист, чувачок, Criteria может маппиться на SQL-код. Вручную или автоматически. У автора проблема формирования критерия выборки, который зависит от региона пользователя и ещё хрен знает чего.
Ни в встречал (кроме как у PHP разработчиков) случаи использования паттерна критерия (который применяется к объектам , пулу объектов) для задачи подбора правильного SQL. Порой тихо ... от того, что натворят в приложениях, лишь бы использовать.
 

Активист

Активист
Команда форума
Активист, стратегия как раз тут и полезна. В качестве criteria builder. Можно в стратегию унести и весь код - создание маппера, формирование criteria и возврат коллекции - но стратегия только на criteria гибче, т.к. позволит добавить любые дополнительные условия, не зависящие от региона, не загаживая реализацию стратегии ерундой. Наследование же моделей в таком виде выглядит глупо - я это наглядно выше продемонстрировал через reductio ad absurdum; и к strategy pattern таковое наследование никакого отношения не имеет.
А еще странно, что никто не обратил внимание на "getInformation". Это же жесть! :)
Вам не кажется что это гараздо сложнее, чем заюзать стратегию в ее общепринятом смысле? А если бы у вас в приложении было бы больше одной интерации? Вы бы ... с таким кодом утечки ловить.
 

Вурдалак

Продвинутый новичок
Почитай про persistence ignorance. По большому счёту нам должно быть похер на хранилище: будь это это InMemoryFooRepository, NativeMysqlFooRepository, HandlerSocketFooRepository. Нам важно, что у нас есть интерфейс:
PHP:
interface FooRepository {
    /**
     * @return Foo
     */
    public function findById($id);

    /**
     * @return Foo[]
     */
    public function findByCriteria(FooCriteria $criteria);
}
Необязательно по хардкору реализовать аналог Criteria из какого-нибудь Hibernate, но всегда можно написать тупой класс типа FooCriteria для сущности Foo, на основании которого мы с помощью QueryBuilder в DataMapper будет формировать запрос. Хранилищу должно быть похер на бизнес-требования типа региона, а бизнесу похер на устройство хранилища.
 

Активист

Активист
Команда форума
Какое еще хранилище? В зависимости от региона нужно выбрать иной алгоритм. Все. Criteria применяется для пула объектов. Если вы называете класс Criteria и пишите туда некий код, он от этого не становится паттерном Criteria.
 
Сверху