По сложившейся традиции пишу

По сложившейся традиции пишу о наиболее интересных и важных нововведениях в движке еще до официального релиза.

Не помню, возможно, писал уже о том, что я несколько раз подступал к реализации ActiveRecord в Альто. Причем, было большое желание не писать все с нуля, а подобрать уже готовую библиотеку и адаптировать ее к своим нуждам. Но, в силу разных причин, так и не получилось это сделать. Лайвстритовскую реализацию ORM в части задания критериев для выборки данных я считаю просто ужасной.

В общем, все закончилось тем, что в Альто была выполнена своя реализация ActiveRecord, о которой сейчас и пойдет речь.
Создание сущности-записи
Для работы с ActiveRecord создан специальный набор классов, который расположен в пространстве имен \alto\engine\ar (да-да, в Альто тихой сапой начинают использоваться пространства имен), и сущность, модуль и маппер в этом случае должны наследоваться соответственно от классов EntityRecord, ArModule и ArMapper, например:

use alto\engine\ar\EntityRecord;

class PluginCompany_ModuleCompany_EntityCompany extends EntityRecord {
// ...
}

use alto\engine\ar\ArModule;

class PluginCompany_ModuleCompany extends ArModule {
// ...
}

use alto\engine\ar\ArMapper;

class PluginCompany_ModuleCompany_EntityCompany extends ArMapper {
// ...
}

Как правило, имя сущности проецируется на имя таблицы базы данных. Например, в примере выше для работы с сущностью _EntityCompany будет использоваться таблица «?_company» (подстрока «?_» в начале имени таблицы будет заменена на префикс таблиц по умолчанию, который задается в конфиге). А если сущность называется, скажем, _EntityCompanyEmployee, то будет использована таблица «?_company_employee».

В качестве первичного ключа по умолчанию используется поле таблицы «id».

Но, при желании, можно задать любую таблицу и любое поле для первичного ключа, это делается в методе инициализации:

class PluginCompany_ModuleCompany_EntityEmployee extends EntityRecord {

public function init() {
// По умолчанию таблица для этой сущности была бы просто «?_employee»,
// но мы хотим, чтобы использовалась «?_company_employee»
$this->setTable('?_company_employee');
// А первичный ключ будет «company_employee_id»
$this->setPrimaryKey('company_employee_id');
}
}

Теперь для создания новой сущности и сохранения ее в базе данных нам понадобится минимум кода:

$oCompany = E::ModuleCompamy()—>make();
$oCompany->setName('Google');
$oCompany->setEmail('info@google.com');
$oCompany->setCountryCode('US');
$oCompany->save();

А можно и еще короче:

E::ModuleCompamy()
—>make()
—>setName('Google')
—>setEmail('info@google.com')
—>setCountryCode('US')
—>save();

По-моему, чего делается в этом коде, понятно без всяких дополнительных пояснений. Если же мы хотим создать сущность, имя которой отличается от имени модуля, то это надо явно указать:

E::ModuleCompamy()
—>make('Employee')
—>setFirstName('Jhon')
—>setLastName('Smith')
—>save();

Чтение сущностей из базы данных
Чтобы найти компанию по ее названию используется следующий синтаксис:

$oCompany = E::ModuleCompamy()
—>find()
—>where(['name' => 'Google'])
—>one();

И никаких тебе SQL-запросов и прочей рутины! А вот так мы найдем все компании у которых указан код страны «US»:

$aCompanies = E::ModuleCompamy()
—>find()
—>where(['country_code' => 'US'])
—>all();

А вот так можно получить список всех сотрудников, которых зовут Jhon Smith:

$aCompanies = E::ModuleCompamy()
—>find('Employee')
—>where(['first_name' => 'Jhon', 'last_name' => 'Smith'])
—>all();

А вот так можно получить сущности по первичным ключам:

// Получить компанию с ID 1231
$oCompany = E::ModuleCompamy()
—>find()
—>one(1231);
// или более короткая форма
$oCompany = E::ModuleCompamy()—>findOne(1231);

// Получить список компаний с ID 1231, 3724, 5930
$aCompanies = E::ModuleCompamy()
—>find()
—>all([1231, 3724, 5930]);
// или более короткая форма
$aCompanies = E::ModuleCompamy()—>findAll([1231, 3724, 5930]);

Значения для задания фильтров в методе where можно задавать явно, как указано выше, а можно и через параметры, например, так:

$oQuery->where(['name' => ':name'])—>bind(':name' => 'Google');
$oQuery->where(['country_code' => ':cc'])—>bind(':name' => $sCountryCode);

Условия where могут задаваться разными способами:

// field = :value
$oQuery->where(['field' => ':value']);

// field = :value (условие задается, как массив в массиве)
$oQuery->where([['field', '=', ':value']]);

// field < :value
$oQuery->where([['field', '<', ':value']]);

// (field1 < :value1) AND (field2 < :value2)
$oQuery->where([['field1', '<', ':value1'], ['field2', '!=', ':value2']]);

// (field1 < :value1) OR (field2 < :value2)
$oQuery->where([['field1', '<', ':value1'])—>orWhere(['field2', '!=', ':value2']]);

// Зададим сложное вложенное условие:
// (a in (1, 2, 3)) OR (a in (10, 20, 30)) AND (b > 100 OR (c = 1 OR c > 8))
$oQuery
—>whereBegin()
—>where([['a', 'in', '?a:param1']])
—>orWhere([['a', 'in', '?a:param2']])
—>whereEnd()
—>andWhereBegin()
—>where([['b', '>', '?d:param3']])
—>orWhereBegin()
—>where(['c' => ':param4'])
—>orWhere([['c', '>', ':param5']])
—>whereEnd()
—>whereEnd()
—>bind([
':param1' => [1, 2, 3],
':param2' => [10, 20, 30],
':param3' => 100,
':param4' => 1,
':param5' => 1,
]);

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

$oQuery->whereSQL('(a in (?a:param1)) OR (a in (a:param2)) AND (b > ?d:param3 OR (c = :param4 OR c > :param5))');
—>bind([
':param1' => [1, 2, 3],
':param2' => [10, 20, 30],
':param3' => 100,
':param4' => 1,
':param5' => 1,
]);

Наконец, можно задать сортировку возвращаемых записей:

// обычная (прямая) сортировка
$aCompanies = E::ModuleCompamy()
—>find()
—>orderBy('country_code')
—>all();

// обратная сортировка
$aCompanies = E::ModuleCompamy()
—>find()
—>orderBy('country_code' => 'desc')
—>all();

// сортировка по нескольким полям
$aCompanies = E::ModuleCompamy()
—>find()
—>orderBy('country_code' => 'desc', 'update_date')
—>all();

Обновление и удаление
Метод save() используется и при записи новой сущности, и при изменении существующей сущности:

$oCompany = E::ModuleCompamy()—>findOne(1231);
$oCompany->setName('Apple');
$oCompany->save();

А код для удаления еще проще:

E::ModuleCompamy()—>findOne(1231)—>delete();

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