ORM: разное
В этой статье собраны некоторые не популярные и не совсем очевидные вещи про ORM.
SPAWN
Метод spawn у маппера порождает новый элемент динамической цепочки или создает дочерний маппер. Звучит непонятно, поэтому рассмотрим примерную реализацию этого метода:
public function spawn() { return new self($this); } public function __construct($parent = null) { $this->parent = $parent; }
Таким образом, маппер содержит ссылку на родительский маппер от которого он был порожден. Все это необходимо для двух вещей:
- во-первых это позволяет мапперу считывать опции родительского
- во-вторых это позволяет добавлять опции, или параметры выборки не модифицируя родительский маппер
Рассмотрим пример:
$users = $users_mapper->where('id > 5 AND id < 10')->select(); $id = 1; $admin = $users_mapper->find($id);
В этом случае $admin будет содержать пустое значение, т.к. ранее мы модифицировали маппер и добавили в него условие. Чтобы все работало корректно нужно вызвать метод spawn
$users = $users_mapper->spawn()->where('id > 5 AND id < 10')->select(); $id = 1; $admin = $users_mapper->find($id);
Методы для работы с дочерними и родительскими элементами
Родительский маппер доступен по свойству parent.
Если вызвать метод immutable, то любые попытки модифицировать запрос с помощью методов маппера будут приводить к насильному вызову метода spawn. Например, если при настройке маппера в setup методе вызвать immutable, то предыдущий пример с выборкой пользователей будет работать корректно, т.к. перед методами where и find неявно будет вызван метод spawn.
Корневой маппер доступен по свойству __root, обычно он совпадает с CMS::orm().
Для получения "чистого" маппера без дополнительных условий и опций необходимо вызвать метод clear.
$mapper = CMS::orm()->users->spawn()->where('id > 5')->clear();
В предыдущем примере в переменную $mapper будет сохранен CMS::orm()->users без условия 'id > 5'.
MAP
Для группировки условий выборки и придания им осмысленных названий в маппере принято определять методы с префиксом map_. В дальнейшем этот метод можно вызвать без указания префикса, тогда автоматически вызовется метод spawn. Желательно оформлять таким образом все выборки, тогда обращения к мапперу выглядит осмысленно:
$users = CMS::orm()->users->active->for_project($project)->with_avatar->select();
Как видно из примера, мы можем вызвать свойство active, если в маппере определен метод map_active, но мы также может вызвать метод active(). Если вы хотите реализовать разную логику работы при вызове свойства или метода, то необходимо определить метод cmap_active для метода и pmap_active для свойства.
Для проверки существования map-методов можно вызвать соответствующие методы can_map($method_name), а также can_cmap и can_pmap.
Метод downto позволяет вызвать сразу цепочку map-методов
CMS::orm()->downto('active/with_avatar'); // это аналогично CMS::orm()->active->with_avatar;
Существует возможность динамического добавления map-методов. Рассмотрим пример:
$mapper = $CMS::orm()->users->spawn(); $CMS::orm()->users ->map('for_project', function($project) use($mapper) { return $mapper->where('project_id= :pid', $project->id()); }); $project_users = $CMS::orm()->users->for_project($project)->select();
В случае использования php 5.4 и выше можно избавится от использования use
CMS::orm()->users ->map('for_project', function($project) { return $this->where('project_id= :pid', $project->id()); }); $project_users = $CMS::orm()->users->for_project($project)->select();
Если метод map_for_project уже существует в базовом маппере, то динамический вариант переопределит его.
IN
У маппера есть методы in и not_in с одинаковым синтаксисом
$ids = range(1, 7); CMS::orm()->users->spawn()->in('id', $ids);
LIKE
У маппера есть метод like
CMS::orm()->users->spawn()->like('name', "Вася%"); CMS::orm()->users->spawn()->like(array('name', 'nick', 'login'), "vasya%");
Несколько колонок в запросе объединяются через OR.
SEARCH
У маппера есть метод search, который можно переопределить при необходимости. Кроме этого, если определить в маппере map_search, то будет вызван именно он. По умолчанию метод search делает like по колонкам определенным в search_by. Обычно search_by вызывается в setup
// в классе маппера public function setup() { $this ->table('users') ->search_by('name') ... ; return parent::setup(); } CMS::orm()->users->spawn()->search("Вася");
LOOKUP
Выбрать строку по первичному ключу можно различными способами:
$id = 1; CMS::orm()->users->find($id); CMS::orm()->users[$id]; CMS::orm()->users->load($id);
Все эти методы идентичны и кроме непосредственно загрузки записи из таблицы они запоминают результат и при последующем обращении запись не будет заново выбираться из таблицы.
Иногда может быть удобно использовать при такой подгрузке не только значение первичного ключа. Например в случае тегов было бы удобно использовать название тега.
// в классе маппера public function setup() { $this ->table('tags') ->lookup_by('name') // имя колонки по которой осуществляется загрузка ... ; return parent::setup(); } CMS::orm()->tags["Путин"]; // идентично следующему CMS::orm()->tags->lookup("Путин");
BINDS
Существует несколько вариантов передачи параметров в where условие. Рассмотрим на примере:
CMS::orm()->users->spawn()->where('id = :id', 5); CMS::orm()->users->spawn()->where('id = :id AND status > :status', array(5, 2)); CMS::orm()->users->spawn()->where('id = :id AND status > :status', array('id' => 5, 'status' => 2)); $user = CMS::orm()->users->make_entity(array('id' => 5, 'status' => 2)); CMS::orm()->users->spawn()->where('id = :id AND status > :status', $user);
as_array
Не секрет что обертка над данными из БД в виде DB_ORM_Entity может тормозить, особенно если грузится стразу много строк и таблицы. Уходит время на создание, на вызов методов offsetSet и многое другое. Чтобы избежать этого при необходимости можно вызвать у маппера метод as_array, тогда результат будет возвращен в виде обычного массива
$users = CMS::orm()->users->spawn()->like('nick', "%petr%")->as_array()->select(); foreach ($users as $id => $user) { var_dump($user); // array }
Entity: доступ к значениям
В базовом классе DB_ORM_Entity все значения из БД хранятся в массиве attrs, который можно запросить напрямую
var_dump(CMS::orm()->users[1]->attrs);
Доступ к значениям осуществляется с помощью двух разных интерфейсов: индексного [] или через свойства ->. Подразумевается что через индексные свойства считываются данные как есть из БД. А доступ через свойства является более высокого уровня. Например, в таблице хранится путь к файлу в колонке file. Тогда для entity может быть организованно следующее:
// entity класс public function get_file() { return IO_FS::File($this->attrs['file']); } $row = $mapper->find(1); $row['file']; // путь к файлу -- строка $row->file; // объект файла $row->file->copy('/tmp/test.dat');
Доступ к свойствам entity можно переопределять с помощью методов get_ set_, а доступ к индексным значениям через методы row_get row_set.
raw_insert|update|delete
При вставке, обновлении и удалении записей из таблицы маппер осуществляет множество проверок, вызывает методы after_ и before_, access и validate. Иногда требуется просто вставить запись в таблицу без всех этих дополнительных операций. В этом лучае можно воспользоваться методами в префиксом raw_: raw_insert, raw_update, raw_delete.
validator
У маппера есть опция validator и метод validate. При вставке, обновлении записей маппер вызывает метод validate (его можно переопределить), который по умолчанию берет валидатор из опции и запускает его. Если валидация не прошла успешно, то перации вставки или обновления будут отменены. Предполагается что объект валидатора будет экземпляром класса Validation_Validator.
access
У маппера есть метод access, который проверяет права доступа. По умолчанию этот метод всегда возвращает true и вызывается события.
defaults
У маппера есть метод defaults, который устанавливает значения по умолчанию. Эти значения будут присвоены entity в случае вызова метода make_entity. Значения по умолчания обысно устанавливают один раз в setup методе маппера.
index
У маппера можно вызвать метод index('index_name') для явного указания какой индекс использовать в запросе (USE INDEX).
mode
У маппера есть метод mode, который устанавливает параметр запроса идущий сразу после ключевого SELECT INSERT UPDATE и так далее. Например
CMS::orm()->users->mode('IGNORE')->insert($user); // выполнит INSERT IGNORE
distinct
Для аналога DISTINCT в SQL у маппера есть метод distinct($column = null)
as_string
Маппер реализует Core_StringifyInterface и возвращает SQL запрос. Entity тоже реализует этот интерфейс и возвращает первое непустое значение из колонок title, name, id
collection_class & select key
У модуля DB есть опция collection_class, в которой хранится имя класса, экземпляр которого будет содержать результат выборки. По умолчанию это ArrayObject. Можно переопределить опцию модуля DB, если необходима какая-то особенная обработка результирующей коллекции. А можно переопределить прототип для конкретного запроса:
class MyCollection extends ArrayObject { public function sum($column = 'salary') { $sum = 0; foreach ($this as $row) { $sum += $row[$column]; } return $sum; } } $prototype = new MyCollection(); $users = CMS::orm()->users->query()->fetch_all($prototype, 'id'); // $users instanceof MyCollection => true var_dump($users->sum());
Вторым параметром в fetch_all передается название колонки значение которой будет выступать в качестве ключа в результате. Этот же параметр можно указать и в методе select('id'). По умолчанию этот параметр равен первичному ключу. Если указать null, ключ будет формироваться автоматически от нуля до количества записей.
update_all, delete_all
У маппера есть метод delete_all, который удаляет несколько записей удовлетворяющих условиям выборке. Так же есть метод update_all для обновления сразу нескольких строк в таблице
CMS::orm()->users->spawn()->where('active = 1')->update_all(array('status' => 1), array('activity' => 'activity + 1'))
save
У маппера есть метод save, который пытается вставить запись в таблицу или обновить уже существующую, если она есть. Это делает на основе информации о первичном ключе текущей записи.
Entity mapper
У базова класса DB_ORM_Entity есть доступ к мапперу, но только в том случае, если entity создано через метод make_entity или является результатом выборки. Маппер можно получить по свойству ->mapper. Кроме того в классе entity продублированы основные методы для вставки, удаления, обновления: insert, delete, update, save.