Каждая таблица может быть обернута классом Модели. Модель позволяет с легкостью обращаться к данным таблицы без использования прямых 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';
По умолчанию - ключевым полем является поле 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 - название поля в базе данных. По умолчанию соответствует ключу массива
Данное действие является не обязательным и помогает только через 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();
В общем плане заготовки помогают вам использовать заготовленные ранее выражения для облегчения выражений с бизнес-логикой
Выражение применяется к модели в любом вызове или работы с ним автоматически. Чтобы создать глобальный заготовок - используется функция
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();
В моделях предусмотрены функции, которые срабатывают при совершении определенных событий. К данным событиям относятся событие перед и после сохранения, а также перед и после удаления. В событиях можно взаимодействовать со свойствами модели, а также прерывать операции сохранения и удаления перед выполнением
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 очень тесно связаны. Все модели могут быть использованы, как основная таблица для построение query-builder, так и средство для работы с моделью.
К любой модели может быть применима функция, доступная в query-builder, но если вам хочется получить подсказки - вы можете использовать метод
user::withHinth()->...
После данного перехода - модель станет частью 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');
форма описания запроса в одну строку с селекторами, в css стандарте.
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');
Сырой произвольный 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));
Стандартный запрос с поискос диапазона
c\models::builder('user')->between('field',5,10);
user::withHints()->between('field',5,10);
user->between('field',5,10);
Чтобы перечислить колонки, необоходимые для работы в качестве результата запроса - можно отдельно перечислить или убрать из ранее перечисленных при помощи команд 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');
Сортировка полей осуществляется стандартными образами, но ведется с использованием приоритетов. Чем больше приоритет - тем позже он будет установлен. Можно установить приоритет любого уровня в любом месте запроса.
Через 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);
Ограничение определенного числа выводимых записей
c\models::builder('user')->limit(0,10);
user::withHints()->limit(0,10);
user->limit(0,10);
Объединение запроса с моделью $className. Модели в данном запросе присваивается алиас $alias (по умолчанию алиас совпадает с названием модели). Алиасом, с которым пойдет обновление указывается $withAlias. По умолчанию - объединение идет с последним активным алиасом. После написания джойна - активным становится последний класс. Все операции происходят по умолчанию с ним, если не указан другой алиас.
Объединение идет по колонке, описаной в правеле join модели
c\models::query('user>depts');
c\models::builder('user')->join('depts');
user::withHints()->join('depts');
user->join('depts');
Все предыдущие функции собирали цепочку между собой, эти функции выводят конкретный результат
Вывод агрегаций по колонке. 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();
Функции работают аналогично функциям класса для работы с базой данных db.
Создано при помощи сервиса Core CMS