MVC компонент

Модели, коллекции, query-builder

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

model (as source)->query-builder->collection->model (row)

Прежде, чем начать работать с базами данных - необходимо настроить подключение

Определение модели

Подключение модели

Прежде, чем использовать модель - её нужно описать. Модели удобно размещать в папке models в корне проекта. Общие модели, доступные для всех проектов могут быть размещены в папке core/models

Для указания моделей в произвольных расположениях - можно воспользоваться конструкцией

c\models::$di['model_name']='any_file_name.php';

Для указания папки расположения моделей для проекта - используется конфигурация

c\core::$data['include_dir']='models/';

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

Описание модели

Модель должна наследовать класс c\model

Модель не должна содержать неймспейс. Минимально описаная модель содержит следующую конструкцию:

<?php
class user extends c\model{}

Теперь обращение вида

echo user::count();

Вернет вам общее количество строк в таблице user. Для этого будет использовано текущее активное подключение (указаное через c\core::$data['db'])

Название таблицы

По умолчанию - название таблицы совпадает с названием модели. Если название таблицы отличается от названия модели - вы можете отдельно указать название таблицы в свойстве модели

var $tableName='users';

Название подключения

По умолчанию - название подключения используется - текущее (указаное через c\core::$data['db']). Если сам нужно указать подключение отличное от текущего - вы можете использовать свойство

var $connection='otherConnectionName';

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

var $connection=array('firstConnection','SecondConnection','otherConnection');

Название схемы

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

var $scheme='otherScheme';

Primary Keys

По умолчанию - ключевым полем является поле ID. Если ключевое поле используется другое - вы можете переопределить его через

var $primaryField='USR_ID';

Доступ только для чтения

Чтобы обезопасить данные - есть возможность запретить изменение данных через модель, при помощи параметра

var $readonly=true;

Получение нескольких экземпляров модели

После создания и настройки модели, а также существовании таблицы - можно получить все данные модели

$users=user::get();

Корректировка запроса

Чтобы скорректировать выдачу результатов запроса - нужно перед вызовом метода get() - поставить дополнительные параметры доступные в конструкторе запросов

$users=user::whereStatic('SALARY','>',10000)->order('FIO','desc')->limit(0,10)->get();

$users=user::whereStatic('USR_ID','in',[125,631])->get();

$users=user::whereStatic(['name'=>'Петя','fired'=>false,'dept'=>153215])->get();

Коллекции

Метод get() возвращает набор данных - коллекцию c\collection_object, которая содержит множество полезных методов для работы с массивом данных. Для получения значения столбца - необходимо обратиться к свойству модели с тем же именем. Например, чтобы вывести все ФИО по строкам - можно использовать выражение

foreach ($users as $user){
    echo $user->FIO;
}

К коллекции применимы все свойства, описаные в классе datawork. group, tree

Выводить множество значений можно без оператора get(). При использовании модели в конструкции foreach - get() срабатывает автоматически

foreach (user::whereStatic('SALARY','>',10000) as $user){
    echo $user->FIO;
}

Построчное чтение больших моделей

Для экономии памяти при работе с большими данными - можно разрезать модель на строки. В данной постановке - результаты модели не кешируются

user::cursor(function($user){
    echo $user->FIO;
})

Получение строчных моделей

Помимо работы в режиме таблицы - модель может работать в режиме строки. Чтобы получить данные из модели в режиме строки - используется конструкция

$row=user::findStatic(1);

Метод ищет запись в модели по primary_key.

Исключения при поиске

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

$row=user::findOrFail(1, new Exception('user not found'));

Создание записи в случае её отсутствия

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

$row=user::findOrCreate(1);

Первый или последний экземпляр модели

Применяются к коллекциям, полученым при помощи метода get()

$users->first();
$users->last();

user::whereStatic('active','=',1)->first();
user::whereStatic('active','=',1)->get()->first();

Агрегации

Есть возможность пользоваться методами агрегации для получения итоговых значений. В качестве результата возвращается как правило число

$count=user::where('active',1)->count();
$max=user::where('active',1)->max('salary');
$min=user::where('active',1)->min('salary');
$avg=user::where('active',1)->avg('salary');
$sum=user::where('active',1)->sum('salary');

Общее описание модели

В общем виде описание модели выглядит, как:

<?php
class user extends c\model{
    var $connection=array('connection1','localhost');
    var $scheme='db_scheme';
    var $primaryField='ID';
    var $tableName='users';
    var $readonly=true;
    var $fields=array(
        'USR_ID'=>array(
            'label'=>'Tabel number',
            'type'=>'integer',
            'validate'=>array(
				array('type'=>'abs','text'=>c\input::VALIDATE_AUTO_TEXT),
				array('type'=>'require','text'=>c\input::VALIDATE_AUTO_TEXT),
			)
        ),
        'FIO'=>array(
...
        ),
        'DEPT_ID'=>array(
            'label'=>'Dept id',
            'type'=>'integer',
            'get'=>array('dept','to_dept')
        ),
    );
    static function to_user($val){
        return user::find($val);
    }
}

В параметрах

$fields - описание полей модели в формате описания формы. с небольшими дополнениями

Пользовательские функции, необходимые для адаптации переменных

Описание полей

var $fields=array(
'FIELD_KEY'=>array(
... Все поля, которые необходимы для постройки формы ...
'pk'=>true,
'get'=>array('dept','to_dept'),
'fill'=>array('dept','to_dept'),
'set'=>array('dept','to_dept'),
'nosql'=>true,
'readonly'=>true,
'getThis'=>false,
'dbname'=>'name_in_db'
),
..
)

Каждый ключ массива - название поля в базе данных

В качестве значения перечисляются все поля, необоходимые для постройки формы + дополнительные поля.

pk = true - говорит что данное поле является ключевым (аналог var $primaryField='id';)

nosql = true - говорит, что поле является виртуальным и для него не нужно получать данные из query-builder

get - выполняет функцию преобразования при обращении к переменной модели. Если функция преобразования находится внутри модели - необоходимо передать массив с названием класса и метода

set - выполняет функцию обратного преобразования при сохранении данных в базу. Если функция преобразования находится внутри модели - необоходимо передать массив с названием класса и метода

fill - выполняет функцию обратного преобразования при изменении данных через модель (при каждом изменении свойства). Если функция преобразования находится внутри модели - необоходимо передать массив с названием класса и метода

readonly - запрещает поле для изменения. Соответственно, при создании новой записи поле должно иметь значение по умолчанию

getThis - boolean выражение, указывающее на необходимость передачи в функцию get - 3м аргументом - ссылку на экземпляр модели $this

dbname - название поля в базе данных. По умолчанию соответствует ключу массива

Дополнительные hint классы моделей

Данное действие является не обязательным и помогает только через IDE увидеть доступные свойства полей класса, а также получить информацию о содержимом поля и подсказок. Для правильного использования hint классов - необходимо в любое место проекта (отдельную папку) - рекомендуется models/hint/ создать классы дубликаты с phpdoc синтаксисом подсказок. Например:

<?php
class user extends c\model{
    /**
     * @var number
     */
    var $USR_ID;
    var $FIO;
    var $FNAME;
    /**
     * Должность
     * @var number
     */
    var $SJOB_SJOB_ID;
    /**
     * Подразделение
     * @var number
     */
    var $SDEPT_SDEPT_ID;

    /**
     * @return user
     */
   function find(){}
    /**
     * @return user_collection|user[]
     */
    function get(){}
}
class user_collection extends c\collection_object{
    /**
     * @return user;
     */
    function first(){}
    /**
     * @return user;
     */
    public function offsetGet($offset){}
    /**
     * @return user;
     */
    public function current(){}
}

Чтобы подтянуть экземпляр модели в шаблон, в котором не была объявлена переменная, и пользоваться всеми подсказками - перед обращением, или в самом начале шаблона - необходимо написать подсказку для IDE

/* @var $varName Type_Name */

Создание записи на основе модели

После описания моделей - можно создавать новые экземпляры

$user= new user();
$user->USR_ID=123;
$user->LNAME='Иванов';
$user->FIO='Иванов Иван Иванович';
$user->save();

save() создаст новый экземпляр модели, или заменит существующую запись, если запись с таким PK уже была

Изменение существующих записей

Процедура изменения существующих записей состоит из частей: Получение экземпляра строки, изменение данных и сохранение результатов

$user=user::findStatic(1);
$user->SALARY+=100000;
$user->save();

Преобразование модели в массив

Чтобы разом преобразовать всю модель в массив - используется метод

$user->toArray();

Создание экземпляра модели на основе имеющейся записи

Для создания новой записи на основе уже имеющейся - используется функция replicate

$user=user::find(1);
$user2=$user->replicate();
$user2->name='Федя';
$user2->save();

Удаление записей

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

user::whereStatic('active',1)->delete();
user::findStatic(1)->delete();

Заготовки запросов (scopes)

В общем плане заготовки помогают вам использовать заготовленные ранее выражения для облегчения выражений с бизнес-логикой

Глобальные заготовки

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

function globalScope(){
    return $this->where('SALARY','>',200);
}

Локальные пользовательские заготовки

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

function active(){
    return $this->is('FIRED',0)->is('USTAT',3);
}
function isBoss(){
    return $this->is('LEADPOS',1);
}

$activeBosses=user::orderBy('DATE_BEGIN','desc')->active()->isBoss()->get();

Динамические параметры

Вы также в праве использовать заготовки с использованием пользовательских значений

function salaryMore($salary){
    return $this->where('salary','>',$salary);
}

$more500=user::salaryMore(500)->orderBy('salary')->get();

События (events)

В моделях предусмотрены функции, которые срабатывают при совершении определенных событий. К данным событиям относятся событие перед и после сохранения, а также перед и после удаления. В событиях можно взаимодействовать со свойствами модели, а также прерывать операции сохранения и удаления перед выполнением

function on_before_save(){
	$this->FIO=$this->FNAME.' '.$this->LNAME.' '.$this->MNAME;
}
function on_after_save(){
	c\mail::send($this->EMAIL,'Данные сохранены');
}
function on_before_delete(){
	if ($this->CLOSED==0)thrown new Exception('Нельзя удалять закрытые заявки');		
}
function on_after_delete(){
	
}

Отношения моделей

Всего существует два способа совершения отношения между моделями.

Отношения через свойство

Применимо для получения отношений к 1 из режима строки. Для этого - при обращении к полю - ставится мутатор. Данный способ не рекомендуется использовать, т.к. он не учитывает ленивый способ обращения и может быть вызван не оптимально. Для отношений к одному - рекомендуется использовать метод relationToOne

//user class
var $fields=array(
    'dept_id'=>array(
        'get'=>array('dept','to_dept')
    );
);

//dept class
static function to_dept($val){
    return dept::find($val);
}

user::find(10)->dept_id->name; // not recommend

Данный метод также не дает понять, чем будет являться переменная dept_id, что может вызвать дополнительные трудности в понимании кода. Рекомендуется параметры доставаемые в сыром виде из базы получать через переменную без преобразований, а для преобразований и отношений использовать

Отношения через функцию

Для связей ко многим, или отношений к одному рекомендуется использовать отношение через функцию. В простом случае описание отношение описывается, как

//user class
/**
 * @return user
 */
function users(){
    return $this->relation('user','parent_dept_id');
    //return (new user())->where('parent_dept_id',$this->dept_id);
}

//usage
foreach (dept::where('active',1)->get() as $dept){
    foreach ($dept->users()->where('fired',0)->get() as $user){
        echo $user->fio;
    }
}

Самый простой способ создания отношения - при вызове функции - создать новый экземпляр с ограничениями. В этом случае - всё будет работать, но при каждом новом обращении - будет выполнен отдельный запрос. В случае использования функции relation  - при первой попытке извлечь данные - будет отправлен один запрос по всем пользователям, которые в конечном итоге будут задействованы

relation($model,$foreign_key=null,$local_key=null,$is_inner=false)
relationInner($model,$foreign_key=null,$local_key=null)

При использовании отношения с кешем - передается зависимая модель, а также название ключевого поля зависимой таблицы и поля, по которому будет идти связка с данной модели.

Признак inner указывает на inner join отношение, получаемая модель в конечном итоге будет содержать все элементы, которые есть в оригинальной модели

Для отношений к одному из режима строки или таблицы - рекомендуется использовать метод relationToOne. Он учитывает запросы внутри цикла и строит их оптимальным образом. Разница с методом relation состоит только при обращении из режима строки - он возвращает единичный экземпляр, а не набор коллекции из одного элемента найденого через отношение

relationToOne($model,$foreign_key=null,$local_key=null)

Отношение многие ко многим реализуется через два отношения много <-> один <-> много

Отложеная загрузка

Отложеная загрузка происходит всегда автоматически во всех случаях и ничего для её включения делать не нужно.

Преобразование в Query-Builder, поиск данных по модели (deprecated)

Модель и Query-builder очень тесно связаны. Все модели могут быть использованы, как основная таблица для построение query-builder, так и средство для работы с моделью.

К любой модели может быть применима функция, доступная в query-builder, но если вам хочется получить подсказки - вы можете использовать метод

user::withHinth()->...

После данного перехода - модель станет частью query-builderа

Query-builder

В данном фреймворке query-builder выступает, как средство построения запросов и образования моделей на основе запросов. Весь query-builder содержится в классе models. Чтобы образовать query-builder существует 3 способа

//через модели
user::withHints();
// через query-builder
c\models::builder('user');
// через query-style форму образования query-builder
c\models::query('user');

Jquery-style форма построения запроса

форма описания запроса в одну строку с селекторами, в css стандарте.

where

c\models::query('user#value'); // pk field where
c\models::query('user[field="value"]');
c\models::builder('user')->where('field','=','value');
user::withHints()->where('field','=','value');
user::where('field','=','value');

возможны правила сравнения = , in, is null, is not null, null, >,<

Для более короткого синтаксиса по правилу = - можно использовать

c\models::builder('user')->is('field','value');
user::withHints()->is('field','value');
user->is('field','value');

whereRaw

Сырой произвольный where запрос. Вы передаете произвольное sql выражение и сопутствующие ему бинды

c\models::builder('user')->whereRaw('case field1 when :bind1 then 1 else :bind2 end',array('bind1'=>1,'bind2'=>2));
user::withHints()->whereRaw('case field1 when :bind1 then 1 else :bind2 end',array('bind1'=>1,'bind2'=>2));
user->whereRaw('case field1 when :bind1 then 1 else :bind2 end',array('bind1'=>1,'bind2'=>2));

Between

Стандартный запрос с поискос диапазона

c\models::builder('user')->between('field',5,10);
user::withHints()->between('field',5,10);
user->between('field',5,10);

select unselect

Чтобы перечислить колонки, необоходимые для работы в качестве результата запроса - можно отдельно перечислить или убрать из ранее перечисленных при помощи команд select и unselect

Если команды не указывать - они будут работать для всех строк. Если unselect указывать до select - подразумеается, что вычитание будет работать из всех строк. Можно прибавлять и убавлять колонки в любое время до совершения самого запроса

c\models::query('user{USR_ID,FIO}')->unselect('FIO');
c\models::builder('user')->select(array('USR_ID','FIO'))->unselect('FIO');
user::withHints()->select(array('USR_ID','FIO'))->unselect('FIO');
user::select(array('USR_ID','FIO'))->unselect('FIO');

Order by

Сортировка полей осуществляется стандартными образами, но ведется с использованием приоритетов. Чем больше приоритет - тем позже он будет установлен. Можно установить приоритет любого уровня в любом месте запроса.

Через jquery-style сортировка регулируется слешами напротив полей. За счет экранирование обратного слеша - необходимо писать либо / - по возрастанию, либо \\ - по убыванию. Приоритет ставится в этом случае, как и по умолчанию 999

c\models::query('user{\\USR_ID}');
c\models::builder('user')->order('USR_ID','desc',999);
user::withHints()->order('USR_ID','desc',999);
user::order('USR_ID','desc',999);
c\models::builder('user')->orderBy('USR_ID','desc',999);
user::withHints()->orderBy('USR_ID','desc',999);
user::orderBy('USR_ID','desc',999);

Limit($start,$count=0)

Ограничение определенного числа выводимых записей

c\models::builder('user')->limit(0,10);
user::withHints()->limit(0,10);
user->limit(0,10);

Join ($className,$alias=false,$withAlias=false)

Объединение запроса с моделью $className. Модели в данном запросе присваивается алиас $alias (по умолчанию алиас совпадает с названием модели). Алиасом, с которым пойдет обновление указывается $withAlias. По умолчанию - объединение идет с последним активным алиасом. После написания джойна - активным становится последний класс. Все операции происходят по умолчанию с ним, если не указан другой алиас.

Объединение идет по колонке, описаной в правеле join модели

c\models::query('user>depts');
c\models::builder('user')->join('depts');
user::withHints()->join('depts');
user->join('depts');

Получение результатов с query-builder

Все предыдущие функции собирали цепочку между собой, эти функции выводят конкретный результат

Агрегации count, min, max, avg, sum

Вывод агрегаций по колонке. Count при указании колонки - выводит distinct всех записей данной колонки

c\models::builder('user')->count();
c\models::builder('user')->min('BALLS');
c\models::builder('user')->max('BALLS');
c\models::builder('user')->avg('BALLS');
c\models::builder('user')->sum('BALLS');
user::withHints()->count();
user::count();

Получение результатов запроса ea, ea1, ea11, eo, ec, eq

Функции работают аналогично функциям класса для работы с базой данных db.

Получение результатов запроса с сохранением свойств модели get() find($pk_value)

 

Создано при помощи сервиса Core CMS