Оргенизация слоя хранения данных это всегда не простая задача, с кучей кейсов, вроде блокировки перезаписи, транзакции, сохранение целостности данных. В какой-то момент появились СУБД и обещали решить эти проблемы, но с ними появились новые - как нам работать с состоянием, которое мы храним? Существует 4 основных подхода для организации рабоыт с базой данных, [Table Gateway](http://martinfowler.com/eaaCatalog/tableDataGateway.html), [Row Data Gateway](http://martinfowler.com/eaaCatalog/rowDataGateway.html), Active Record и Data Mapper. Все эти подходы объеденяет то, что они скрывают от нас базу данных и нюансы работы с ними (в частности SQL). На сегодняшний день самым популярным подходом являются Active Record и Data Mapper, все о них слышали, но для того что бы более полно представлять, как развивалась идея, стоит рассказать и о первых двух. ## Table Data Gateway **Table Data Gateway**, который так же можно встретить под названием DAO (Data Access Object), предоставляет нам объектное представление отдельных таблиц, с доступом ко всем рядам оных. Его основное предназначение - спрятать SQL как деталь реализации, предоставиви простой CRUD интерфейс для работы с таблицами в соответствии с нуждами приложения. Рассмотрим на примере (для удобства я заменил обычный SQL на Database компонент из Laravel с целью использовать тамошний query builder) ```php class UserTableGateway { private $table; public function __construct() { $this->table = DB::table('users'); } public function insert($name, $email, $password) { $this->table->insert(compact('name', 'email', 'password')); } // пароль должен обновляться отдельно от профиля public function updateProfile($id, $name, $email) { $this->table->where('id', $id)->update(compact('name', 'email')); } public function findCoolestUser() { return $this->table->orderBy('rating', 'DESC')->first(); // } } $usersTable = new UserTableGateway(); $usersTable->insert('Sergey', 'fesor@example.com`, password_hash('somepassword', PASSWORD_DEFAULT)); $user = $usersTable->findCoolestUser(); // вернет нам один ряд ``` Основная идея тут - держать все что относится к формированию запросов в одном месте, что бы наш SQL, query builder, или же PDO, не расползался по системе. Не изолировать код от хранилища, а предоставить удобный способ взаимодействия с ним. Возможность инкапсулировать различные запросы в рамках этого подхода позволяет сильно уменьшить вероятность ошибок. Однако работать с "сырыми" данными не очень удобно, обычно мы все же хотим работать со всеми данными как с объектами. ## Row Data Gateway Можно было бы конечно сделать объект, и держать логику хранения данных внутри. Но в таком случае у нас наши сущности будут зависеть от базы данных, и их будет не так уж легко тестировать. Тесты будут требовать подключения к базе данных и от того станут медленными. Для решения этой проблемы, нам нужна прослойка между сущностью и базой данных. **Row Data Gateway**. При этом подходе мы проэцируем отдельные ряды нашей таблицы на объекты, которые служат промежуточным звеном и инкпсулируют все детали о том как сохраняется информация в себе. В итоге наши сущности могут работать через этот gateway используя штатные средства языка программирования. ``` class UserGateway { // сделав свойства публичным я хочу подчеркнуть то, // что этот класс это просто структура данных, состояние без поведения private $id; public $name; public $email; public $password; // еще раз, это проекция таблицы, а не объект бизнес логики public function __constructor(string $id = null) { if ($id) { $this->id = $id; $this->fetchRow(); } else { // если ID не задан, то мы генерируем ноывй $this->id = Cuid::cuid(); } } public function insert() { DB::table($this->tableName)->insert($this->getAttributes()); } public function update() { DB::table($this->tableName)->where('id', $this->id)->update($this->getAttributes()); } private function fetchRow() { $row = DB::where('id', $this->id')->first(); if (!$row) { throw new \UnableToCreateGatewayException(''); } } private function getAttributes() { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'password' => $this->password, ]; } } class User { private $gateway; public function __construct(UserGateway $gateway) { $this->gateway = $gateway; } public function save() { } } ``` Отдельно стоит заметить, что поскольку Row Data Gateway представляет собой отдельную строку таблицы, для выборки нужных рядов нам нужно уже делать отдельный компонент Finder. Как вы можете видеть, с данным подходом мы уже можем добавлять поведение для сущности `User`, работая изнутри с `UserGateway` ## Active Record **Active Record** - это Row Data Gateway, к которому добавили логику предметной области. Дописать... ## Data Mapper AR и Row Data Gateway не очень удобно тестировать. Нам хочется полной независимости от базы данных. ## Object Relation Mapping TODO ## сырой булшит По сути мы работаем с объектами нашей базы данных как с обычными объектами. Разлица лишь в том, что каждый способ предоставляет больше возможностей для изоляции от хранилища. Table Data Gateway заставляет нас работать со всей таблицей целиком, Row Data Gateway - с отдельными рядами таблицы. Active Record является развитием идеи Row Data Gateway но уже позволяет инкапсулировать дополнительное поведение. Data Mapper же вообще не приязан к структуре базы данных, и, так же как и в случае с AR, мы можем примешивать объектам поведение. Поскольку первые два подхода практически не используются, рассмотрим Active Record и Data Mapper. В чем координальное отличие? В рамках Active Record (или Active State) мы работаем с объектами как отображением элементов нашей базы данных, как если бы у нас был прямой доступ к ним без SQL прослойки. В DataMapper мы работаем исключительно с нашими объектами, которые лежат в памяти, и просим отдельную штуку (мэппер) что бы тот синхронизировал состояние объектов в памяти и в базе (сохранил состояние по сути). Доустим у нас есть некий граф объектов, с которым мы работаем в рамках бизнес транзакции. И мы должны сохранить изменения в базу, то есть объект на верху графа и все связанные с ним сущности. Если руководствоваться паттерном "Информационный эксперт", заниматься этим должен тот, кто знает как это делать. В случае AR эта логика выносится прямо в сущности и мы явно задаем в каком порядке что сохранять. В случае же с DM у сущности нет таких знаний и мы выделяем все в отдельный компонент, что дает нам дополнительные варианты как это организовать удобнее для нас. От тупого разруливания графа "руками", до алгоритмов, которые разруливают это автоматически. Как доставать объекты из хранилища - это отдельная темам для разговора, и тот и другой подход никак не это не влияют. Спор AR vs DM - это спор о том кто сохраняет объекты. Это основное различие в подходах. Мэпить любую структуру на любую структуру мы можем и при том и при другом подходе, но только при варианте с Active Record обычно так не делают (что бы сохранить иллюзию прямой работы с базой), так как этот шаблон подразумевает максимально упрощенную реализацию. Именно по этому при использовании AR обычно говорят о тестной связанности с базой, а при DM - слабой (мы вообще от нее не зависим по сути). Остальные нюансы, вроде UnitofWork, Ideniny Map, работа со связями, ленивая инициализация связанных объектов, все это - приблизительно одинаково реализуется при обоих подходах и никакого отношения непосредственно к ActiveRecord или DataMapper не имеет. Но без этого всего сами подходы не предоставляют нам той гибкости, которую мы ожидаем получить от ORM. На негодняшний день в чистом виде в рамках существующих ORM вы можете встретить только DataMapper. ORM на основе Active Record так или иначе внутри используют намного более сложные концепции, дабы упростить разработчикам жизнь и снизить сложность. ## Реализации ORM использующие Data Mapper - Doctrine2 - Самое мощное решение из существующих на данный момент. - [Spot2](https://github.com/vlucas/spot2) - легковесная ORM а базе Doctrine DBAL без магии. - [Analogue](https://github.com/analogueorm/analogue) - легковесая ORM, использующая в качестве основы Laravel Database - [Atlas.Orm](https://github.com/atlasphp/Atlas.Orm) - сырая, но интересная ORM, которая пытается полностью вынести Persistance Model из слоя бизнес логики, но без какой либо магии. TODO ## Реализации ORM использующие Active Record TODO ## Реализации ORM использующие смешанные подходы TODO