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.

Метки: ORM
06.10.2014
Все статьи