Архитектура LS:: Мапперы
Представляю первую статью цикла статей об архитектуре livestreet, я думаю будут и продолжения. Исходные коды примера, приведенные здесь доступны на github.
1. Что такое маппер
Ну-с начнем, и начнем с того, что определим, что такое маппер? Маппер, или конкретнее Data Mapper является одним из стандартных паттернов проектирования программных средств, определенный в LS как PHP-объект наследуемый от базового, для всех мапперов класса Mapper. Основным назначением маппера является отделение логики хранения данных от бизнес-логики самого приложения. Маппер содержит методы, реализующие операции CRUD (Create-Read-Update-Delete) над одной или несколькими таблицами по «просьбе» объекта. Маппер является интерфейсом между объектами и базой данных, средством, повышающим уровень абстракции в архитектуре приложения.
Для чего нужен маппер? Прежде чем определим его необходимость и функции, рассмотрим следующие два положения:
Концептуальный уровень предназначен для единого (одинакового) внешнего вида базы данных, общего для всех используемых ее объектов. То есть любой объект LS может напрямую обратиться к базе данных, используя средства PHP с помощью языка SQL – это не сложно, но неправильно потому, что:
2. Реализация маппера в LS
Маппер реализован как часть модуля и физически располагается в папке модуля в подкаталоге mapper. Файл маппера имеет имя: [ИмяМодуля].mapper.class.php. Каждый модуль в LS имеет максимум один маппер. Сам класс маппера реализован как потомок класса Mapper – родительского для всех мапперов. Имя класса маппера строиться по следующему принципу:
Module[ИмяМодуля]_Mapper[ИмяМаппера]
Или, если в составе плагина
Plugin[ИмяПлагина]_Module[ИмяМодуля]_Mapper[ИмяМаппера]
Рассмотрим процесс создания маппера на следующем примере: Пусть есть таблица, хранящая сведения о ранге пользователя, то есть его месте в общем рейтинге пользователей, имеющая следующую структуру:
Путь для реализации функционала по работе с рангом пользователя создан модуль Rang. В таком случае мы будем использовать файл маппера с именем: Rang.mapper.class.php, расположенный в каталоге classes/rang/mapper/. Сам файл будет иметь следующее содержание:
Или, если модуль реализован в составе плагина, то такое:
Если Ваш модуль работает с таблицами БД, то для него должен быть реализован соответствующий маппер.
3. Функционал маппера: реализация операций CRUD
Весь функционал по работе с базой данных реализует объект класса DbSimple_Generic_Database (http://en.dklab.ru), реализованный в файле: lib/external/dbSimple/Generic.php. DbSimple поддерживает следующие СУБД: MySQL, PostgreSQL и InterBase/FireBird, то есть реализация маппера через данный объект позволит без особых сложностей сменить СУБД.
Рассмотрим пример типовых операций CRUD, реализуемых в классе маппера:
3.1. Добавление записи:
Для успешного выполнения запроса в конфигурационном файле общем, или файле config.php плагина должно быть указано имя таблицы, в этом примере так:
Метод $this->oDb->query($sql, $iUserId, $iUserRang) получает первым параметром строку SQL-запроса, остальными параметрами – параметры запроса, в его тексте указанные как ?, в том порядке, в котором эти ? и следуют.
3.2. Чтение записи
Чтение данных реализуется через метод $this->oDb->selectRow($sql, $iUserId), который также получает первым параметром строку SQL-запроса, остальными параметрами – параметры запроса. Метод возвращает массив, содержащий первую строку результата запроса.
3.3. Редактирование записи
Принцип тот же: формируем строку запроса, отправляем ее с помощью метода query.
3.4. Удаление записи
4. Использование маппера
Мапперы используются в модулях с помощью свойства модуля oMapper, представляющий собой объект класса маппера, определенного для этого модуля. Это свойство отсутствует у родительского объекта, поэтому его необходимо создать и инициализировать. На картинке ниже в методе инициализации модуля указан код создания маппера. То есть у объекта модуля, если предполагается использование маппера, он создается и определяется как свойство oMapper, которое доступно стандартным способом: $this->oMapper. То есть сам модуль не обращается к базе данных, он лишь запрашивает данные у маппера, дает ему указания выполнить определенные операции и ничего не знает о механизмах их исполнения. Эти механизмы инкапсулирует маппер, предоставляя модуль лишь интерфейс взаимодействия с БД.
Здесь остается открытым вопрос: можно ли определить несколько мапперов для одного объекта. В составе движка я такого кода не видел, но принципиально это возможно.
Ну и в качестве финального аккорда, в модуле Rang, для которого определен маппер, в методе его инициализации используем все созданные операции так:
1. Что такое маппер
Ну-с начнем, и начнем с того, что определим, что такое маппер? Маппер, или конкретнее Data Mapper является одним из стандартных паттернов проектирования программных средств, определенный в LS как PHP-объект наследуемый от базового, для всех мапперов класса Mapper. Основным назначением маппера является отделение логики хранения данных от бизнес-логики самого приложения. Маппер содержит методы, реализующие операции CRUD (Create-Read-Update-Delete) над одной или несколькими таблицами по «просьбе» объекта. Маппер является интерфейсом между объектами и базой данных, средством, повышающим уровень абстракции в архитектуре приложения.
Для чего нужен маппер? Прежде чем определим его необходимость и функции, рассмотрим следующие два положения:
- Все данные, используемые LS, хранятся в таблицах базы данных, причем в реляционных таблицах, а движок LS написан с использованием объектно-ориентированного подхода.
- Для хранения данных используется MySQL, хотя на самом деле способ хранения данных не должен влиять на работу CMS, не должен определять ее поведение. Объекты, определяющие функционал CMS должны иметь возможность получать необходимые данные вне зависимости от способа организации хранения данных.
Концептуальный уровень предназначен для единого (одинакового) внешнего вида базы данных, общего для всех используемых ее объектов. То есть любой объект LS может напрямую обратиться к базе данных, используя средства PHP с помощью языка SQL – это не сложно, но неправильно потому, что:
- Объект имеет прямой доступ к БД минуя типовые механизмы CMS по взаимодействию с БД, а мапперы и реализуют этот механизм. Следовательно, увеличивается сложность сопровождения такой разработки, поскольку требуется иметь в виду и типовые механизмы и такую «авторскую реализацию».
- Базовый класс маппера содержит проверку операций с данными на ошибки, а прямой доступ к данным приведет к дублированию кода проверки или к сложностям с отладкой.
- При отсутствии маппера приходиться учитывать особенности реализации базы данных в своих разработках, а не только структуру имеющихся данных, что приводит к дополнительным сложностям в разработке.
- При изменении способа организации БД нужно будет менять логику работы дополнения, в случае использования маппера изменению подлежит только он. Следовательно, уменьшается сложность модернизации программного средства.
2. Реализация маппера в LS
Маппер реализован как часть модуля и физически располагается в папке модуля в подкаталоге mapper. Файл маппера имеет имя: [ИмяМодуля].mapper.class.php. Каждый модуль в LS имеет максимум один маппер. Сам класс маппера реализован как потомок класса Mapper – родительского для всех мапперов. Имя класса маппера строиться по следующему принципу:
Module[ИмяМодуля]_Mapper[ИмяМаппера]
Или, если в составе плагина
Plugin[ИмяПлагина]_Module[ИмяМодуля]_Mapper[ИмяМаппера]
Рассмотрим процесс создания маппера на следующем примере: Пусть есть таблица, хранящая сведения о ранге пользователя, то есть его месте в общем рейтинге пользователей, имеющая следующую структуру:
Путь для реализации функционала по работе с рангом пользователя создан модуль Rang. В таком случае мы будем использовать файл маппера с именем: Rang.mapper.class.php, расположенный в каталоге classes/rang/mapper/. Сам файл будет иметь следующее содержание:
Или, если модуль реализован в составе плагина, то такое:
Если Ваш модуль работает с таблицами БД, то для него должен быть реализован соответствующий маппер.
3. Функционал маппера: реализация операций CRUD
Весь функционал по работе с базой данных реализует объект класса DbSimple_Generic_Database (http://en.dklab.ru), реализованный в файле: lib/external/dbSimple/Generic.php. DbSimple поддерживает следующие СУБД: MySQL, PostgreSQL и InterBase/FireBird, то есть реализация маппера через данный объект позволит без особых сложностей сменить СУБД.
Рассмотрим пример типовых операций CRUD, реализуемых в классе маппера:
3.1. Добавление записи:
Для успешного выполнения запроса в конфигурационном файле общем, или файле config.php плагина должно быть указано имя таблицы, в этом примере так:
Метод $this->oDb->query($sql, $iUserId, $iUserRang) получает первым параметром строку SQL-запроса, остальными параметрами – параметры запроса, в его тексте указанные как ?, в том порядке, в котором эти ? и следуют.
3.2. Чтение записи
Чтение данных реализуется через метод $this->oDb->selectRow($sql, $iUserId), который также получает первым параметром строку SQL-запроса, остальными параметрами – параметры запроса. Метод возвращает массив, содержащий первую строку результата запроса.
3.3. Редактирование записи
Принцип тот же: формируем строку запроса, отправляем ее с помощью метода query.
3.4. Удаление записи
4. Использование маппера
Мапперы используются в модулях с помощью свойства модуля oMapper, представляющий собой объект класса маппера, определенного для этого модуля. Это свойство отсутствует у родительского объекта, поэтому его необходимо создать и инициализировать. На картинке ниже в методе инициализации модуля указан код создания маппера. То есть у объекта модуля, если предполагается использование маппера, он создается и определяется как свойство oMapper, которое доступно стандартным способом: $this->oMapper. То есть сам модуль не обращается к базе данных, он лишь запрашивает данные у маппера, дает ему указания выполнить определенные операции и ничего не знает о механизмах их исполнения. Эти механизмы инкапсулирует маппер, предоставляя модуль лишь интерфейс взаимодействия с БД.
Здесь остается открытым вопрос: можно ли определить несколько мапперов для одного объекта. В составе движка я такого кода не видел, но принципиально это возможно.
Ну и в качестве финального аккорда, в модуле Rang, для которого определен маппер, в методе его инициализации используем все созданные операции так:
34 комментария
? — для строковых величин.
для целых есть ?d
раз уж вы пишете статью для новичков (я ведь правильно понимаю), то следует и примеры все приводить точные.
И ответ: В родном маппере User.mapper.class.php в методе
Для поля user_id указан "?", а не "?d" — разработчики могут так делать, значит могу и я. Где я не прав?
Своей головы нет?
«разработчики» — обширное понятие. ЛС дорабатывается (делаются пул реквесты) многими людьми в т.ч. и мною и вполне возможно кто-то не досмотрел и на автомате поставил строковой тип, но это вовсе не означает что следует использовать заведомо неверные способы/методы разработки, игнорируя правила написания кода. Если бы дело было настолько простым что можно было обойтись только ?, то тогда, наверное, не придумали бы других типов…
1. строковый как у вас:
или
2. числовой как нужно:
если число, то нужно использовать ?d
Во всем этом диалоге мне никто не привел контраргументов об ошибочности используемого подхода и о его полной неприменимости. И я немогу понять: все говорят, что так делать нельзя, а почему нельзя — неизвестно. Поверьте, я не считаю этот диалог спором и не считаю свою точку зрения лучшей в нем (исправить в статье "?" на "?d" несложно) я хочу понять — нужно ли?
1. зачем отдавать на откуп БД преобразование типов, если это можно сделать в коде?
2. читаемость кода, видя запросы в маппер сразу понятно, что поле имеет числовой тип
, то ошибки не произойдет и запись добавится для 0-го пользователя, если же использовать "?", то при преобразовании типов в БД будет ошибка и запись не добавится. Мой пример корректен и покрывает Ваши аргументы. Дополнительные методы проверок лишь добавят строки кода.
.
То есть позволяют просто сократить письмо и не используйте дополнительные placeholder-ы. Я еще в самом начале написал, что В статье не ставилось целью рассказывать о работе DbSimple — поэтому используются основные плейсхолдеры, использование которых корректно согласно ОФИЦИАЛЬНОМУ МАНУАЛУ. Но и не буду кривить душой, там еще есть строки:
Но в моей статье я НЕ ИСПОЛЬЗУЮ этого. Я повторю еще раз:
.
Предлагаю закончить спор. Контраргументы к своей линии привел лишь ort и я сними согласился. Но и в моей статье плейсхолдеры используются правильно.
Есть ли похожие статьи про архитектуру?
А тебе не мешало бы почитать классику: Толстого, Достевского, Чехова, ну или Пушкина на крайний случай :)
Ну а если серьезно, где взять подобные мануалы / литературу про мапперы и прочее?