Как организовать и реализовать разделение проблем и хорошее ООП с помощью растущего списка запросов по всему моему приложению?

Где разместить растущий список запросов (в основном все запросы "SELECT") и как правильно обращаться к ним по всему моему приложению?
Я пытаюсь повторно учесть мое приложение, в котором на данный момент есть запросы, разбросанные по всем службам в моем приложении. Я начинаю сталкиваться с проблемами СУХОГО, когда у меня есть маленькие и большие запросы, которые похожи на другие запросы, которые я уже написал. У меня также есть места, где я снова и снова переписываю одинаковое форматирование для этих запросов. Хотя я не беспокоюсь о замене своего выбора базы данных, я беспокоюсь о дальнейшей оптимизации моих запросов и задаюсь вопросом, сделал ли я это почти невозможным, не организовав все свои запросы лучше и сохранив их где-то в центре. Я хочу следовать хорошей практике OOP и SoC.

Пример того, где у меня в данный момент есть запрос в моем приложении
Например, у меня есть служба, которая предоставляет PDF (или, скорее, контент) всем сотрудникам на определенном мероприятии. У службы есть метод, который принимает некоторые входные данные (например, "сортировка", "фильтр", "предел разбиения на страницы") и будет выполнять запрос с 10 объединениями таблиц (запрос разрезается довольно хорошо вплоть до управляемого числа строк перед большей частью СОЕДИНЕНИЯ работают над этим) и форматируют результаты желаемым способом для PDF.

Что я запланировал до сих пор
У меня возникают проблемы, когда я пытаюсь идеально подумать о потребностях моей базы данных с точки зрения объектов. Я делаю много гибких запросов, не все нерегулярные, но широкий диапазон SELECT с большим количеством JOIN и WHERE, но я стараюсь изо всех сил:

  1. Доменные объекты: я буду создавать классы, такие как usersEntity, eventsEntity (каждая таблица соответствий в моей базе данных) и настройки свойств в них, как id, name, phone,так далее. которые соответствуют полям таблицы базы данных. Я могу создавать геттеры / сеттеры, чтобы я мог мутировать (форматировать такие вещи, как даты и т. Д. Или выполнять проверку). Я мог бы создать сущности, которые на самом деле не представляют собой единую таблицу в моей базе данных, но, возможно, представляют собой общую коллекцию данных, которую я часто использую в своем приложении. Может быть, позвоните usersCollection или же usersEvents,

  2. Data Mapper: поэтому я поместил все операции CUD (Create Update Delete... not Read... кроме, возможно, findById) и сохранил их в классах, называемых usersMapper, eventsMapper, и так далее. Сопоставители принимают только один тип объекта предметной области (см. № 1 выше) и будут обрабатывать постоянство (и, возможно, небольшой набор очень простых операций чтения / выбора, таких как findById($id) или же findByEvent($eventId)).

Теперь это оставляет меня с растущим списком операций чтения (запросы SELECT) и где их разместить?

Варианты, которые мне известны, но не дотягивают. Может быть, я их неправильно понимаю.

Допустим, у меня есть такой запрос

 ВЫБЕРИТЕ users.first_name, users.last_name, events.name, events.date, venues.name,
venue_types.name, GROUP_CONCAT(user_friends.last_name) как "друзья"
ОТ пользователей 
ПРИСОЕДИНЯЙТЕСЬ к событиям ВКЛ (events.id = users.event_id AND events.date =:date)
ПРИСОЕДИНЯЙТЕСЬ и т.д.... 

Так что я вижу в блогах всего сообщества PHP 3 варианта:

  1. Замените мои запросы запросов чем-то вроде вызовов Doctrine2. Их DQL, кажется, не делает вызов повторно используемым? И кажется, что все связано таким образом, что он будет возвращаться как 5 сущностей (на основе JOIN) со всеми свойствами, заполненными для пользователей, событий, мест, типов мест, друзей. Это много полей, которые мне не нужны, я просто хотел по 1 столбцу от каждого, как вы можете видеть в моем запросе выше. Кроме того, я не планирую сохранять какие-либо объекты после такого запроса.
  2. Замените все мои запросы запросов классами репозитория, которые имеют такие методы, как: $someUserRepo-> findNameAndEventNameAndEventDateAndVenueNameAndVenueTypeNameAndFriends(), Помимо действительно сумасшедшего имени метода, я не уверен, что я передаю здесь (условия, такие как: сортировка, ограничение, несколько где?) Или что я возвращаю (массивы? Объекты?).

  3. Замените все мои запросы запросов на вызовы ORM (например, Eloquent Laravel), используя их конструкторы запросов. Я не вижу, как я получил что-то, заменив мой SQL-запрос please->ORM->build->this->for->me->where->andWhere->andWhere->I->dont->know->SQL, Кстати, я почти уверен, что ORM - это анти-паттерн?

Я подозреваю, что это очень сложная проблема для всего сообщества PHP. Или что мне здесь не хватает?

1 ответ

Решение

Использование репозитория - единственный разумный вариант, который вы рассматривали.

Вот что ты мог сделать.

Определить интерфейс репозитория

Этот интерфейс будет описывать, как вы запрашиваете определенные объекты.

interface Events
{
    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, $sort, $limit, array $filters = []);
}

или возможно:

interface Events
{
    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, Query $query);
}

class Query
{
    private $sort;
    private $limit;
    private $filters = [];

    // methods
}

Реализовать интерфейс

Используйте все, что вам нравится для реализации интерфейса - Doctrine, Eloquent, простой SQL - все, что вам удобно.

use Doctrine\ORM\EntityRepository;

class DoctrineEvents implements Events
{
    private $entityRepository;

    public function __construct(EntityRepository $entityRepository)
    {
        $this->entityRepository = $entityRepository;
    }

    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, $sort, $limit, array $filters = [])
    {
        // query $this->entityRepostory and return a collection of Event objects
    }
}

Кроме того, вы можете добавить EntityManager вместо EntityRepository.

Только для того, чтобы показать вам, как легко обеспечить альтернативные реализации, вот реализация SQL:

class SqlEvents implements Events
{
    private $pdo;

    public function __construct(\PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, $sort, $limit, array $filters = [])
    {
        // use $this->pdo to query the database
        // you'll need to convert results into a collection of Event objects 
    }
}

Легко. Для тестов вы можете предоставить реализацию в памяти.

Положитесь на интерфейс

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

class MyController
{
    private $events;
    private $view;

    public function __construct(Events $events, View $view)
    {
        $this->events = $events;
    }

    public function listAction(Request $request)
    {
        $user = // ...

        $events = $this->events->findUserEvents($user, 'ASC', 10);

        return $this->view->render('list.html', ['events' => $events]);
    }
}

В этом примере MyController может работать с любой реализацией интерфейса Events. Это не волнует, если это доктрина, красноречивый или sql.

Выберите реализацию

Мое личное предпочтение было бы использовать Doctrine. Он позаботится о многих вещах, таких как увлажнение объектов или кэширование. Если вам когда-нибудь понадобится написать SQL-запрос вручную для случая, когда вам нужна более высокая производительность, вы всегда можете сделать это для этого конкретного запроса. Не все должно быть реализовано одним способом, и, поскольку вы отделены от реализации, это легко изменить.

Другие вопросы по тегам