PHP MVC: шаблон Data Mapper: дизайн класса

У меня есть веб-приложение MVC с объектами домена и данными. Методы класса данных отображает всю логику запросов к базе данных. Я пытаюсь избежать зеркалирования любой структуры базы данных и, следовательно, для достижения максимальной гибкости при построении операторов sql. Итак, в принципе, я стараюсь не использовать любую структуру ORR или ActiveRecord / ВСЕ.

Позвольте мне привести вам пример. Обычно я мог бы иметь абстрактный класс AbstractDataMapper унаследованный всеми конкретными классами сопоставления данных, например класс UserDataMapper . И тогда я мог бы определить метод findById() в AbstractDataMapper , чтобы получить запись конкретных users похожих на таблицу, – с помощью заданного значения id , например идентификатора пользователя. Но это означало бы, что я всегда получал запись из одной таблицы без возможности использования каких-либо левых соединений для получения других данных из других таблиц, соответствующих данному id id.

Итак, мой вопрос: в этих условиях, к которым я сам обязан, должен ли я реализовать абстрактный класс картографирования данных, или каждый класс mapper данных должен содержать свою собственную полностью «проприетарную» реализацию уровня доступа к данным?

Надеюсь, я могу выразить свою идею понятной. Скажите, пожалуйста, если бы я был как-то неясен или у вас возникли вопросы.

Большое вам спасибо за ваше время и терпение.

Если бы я понял вашу мысль …

Имея все ваши конкретные картографы, наследующие SQL от общего класса, есть несколько проблем, которые вы пропустили:

  • имена параметров в объектах вашего домена зависят от имен столбцов
  • в картографах есть «метод выборки», который не имеет соответствующей таблицы
  • у вас все еще есть конфигурация (имя таблицы), ожидаемая суперклассом
  • схема DB должна иметь id как имя для всех столбцов PRIMARY KEY

Теперь я попытаюсь распаковать каждый из них.

Имена параметров и столбцов

Чтобы создать общий findById() , единственным прагматичным подходом является построение его вокруг чего-то вроде этого:

 "SELECT * FROM {$this->tableName} WHERE id = :id" 

Основной проблемой на самом деле является символ подстановки * .

Существует два основных подхода к заполнению объекта с использованием устройства отображения данных: использование сеттеров или использование рефлексии. В обоих случаях «имена» параметров / сеттеров подразумеваются столбцами, которые вы выбрали.

В обычном запросе вы можете сделать что-то вроде SELECT name AS fullName FROM ... , которое позволяет использовать запрос для повторного присвоения имен полям. Но с «унифицированным подходом» нет хороших вариантов.

Каждый картограф может извлекать данные по id ?

Итак, дело в том, что если у вас нет структуры mapper-per-table (в этом случае активная запись начинается, как прагматичная опция), вы получите несколько (реально распространенных) сценариев «крайних случаев» для ваших картографов:

  • используется только для сохранения данных
  • занимается сбором и несингулярными объектами
  • агрегирует данные из нескольких таблиц
  • работает со столом, имеющим составной ключ
  • это фактически не таблица, а представление SQL
  • … или комбинации вышеуказанного

Ваша оригинальная идея будет прекрасно работать в небольшом проекте (с одним или двумя картографами, являющимися «краевым случаем»). Но с большим проектом использование findById() будет исключением, а не нормой.

Независимое воспитание?

Чтобы на самом деле получить этот findById() в суперклассе, вам понадобится способ сообщить ему имя таблицы. Это означало бы, что у вас есть что-то вроде protected $tableName в определении класса.

Вы можете смягчить его, используя abstract function getTableName() в вашем классе абстрактного mapper, которая при реализации возвращает значение глобальной константы.

Но что происходит, когда ваш картограф должен работать с несколькими таблицами.

Мне кажется, что у меня запах кода , потому что информация фактически пересекает две границы (из-за отсутствия лучшего слова). Когда этот код сломается, ошибка будет показана для SQL в суперклассе, в котором не возникает ошибка (особенно, если вы идете с константами).

Именование первичного ключа

Это немного более спорное мнение 🙂

Насколько я могу судить, практика вызова всех первичных id столбцов поступает из разных ORM. Штраф, который он несет, применяется только к читаемости (и обслуживанию кода). Рассмотрим эти два вопроса:

 SELECT ar.id, ac.id FROM Articles AS ar LEFT JOIN Accounts AS ac ON ac.id = ar.account_id WHERE ar.status = 'published' SELECT ar.article_id, ac.account_id FROM Articles AS ar LEFT JOIN Accounts AS ac USING(account_id) WHERE ar.status = 'published' 

По мере того как схема БД растет и запросы становятся более сложными, становится все труднее и сложнее отслеживать, что означает «id» в каком случае.

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

В качестве незначительного бонуса вы получаете синтаксический синтаксис USING() .

TL; DR

Плохая идея. Вы в основном ломаете LSP .