Работа с моделями
Модель представляет информацию (данные) приложения и правила манипулирования этими данными. Модели в основном используются для управления правилами взаимодействия с соответствующей таблицей базы данных. В большинстве случаев каждая таблица в вашей базе данных будет соответствовать одной модели в вашем приложении. Большая часть бизнес-логики вашего приложения будет сосредоточена в моделях.
Phalcon\Mvc\Model является основой для всех моделей в приложении Phalcon. Она обеспечивает независимость от базы данных, базовые функции CRUD, расширенные возможности поиска и возможность связывать модели друг с другом, среди прочих сервисов. Phalcon\Mvc\Model позволяет избежать необходимости использования SQL-запросов, поскольку она динамически переводит методы в соответствующие операции с базой данных.
Модели предназначены для работы с базой данных на высоком уровне абстракции. Если вам нужно работать с базами данных на более низком уровне, ознакомьтесь с документацией компонента Phalcon\Db.
Создание моделей
Модель — это класс, который расширяет Phalcon\Mvc\Model. Он должен быть размещён в каталоге моделей. Файл модели должен содержать один класс; имя класса должно быть в обозначении с использованием верблюжьей нотации:
class Robots extends \Phalcon\Mvc\Model
{
}
Приведенный выше пример демонстрирует реализацию модели «Robots». Обратите внимание, что класс Robots наследуется от Phalcon\Mvc\Model. Этот компонент предоставляет множество функций для моделей, которые его наследуют, включая базовые операции CRUD (Создание, Чтение, Обновление, Удаление) базы данных, валидацию данных, а также сложную поддержку поиска и возможность взаимосвязи нескольких моделей друг с другом.
Если вы используете PHP 5.4/5.5, рекомендуется объявлять каждый столбец, который является частью модели, для экономии памяти и уменьшения выделения памяти.
По умолчанию модель «Robots» будет ссылаться на таблицу «robots». Если вы хотите вручную указать другое имя для сопоставляемой таблицы, вы можете использовать метод getSource():
class Robots extends \Phalcon\Mvc\Model
{
public function getSource()
{
return "the_robots";
}
}
Теперь модель Robots сопоставляется с таблицей «the_robots». Метод initialize() помогает в настройке модели с настраиваемым поведением, т. е. другой таблицей. Метод initialize() вызывается только один раз во время запроса.
class Robots extends \Phalcon\Mvc\Model
{
public function initialize()
{
$this->setSource("the_robots");
}
}
Метод initialize() вызывается только один раз во время запроса, он предназначен для выполнения начальных настроек, которые применяются ко всем экземплярам модели, созданным в приложении. Если вы хотите выполнить задачи инициализации для каждого созданного экземпляра, вы можете использовать ‘onConstruct’:
class Robots extends \Phalcon\Mvc\Model
{
public function onConstruct()
{
//...
}
}
Общедоступные свойства против сетеров/геттеров
Модели могут быть реализованы с свойствами с общедоступным доступом, что означает, что каждое свойство может читаться/обновляться из любой части кода, которая создала этот класс модели, без каких-либо ограничений:
class Robots extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public $price;
}
Используя геттеры и сеттеры, вы можете контролировать, какие свойства являются общедоступными, выполнять различные преобразования данных (что было бы невозможно в противном случае), а также добавлять правила валидации к данным, хранящимся в объекте:
class Robots extends \Phalcon\Mvc\Model
{
protected $id;
protected $name;
protected $price;
public function getId()
{
return $this->id;
}
public function setName($name)
{
//The name is too short?
if (strlen($name) < 10) {
throw new \InvalidArgumentException('The name is too short');
}
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setPrice($price)
{
//Negative prices aren't allowed
if ($price < 0) {
throw new \InvalidArgumentException('Price can\'t be negative');
}
$this->price = $price;
}
public function getPrice()
{
//Convert the value to double before be used
return (double) $this->price;
}
}
Общедоступные свойства обеспечивают меньшую сложность при разработке. Однако геттеры/сеттеры могут значительно повысить тестируемость, расширяемость и поддерживаемость приложений. Разработчики могут решить, какая стратегия более подходит для создаваемого ими приложения. ORM совместим с обеими схемами определения свойств.
Модели в именованных пространствах
Именованные пространства могут использоваться для предотвращения конфликтов имен классов. Сопоставляемая таблица берется из имени класса, в данном случае «Robots»:
namespace Store\Toys;
class Robots extends \Phalcon\Mvc\Model
{
}
Понимание преобразования записей в объекты
Каждый экземпляр модели представляет строку в таблице. Вы можете легко получить данные записи, прочитав свойства объекта. Например, для таблицы «robots» со записями:
mysql> select * from robots; +----+------------+------------+------+ | id | name | type | year | +----+------------+------------+------+ | 1 | Robotina | mechanical | 1972 | | 2 | Astro Boy | mechanical | 1952 | | 3 | Terminator | cyborg | 2029 | +----+------------+------------+------+ 3 rows in set (0.00 sec)
Вы можете найти определённую запись по её первичному ключу, а затем напечатать её имя:
// Find record with id = 3 $robot = Robots::findFirst(3); // Prints "Terminator" echo $robot->name;
После того, как запись находится в памяти, вы можете внести изменения в её данные, а затем сохранить изменения:
$robot = Robots::findFirst(3); $robot->name = "RoboCop"; $robot->save();
Как видите, нет необходимости использовать сырые SQL-запросы. Phalcon\Mvc\Model предоставляет высокую абстракцию базы данных для веб-приложений.
Поиск записей
Phalcon\Mvc\Model также предлагает несколько методов для запроса записей. Следующие примеры покажут вам, как запросить одну или несколько записей из модели:
// How many robots are there?
$robots = Robots::find();
echo "There are ", count($robots), "\n";
// How many mechanical robots are there?
$robots = Robots::find("type = 'mechanical'");
echo "There are ", count($robots), "\n";
// Get and print virtual robots ordered by name
$robots = Robots::find(array(
"type = 'virtual'",
"order" => "name"
));
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// Get first 100 virtual robots ordered by name
$robots = Robots::find(array(
"type = 'virtual'",
"order" => "name",
"limit" => 100
));
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
Вы также можете использовать метод findFirst() для получения только первой записи, соответствующей заданным критериям:
// What's the first robot in robots table?
$robot = Robots::findFirst();
echo "The robot name is ", $robot->name, "\n";
// What's the first mechanical robot in robots table?
$robot = Robots::findFirst("type = 'mechanical'");
echo "The first mechanical robot name is ", $robot->name, "\n";
// Get first virtual robot ordered by name
$robot = Robots::findFirst(array("type = 'virtual'", "order" => "name"));
echo "The first virtual robot name is ", $robot->name, "\n";
Оба метода find() и findFirst() принимают ассоциативный массив, определяющий критерии поиска:
$robot = Robots::findFirst(array(
"type = 'virtual'",
"order" => "name DESC",
"limit" => 30
));
$robots = Robots::find(array(
"conditions" => "type = ?1",
"bind" => array(1 => "virtual")
));
Доступные параметры запроса:
| Параметр | Описание | Пример |
|---|---|---|
| conditions | Условия поиска для операции find. Используется для извлечения только тех записей, которые соответствуют заданному критерию. По умолчанию Phalcon\Mvc\Model предполагает, что первый параметр — это условия. | “conditions” => “name LIKE ‘steve%’” |
| columns | Возвращает определённые столбцы вместо полных столбцов в модели. При использовании этого параметра возвращается неполный объект. | “columns” => “id, name” |
| bind | Bind используется вместе с параметрами, заменяя заполнительные значения и экранируя значения, тем самым повышая безопасность. | “bind” => array(“status” => “A”, “type” => “some-time”) |
| bindTypes | При привязке параметров вы можете использовать этот параметр для определения дополнительного преобразования привязанных параметров, ещё больше повышая безопасность. | “bindTypes” => array(Column::BIND_TYPE_STR, Column::BIND_TYPE_INT) |
| order | Используется для сортировки результата. Используйте одно или несколько полей, разделённых запятыми. | “order” => “name DESC, status” |
| limit | Ограничивает результаты запроса результатами в определённом диапазоне. | “limit” => 10 / “limit” => array(“number” => 10, “offset” => 5) |
| group | Позволяет собирать данные по нескольким записям и группировать результаты по одному или нескольким столбцам. | “group” => “name, status” |
| for_update | С этим параметром Phalcon\Mvc\Model считывает самые последние доступные данные, устанавливая эксклюзивные блокировки для каждой считанной строки. | “for_update” => true |
| shared_lock | С этим параметром Phalcon\Mvc\Model считывает самые последние доступные данные, устанавливая общие блокировки для каждой считанной строки. | “shared_lock” => true |
| cache | Кеширует результат, уменьшая постоянный доступ к реляционной системе. | “cache” => array(“lifetime” => 3600, “key” => “my-find-key”) |
| hydration | Устанавливает стратегию гидратации для представления каждой возвращённой записи в результате. | “hydration” => Resultset::HYDRATE_OBJECTS |
Если вы предпочитаете, также существует возможность создания запросов объектно-ориентированным способом вместо использования массива параметров:
$robots = Robots::query()
->where("type = :type:")
->andWhere("year < 2000")
->bind(array("type" => "mechanical"))
->order("name")
->execute();
Статический метод query() возвращает объект Phalcon\Mvc\Model\Criteria, удобный для автодополнения IDE.
Все запросы внутри обрабатываются как запросы PHQL. PHQL — это высокоуровневый, объектно-ориентированный и похожий на SQL язык. Этот язык предоставляет вам больше функций для выполнения запросов, таких как объединение других моделей, определение группировок, добавление агрегаций и т. д.
Наконец, есть метод findFirstBy<property-name>(). Этот метод расширяет метод «findFirst()», упомянутый ранее. Он позволяет быстро выполнить запрос из таблицы, используя имя свойства в самом методе и передавая параметр, содержащий данные, которые вы хотите найти в этом столбце. Для примера, возьмем нашу модель Robots, упомянутую ранее:
class Robots extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public $price;
}
У нас есть три свойства для работы: $id, $name и $price. Допустим, вы хотите получить первую запись в таблице с именем «Terminator». Это можно записать следующим образом:
$name = "Terminator";
$robot = Robots::findFirstByName($name);
if($robot){
$this->flash->success("The first robot with the name " . $name . " cost " . $robot->price ".");
}else{
$this->flash->error("There were no robots found in our table with the name " . $name ".");
}
Обратите внимание, что мы использовали «Name» в вызове метода и передали переменную $name, содержащую имя, которое мы ищем в таблице. Обратите также внимание, что при совпадении с нашим запросом доступны и все остальные свойства.
Наборы результатов модели
В то время как findFirst() возвращает непосредственно экземпляр вызываемого класса (при наличии данных для возврата), метод find() возвращает Phalcon\Mvc\Model\Resultset\Simple. Это объект, который инкапсулирует все функциональные возможности набора результатов, такие как итерация, поиск определённых записей, подсчёт и т. д.
Эти объекты более мощные, чем стандартные массивы. Одной из лучших особенностей Phalcon\Mvc\Model\Resultset является то, что в памяти всегда находится только одна запись. Это значительно помогает в управлении памятью, особенно при работе с большими объёмами данных.
// Get all robots
$robots = Robots::find();
// Traversing with a foreach
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// Traversing with a while
$robots->rewind();
while ($robots->valid()) {
$robot = $robots->current();
echo $robot->name, "\n";
$robots->next();
}
// Count the resultset
echo count($robots);
// Alternative way to count the resultset
echo $robots->count();
// Move the internal cursor to the third robot
$robots->seek(2);
$robot = $robots->current();
// Access a robot by its position in the resultset
$robot = $robots[5];
// Check if there is a record in certain position
if (isset($robots[3])) {
$robot = $robots[3];
}
// Get the first record in the resultset
$robot = $robots->getFirst();
// Get the last record
$robot = $robots->getLast();
Наборы результатов Phalcon эмулируют прокручиваемые курсоры; вы можете получить любую строку, просто обратившись к её позиции или переместив внутренний указатель в определённую позицию. Обратите внимание, что некоторые системы баз данных не поддерживают прокручиваемые курсоры; это вынуждает повторно выполнить запрос, чтобы перемотать курсор в начало и получить запись в запрошенной позиции. Аналогично, если набор результатов просматривается несколько раз, запрос должен выполняться такое же количество раз.
Хранение больших результатов запроса в памяти может потреблять много ресурсов. Поэтому наборы результатов получаются из базы данных частями по 32 строки, что уменьшает потребность в многократном выполнении запроса в нескольких случаях, а также экономит память.
Обратите внимание, что наборы результатов могут быть сериализованы и сохранены в кэше. Phalcon\Cache может помочь с этой задачей. Однако сериализация данных заставляет Phalcon\Mvc\Model извлекать все данные из базы данных в массив, таким образом потребляя больше памяти во время этого процесса.
// Query all records from model parts
$parts = Parts::find();
// Store the resultset into a file
file_put_contents("cache.txt", serialize($parts));
// Get parts from file
$parts = unserialize(file_get_contents("cache.txt"));
// Traverse the parts
foreach ($parts as $part) {
echo $part->id;
}
Фильтрация наборов результатов
Самый эффективный способ фильтрации данных — это установка некоторых критериев поиска; базы данных будут использовать индексы, заданные для таблиц, для более быстрого возврата данных. Phalcon также позволяет фильтровать данные с помощью PHP, используя любые ресурсы, которые недоступны в базе данных:
$customers = Customers::find()->filter(function($customer) {
//Return only customers with a valid e-mail
if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
return $customer;
}
});
Связывание параметров
Связанные параметры также поддерживаются в Phalcon\Mvc\Model. Хотя использование связанных параметров оказывает минимальное влияние на производительность, вам рекомендуется использовать этот метод, чтобы исключить возможность атак с использованием SQL-инъекций. Поддерживаются как строковые, так и целочисленные плейсхолдеры. Связывание параметров можно выполнить следующим образом:
// Query robots binding parameters with string placeholders
$conditions = "name = :name: AND type = :type:";
//Parameters whose keys are the same as placeholders
$parameters = array(
"name" => "Robotina",
"type" => "maid"
);
//Perform the query
$robots = Robots::find(array(
$conditions,
"bind" => $parameters
));
// Query robots binding parameters with integer placeholders
$conditions = "name = ?1 AND type = ?2";
$parameters = array(1 => "Robotina", 2 => "maid");
$robots = Robots::find(array(
$conditions,
"bind" => $parameters
));
// Query robots binding parameters with both string and integer placeholders
$conditions = "name = :name: AND type = ?1";
//Parameters whose keys are the same as placeholders
$parameters = array(
"name" => "Robotina",
1 => "maid"
);
//Perform the query
$robots = Robots::find(array(
$conditions,
"bind" => $parameters
));
При использовании числовых плейсхолдеров вам необходимо определить их как целые числа, т. е. 1 или 2. В этом случае «1» или «2» считаются строками, а не числами, поэтому плейсхолдер не может быть успешно заменён.
Строки автоматически экранируются с помощью PDO. Эта функция учитывает кодировку символов подключения, поэтому рекомендуется определять правильную кодировку символов в параметрах подключения или в конфигурации базы данных, так как неправильная кодировка символов может привести к нежелательным последствиям при хранении или извлечении данных.
Кроме того, вы можете установить параметр «bindTypes», который позволяет определить, как параметры должны быть связаны в соответствии с их типом данных:
use \Phalcon\Db\Column;
//Bind parameters
$parameters = array(
"name" => "Robotina",
"year" => 2008
);
//Casting Types
$types = array(
"name" => Column::BIND_PARAM_STR,
"year" => Column::BIND_PARAM_INT
);
// Query robots binding parameters with string placeholders
$robots = Robots::find(array(
"name = :name: AND year = :year:",
"bind" => $parameters,
"bindTypes" => $types
));
Поскольку значение по умолчанию для bind-типа — \Phalcon\Db\Column::BIND_PARAM_STR, нет необходимости указывать параметр «bindTypes», если все столбцы имеют этот тип.
Связанные параметры доступны для всех методов запроса, таких как find() и findFirst(), а также для методов расчёта, таких как count(), sum(), average() и т. д.
Инициализация/подготовка извлечённых записей
Возможно, после получения записи из базы данных необходимо инициализировать данные перед использованием их остальной частью приложения. Вы можете реализовать метод «afterFetch» в модели; этот событие будет выполнено сразу после создания экземпляра и присвоения данных ему:
class Robots extends Phalcon\Mvc\Model
{
public $id;
public $name;
public $status;
public function beforeSave()
{
//Convert the array into a string
$this->status = join(',', $this->status);
}
public function afterFetch()
{
//Convert the string to an array
$this->status = explode(',', $this->status);
}
}
Если вы используете геттеры/сеттеры вместо или вместе с общедоступными свойствами, вы можете инициализировать поле после обращения к нему:
class Robots extends Phalcon\Mvc\Model
{
public $id;
public $name;
public $status;
public function getStatus()
{
return explode(',', $this->status);
}
}
Взаимоотношения между моделями
Существует четыре типа взаимоотношений: один к одному, один ко многим, многие ко одному и многие ко многим. Взаимоотношение может быть однонаправленным или двунаправленным, и каждое из них может быть простым (модель один к одному) или более сложным (комбинация моделей). Менеджер моделей управляет ограничениями внешних ключей для этих взаимоотношений; определение этих ограничений способствует соблюдению референтной целостности, а также обеспечивает лёгкий и быстрый доступ к связанным записям для модели. Благодаря реализации взаимоотношений легко получить доступ к данным в связанных моделях из каждой записи равномерным способом.
Однонаправленные взаимоотношения
Однонаправленные взаимоотношения — это взаимоотношения, которые генерируются по отношению друг к другу, но не наоборот.
Двунаправленные взаимоотношения
Двунаправленные взаимоотношения создают взаимоотношения в обеих моделях, и каждая модель определяет обратное взаимоотношение другой.
Определение взаимоотношений
В Phalcon взаимоотношения должны быть определены в методе initialize() модели. Методы belongsTo(), hasOne(), hasMany() и hasManyToMany() определяют взаимоотношения между одним или несколькими полями текущей модели и полями другой модели. Каждый из этих методов требует 3 параметров: локальные поля, связанная модель, связанные поля.
| Метод | Описание |
|---|---|
| hasMany | Определяет взаимоотношение 1-n |
| hasOne | Определяет взаимоотношение 1-1 |
| belongsTo | Определяет взаимоотношение n-1 |
| hasManyToMany | Определяет взаимоотношение n-n |
Следующая схема показывает 3 таблицы, взаимоотношения которых будут использоваться в качестве примера для взаимоотношений:
CREATE TABLE `robots` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(70) NOT NULL,
`type` varchar(32) NOT NULL,
`year` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `robots_parts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`robots_id` int(10) NOT NULL,
`parts_id` int(10) NOT NULL,
`created_at` DATE NOT NULL,
PRIMARY KEY (`id`),
KEY `robots_id` (`robots_id`),
KEY `parts_id` (`parts_id`)
);
CREATE TABLE `parts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(70) NOT NULL,
PRIMARY KEY (`id`)
);
- Модель «Robots» имеет множество «RobotsParts».
- Модель «Parts» имеет множество «RobotsParts».
- Модель «RobotsParts» относится к моделям «Robots» и «Parts» как взаимоотношение многие ко одному.
- Модель «Robots» имеет взаимоотношение многие ко многим с «Parts» через «RobotsParts»
См. диаграмму EER для лучшего понимания взаимоотношений:
Модели со своими взаимоотношениями можно реализовать следующим образом:
class Robots extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany("id", "RobotsParts", "robots_id");
}
}
class Parts extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany("id", "RobotsParts", "parts_id");
}
}
class RobotsParts extends \Phalcon\Mvc\Model
{
public $id;
public $robots_id;
public $parts_id;
public function initialize()
{
$this->belongsTo("robots_id", "Robots", "id");
$this->belongsTo("parts_id", "Parts", "id");
}
}
Первый параметр указывает поле локальной модели, используемой во взаимоотношении; второй указывает имя связанной модели, а третий — имя поля в связанной модели. Вы также можете использовать массивы для определения нескольких полей во взаимоотношении.
Взаимоотношения многие ко многим требуют 3 моделей и определяют атрибуты, вовлечённые во взаимоотношении:
class Robots extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public function initialize()
{
$this->hasManyToMany(
"id",
"RobotsParts",
"robots_id", "parts_id",
"Parts",
"id"
);
}
}
Использование взаимоотношений
При явном определении взаимоотношений между моделями легко найти связанные записи для конкретной записи.
$robot = Robots::findFirst(2);
foreach ($robot->robotsParts as $robotPart) {
echo $robotPart->parts->name, "\n";
}
Phalcon использует магические методы __set/__get/__call для хранения или извлечения связанных данных, используя взаимоотношения.
Обращение к атрибуту с тем же именем, что и взаимоотношение, возвращает все связанные записи.
$robot = Robots::findFirst(); $robotsParts = $robot->robotsParts; // all the related records in RobotsParts
Также вы можете использовать магический геттер:
$robot = Robots::findFirst();
$robotsParts = $robot->getRobotsParts(); // all the related records in RobotsParts
$robotsParts = $robot->getRobotsParts(array('limit' => 5)); // passing parameters
Если вызываемый метод имеет префикс «get», Phalcon\Mvc\Model вернёт результат findFirst()/find(). Следующий пример сравнивает получение связанных результатов с использованием магических методов и без них:
$robot = Robots::findFirst(2);
// Robots model has a 1-n (hasMany)
// relationship to RobotsParts then
$robotsParts = $robot->robotsParts;
// Only parts that match conditions
$robotsParts = $robot->getRobotsParts("created_at = '2012-03-15'");
// Or using bound parameters
$robotsParts = $robot->getRobotsParts(array(
"created_at = :date:",
"bind" => array("date" => "2012-03-15")
));
$robotPart = RobotsParts::findFirst(1);
// RobotsParts model has a n-1 (belongsTo)
// relationship to RobotsParts then
$robot = $robotPart->robots;
Получение связанных записей вручную:
$robot = Robots::findFirst(2);
// Robots model has a 1-n (hasMany)
// relationship to RobotsParts, then
$robotsParts = RobotsParts::find("robots_id = '" . $robot->id . "'");
// Only parts that match conditions
$robotsParts = RobotsParts::find(
"robots_id = '" . $robot->id . "' AND created_at = '2012-03-15'"
);
$robotPart = RobotsParts::findFirst(1);
// RobotsParts model has a n-1 (belongsTo)
// relationship to RobotsParts then
$robot = Robots::findFirst("id = '" . $robotPart->robots_id . "'");
Префикс «get» используется для поиска (find()/findFirst()) связанных записей. В зависимости от типа взаимоотношения будет использоваться «find» или «findFirst»:
| Тип | Описание | Неявный метод |
|---|---|---|
| Один-ко-многим | Возвращает экземпляр модели связанной записи напрямую | findFirst |
| Один-к-одному | Возвращает экземпляр модели связанной записи напрямую | findFirst |
| Многие-ко-многим | Возвращает коллекцию экземпляров моделей связанной модели, неявно выполняет «внутренние соединения» со связанными моделями | (сложный запрос) |
Вы также можете использовать префикс «count» для возврата целого числа, обозначающего количество связанных записей:
$robot = Robots::findFirst(2); echo "The robot has ", $robot->countRobotsParts(), " parts\n";
Псевдонимы взаимоотношений
Для лучшего понимания работы псевдонимов давайте рассмотрим следующий пример:
Таблица «robots_similar» имеет функцию определения похожих роботов:
mysql> desc robots_similar; +-------------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | NO | PRI | NULL | auto_increment | | robots_id | int(10) unsigned | NO | MUL | NULL | | | similar_robots_id | int(10) unsigned | NO | | NULL | | +-------------------+------------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec)
И «robots_id», и «similar_robots_id» имеют взаимоотношение с моделью Robots:
Модель, которая отображает эту таблицу и её взаимоотношения, выглядит так:
class RobotsSimilar extends Phalcon\Mvc\Model
{
public function initialize()
{
$this->belongsTo('robots_id', 'Robots', 'id');
$this->belongsTo('similar_robots_id', 'Robots', 'id');
}
}
Поскольку оба взаимоотношения указывают на одну и ту же модель (Robots), получение записей, связанных с взаимоотношением, может быть неясным:
$robotsSimilar = RobotsSimilar::findFirst(); //Returns the related record based on the column (robots_id) //Also as is a belongsTo it's only returning one record //but the name 'getRobots' seems to imply that return more than one $robot = $robotsSimilar->getRobots(); //but, how to get the related record based on the column (similar_robots_id) //if both relationships have the same name?
Псевдонимы позволяют переименовать оба взаимоотношения для решения этих проблем:
class RobotsSimilar extends Phalcon\Mvc\Model
{
public function initialize()
{
$this->belongsTo('robots_id', 'Robots', 'id', array(
'alias' => 'Robot'
));
$this->belongsTo('similar_robots_id', 'Robots', 'id', array(
'alias' => 'SimilarRobot'
));
}
}
С псевдонимами мы можем легко получить связанные записи:
$robotsSimilar = RobotsSimilar::findFirst(); //Returns the related record based on the column (robots_id) $robot = $robotsSimilar->getRobot(); $robot = $robotsSimilar->robot; //Returns the related record based on the column (similar_robots_id) $similarRobot = $robotsSimilar->getSimilarRobot(); $similarRobot = $robotsSimilar->similarRobot;
Магические геттеры против явных методов
Большинство IDE и редакторов с функцией автодополнения не могут определить правильные типы при использовании магических геттеров. Вместо магических геттеров вы можете явным образом определить эти методы с соответствующими docblock, что поможет IDE создавать более качественный автодополнение:
class Robots extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany("id", "RobotsParts", "robots_id");
}
/**
* Return the related "robots parts"
*
* @return \RobotsParts[]
*/
public function getRobotsParts($parameters=null)
{
return $this->getRelated('RobotsParts', $parameters);
}
}
Виртуальные внешние ключи
По умолчанию взаимоотношения не работают как внешние ключи базы данных, то есть если вы попытаетесь вставить/обновить значение без действительного значения в связанной модели, Phalcon не выдаст сообщение об ошибке проверки. Вы можете изменить это поведение, добавив четвёртый параметр при определении взаимоотношения.
Модель RobotsPart можно изменить, чтобы продемонстрировать эту функцию:
class RobotsParts extends \Phalcon\Mvc\Model
{
public $id;
public $robots_id;
public $parts_id;
public function initialize()
{
$this->belongsTo("robots_id", "Robots", "id", array(
"foreignKey" => true
));
$this->belongsTo("parts_id", "Parts", "id", array(
"foreignKey" => array(
"message" => "The part_id does not exist on the Parts model"
)
));
}
}
Если вы измените взаимоотношение belongsTo() на внешние ключи, это будет проверять, имеют ли вставленные/обновлённые значения действительное значение в связанной модели. Аналогично, если взаимоотношение hasMany()/hasOne() изменится, это будет проверять, что записи нельзя удалить, если эта запись используется в связанной модели.
class Parts extends \Phalcon\Mvc\Model
{
public function initialize()
{
$this->hasMany("id", "RobotsParts", "parts_id", array(
"foreignKey" => array(
"message" => "The part cannot be deleted because other robots are using it"
)
));
}
}
Каскадные/ограничивающие действия
Взаимоотношения, которые работают как виртуальные внешние ключи по умолчанию, ограничивают создание/обновление/удаление записей для поддержания целостности данных:
namespace Store\Models;
use Phalcon\Mvc\Model,
Phalcon\Mvc\Model\Relation;
class Robots extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany('id', 'Store\\Models\Parts', 'robots_id', array(
'foreignKey' => array(
'action' => Relation::ACTION_CASCADE
)
));
}
}
Приведённый выше код настроен на удаление всех ссылающихся записей (parts) при удалении главной записи (robot).
Генерация вычислений
Вычисления (или агрегации) — это помощники для часто используемых функций систем баз данных, таких как COUNT, SUM, MAX, MIN или AVG. Phalcon\Mvc\Model позволяет использовать эти функции напрямую из доступных методов.
Примеры подсчёта:
// How many employees are?
$rowcount = Employees::count();
// How many different areas are assigned to employees?
$rowcount = Employees::count(array("distinct" => "area"));
// How many employees are in the Testing area?
$rowcount = Employees::count("area = 'Testing'");
// Count employees grouping results by their area
$group = Employees::count(array("group" => "area"));
foreach ($group as $row) {
echo "There are ", $row->rowcount, " in ", $row->area;
}
// Count employees grouping by their area and ordering the result by count
$group = Employees::count(array(
"group" => "area",
"order" => "rowcount"
));
// Avoid SQL injections using bound parameters
$group = Employees::count(array(
"type > ?0",
"bind" => array($type)
));
Примеры суммирования:
// How much are the salaries of all employees?
$total = Employees::sum(array("column" => "salary"));
// How much are the salaries of all employees in the Sales area?
$total = Employees::sum(array(
"column" => "salary",
"conditions" => "area = 'Sales'"
));
// Generate a grouping of the salaries of each area
$group = Employees::sum(array(
"column" => "salary",
"group" => "area"
));
foreach ($group as $row) {
echo "The sum of salaries of the ", $row->area, " is ", $row->sumatory;
}
// Generate a grouping of the salaries of each area ordering
// salaries from higher to lower
$group = Employees::sum(array(
"column" => "salary",
"group" => "area",
"order" => "sumatory DESC"
));
// Avoid SQL injections using bound parameters
$group = Employees::sum(array(
"conditions" => "area > ?0",
"bind" => array($area)
));
Примеры среднего значения:
// What is the average salary for all employees?
$average = Employees::average(array("column" => "salary"));
// What is the average salary for the Sales's area employees?
$average = Employees::average(array(
"column" => "salary",
"conditions" => "area = 'Sales'"
));
// Avoid SQL injections using bound parameters
$average = Employees::average(array(
"column" => "age",
"conditions" => "area > ?0",
"bind" => array($area)
));
Примеры max/min:
// What is the oldest age of all employees?
$age = Employees::maximum(array("column" => "age"));
// What is the oldest of employees from the Sales area?
$age = Employees::maximum(array(
"column" => "age",
"conditions" => "area = 'Sales'"
));
// What is the lowest salary of all employees?
$salary = Employees::minimum(array("column" => "salary"));
Режимы гидратации
Как упоминалось выше, наборы результатов являются коллекциями полных объектов, что означает, что каждый возвращённый результат — это объект, представляющий строку в базе данных. Эти объекты могут быть изменены и сохранены в постоянном хранилище:
// Manipulating a resultset of complete objects
foreach (Robots::find() as $robot) {
$robot->year = 2000;
$robot->save();
}
Иногда записи получают только для отображения пользователю в режиме только для чтения, в таких случаях может быть полезно изменить способ представления записей для удобства их обработки. Стратегия представления объектов, возвращаемых в наборе результатов, называется «режимом гидратации»:
use Phalcon\Mvc\Model\Resultset;
$robots = Robots::find();
//Return every robot as an array
$robots->setHydrateMode(Resultset::HYDRATE_ARRAYS);
foreach ($robots as $robot) {
echo $robot['year'], PHP_EOL;
}
//Return every robot as an stdClass
$robots->setHydrateMode(Resultset::HYDRATE_OBJECTS);
foreach ($robots as $robot) {
echo $robot->year, PHP_EOL;
}
//Return every robot as a Robots instance
$robots->setHydrateMode(Resultset::HYDRATE_RECORDS);
foreach ($robots as $robot) {
echo $robot->year, PHP_EOL;
}
Режим гидратации также можно передать как параметр «find»:
use Phalcon\Mvc\Model\Resultset;
$robots = Robots::find(array(
'hydration' => Resultset::HYDRATE_ARRAYS
));
foreach ($robots as $robot) {
echo $robot['year'], PHP_EOL;
}
Создание/обновление записей
Метод Phalcon\Mvc\Model::save() позволяет создавать/обновлять записи в зависимости от того, существуют ли они уже в таблице, связанной с моделью. Метод save вызывается внутренне методами create и update Phalcon\Mvc\Model. Для корректной работы необходимо правильно определить первичный ключ в сущности, чтобы определить, следует ли обновлять или создавать запись.
Также метод выполняет связанные валидаторы, виртуальные внешние ключи и события, определённые в модели:
$robot = new Robots();
$robot->type = "mechanical";
$robot->name = "Astro Boy";
$robot->year = 1952;
if ($robot->save() == false) {
echo "Umh, We can't store robots right now: \n";
foreach ($robot->getMessages() as $message) {
echo $message, "\n";
}
} else {
echo "Great, a new robot was saved successfully!";
}
В массив, передаваемый в «save», можно поместить данные, чтобы избежать ручного назначения каждой колонки. Phalcon\Mvc\Model проверит, существуют ли сеттеры для столбцов, перечисленных в массиве, отдавая им предпочтение перед прямым присваиванием значений атрибутов:
$robot = new Robots();
$robot->save(array(
"type" => "mechanical",
"name" => "Astro Boy",
"year" => 1952
));
Значения, назначенные напрямую или через массив атрибутов, экранируются/обрабатываются в соответствии с типом данных соответствующего атрибута. Таким образом, вы можете передавать небезопасный массив, не беспокоясь о возможных SQL-инъекциях:
$robot = new Robots(); $robot->save($_POST);
Без мер предосторожности массовое назначение может позволить злоумышленникам установить значения любой колонки базы данных. Используйте эту функцию только в том случае, если вы хотите разрешить пользователю вставить/обновить каждую колонку в модели, даже если эти поля отсутствуют в отправленной форме.
Вы можете установить дополнительный параметр в «save», чтобы задать белый список полей, которые должны учитываться при массовом назначении:
$robot = new Robots();
$robot->save($_POST, array('name', 'type'));
Создание/Обновление с уверенностью
В приложениях с большой конкуренцией может случиться так, что при попытке создать запись, она фактически обновится. Это может произойти, если мы используем Phalcon\Mvc\Model::save() для сохранения записей в базе данных. Если нам необходимо быть абсолютно уверенными, что запись создана или обновлена, мы можем заменить вызов save() на create() или update():
$robot = new Robots();
$robot->type = "mechanical";
$robot->name = "Astro Boy";
$robot->year = 1952;
//This record only must be created
if ($robot->create() == false) {
echo "Umh, We can't store robots right now: \n";
foreach ($robot->getMessages() as $message) {
echo $message, "\n";
}
} else {
echo "Great, a new robot was created successfully!";
}
Эти методы «create» и «update» также принимают массив значений в качестве параметра.
Автоматически генерируемые колонки идентификатора
Некоторые модели могут иметь колонки идентификатора. Эти колонки обычно являются первичным ключом сопоставленной таблицы. Phalcon\Mvc\Model может распознавать колонку идентификатора, опуская её в сгенерированном SQL INSERT, чтобы система базы данных могла сгенерировать для неё значение автоматически. После создания записи поле идентификатора будет зарегистрировано со значением, сгенерированным в системе базы данных:
$robot->save(); echo "The generated id is: ", $robot->id;
Phalcon\Mvc\Model способен распознать колонку идентификатора. В зависимости от системы базы данных, такие колонки могут быть последовательными, как в PostgreSQL, или автоинкрементными, как в MySQL.
PostgreSQL использует последовательности для генерации автоматических числовых значений. По умолчанию Phalcon пытается получить сгенерированное значение из последовательности «table_field_seq», например: robots_id_seq. Если последовательность имеет другое имя, необходимо реализовать метод «getSequenceName»:
class Robots extends \Phalcon\Mvc\Model
{
public function getSequenceName()
{
return "robots_sequence_name";
}
}
Сохранение связанных записей
Магические свойства могут использоваться для сохранения записей и их связанных свойств:
// Create an artist $artist = new Artists(); $artist->name = 'Shinichi Osawa'; $artist->country = 'Japan'; // Create an album $album = new Albums(); $album->name = 'The One'; $album->artist = $artist; //Assign the artist $album->year = 2008; //Save both records $album->save();
Сохранение записи и её связанных записей в отношении «многие ко многим»:
// Get an existing artist
$artist = Artists::findFirst('name = "Shinichi Osawa"');
// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist;
$songs = array();
// Create a first song
$songs[0] = new Songs();
$songs[0]->name = 'Star Guitar';
$songs[0]->duration = '5:54';
// Create a second song
$songs[1] = new Songs();
$songs[1]->name = 'Last Days';
$songs[1]->duration = '4:29';
// Assign the songs array
$album->songs = $songs;
// Save the album + its songs
$album->save();
Сохранение альбома и исполнителя одновременно неявно использует транзакцию. Таким образом, если при сохранении связанных записей произойдёт ошибка, родительская запись также не будет сохранена. Пользователю будут переданы сообщения с информацией об ошибках.
Примечание: Добавление связанных сущностей путём перегрузки следующих методов невозможно:
- Phalcon\Mvc\Model::beforeSave()
- Phalcon\Mvc\Model::beforeCreate()
- Phalcon\Mvc\Model::beforeUpdate()
Для этого нужно перегрузить Phalcon\Mvc\Model::save() внутри модели.
Сообщения валидации
Phalcon\Mvc\Model имеет систему сообщений, которая предоставляет гибкий способ вывода или хранения сообщений валидации, сгенерированных во время процессов вставки/обновления.
Каждое сообщение состоит из экземпляра класса Phalcon\Mvc\Model\Message. Набор сгенерированных сообщений можно получить с помощью метода getMessages(). Каждое сообщение предоставляет расширенную информацию, такую как имя поля, сгенерировавшего сообщение, или тип сообщения:
if ($robot->save() == false) {
foreach ($robot->getMessages() as $message) {
echo "Message: ", $message->getMessage();
echo "Field: ", $message->getField();
echo "Type: ", $message->getType();
}
}
Phalcon\Mvc\Model может генерировать следующие типы сообщений валидации:
| Тип | Описание |
|---|---|
| PresenceOf | Генерируется, когда поле с не-null атрибутом в базе данных пытается вставить/обновить значение null |
| ConstraintViolation | Генерируется, когда поле, являющееся частью виртуального внешнего ключа, пытается вставить/обновить значение, которого нет в связанной модели |
| InvalidValue | Генерируется, когда валидатор завершился неудачно из-за некорректного значения |
| InvalidCreateAttempt | Генерируется, когда запись пытается быть создана, но она уже существует |
| InvalidUpdateAttempt | Генерируется, когда запись пытается быть обновлена, но она не существует |
Метод getMessages() можно переопределить в модели, чтобы заменить/перевести стандартные сообщения, сгенерированные ORM автоматически:
class Robots extends Phalcon\Mvc\Model
{
public function getMessages()
{
$messages = array();
foreach (parent::getMessages() as $message) {
switch ($message->getType()) {
case 'InvalidCreateAttempt':
$messages[] = 'The record cannot be created because it already exists';
break;
case 'InvalidUpdateAttempt':
$messages[] = 'The record cannot be updated because it already exists';
break;
case 'PresenceOf':
$messages[] = 'The field ' . $message->getField() . ' is mandatory';
break;
}
}
return $messages;
}
}
События и диспетчер событий
Модели позволяют реализовывать события, которые будут вызываться при выполнении операции вставки/обновления/удаления. Они помогают определить бизнес-правила для конкретной модели. Ниже приведены события, поддерживаемые Phalcon\Mvc\Model, и их порядок выполнения:
| Операция | Название | Можно остановить операцию? | Описание |
|---|---|---|---|
| Вставка/Обновление | beforeValidation | ДА | Выполняется до валидации полей на не-null/пустые строки или внешние ключи |
| Вставка | beforeValidationOnCreate | ДА | Выполняется до валидации полей на не-null/пустые строки или внешние ключи при операции вставки |
| Обновление | beforeValidationOnUpdate | ДА | Выполняется до валидации полей на не-null/пустые строки или внешние ключи при операции обновления |
| Вставка/Обновление | onValidationFails | ДА (операция уже остановлена) | Выполняется после того, как валидатор целостности завершился неудачно |
| Вставка | afterValidationOnCreate | ДА | Выполняется после валидации полей на не-null/пустые строки или внешние ключи при операции вставки |
| Обновление | afterValidationOnUpdate | ДА | Выполняется после валидации полей на не-null/пустые строки или внешние ключи при операции обновления |
| Вставка/Обновление | afterValidation | ДА | Выполняется после валидации полей на не-null/пустые строки или внешние ключи |
| Вставка/Обновление | beforeSave | ДА | Выполняется до требуемой операции над системой базы данных |
| Обновление | beforeUpdate | ДА | Выполняется до требуемой операции над системой базы данных только при обновлении |
| Вставка | beforeCreate | ДА | Выполняется до требуемой операции над системой базы данных только при вставке |
| Обновление | afterUpdate | НЕТ | Выполняется после требуемой операции над системой базы данных только при обновлении |
| Вставка | afterCreate | НЕТ | Выполняется после требуемой операции над системой базы данных только при вставке |
| Вставка/Обновление | afterSave | НЕТ | Выполняется после требуемой операции над системой базы данных |
Реализация событий в классе модели
Проще всего заставить модель реагировать на события, реализовав метод с тем же именем, что и событие, в классе модели:
class Robots extends \Phalcon\Mvc\Model
{
public function beforeValidationOnCreate()
{
echo "This is executed before creating a Robot!";
}
}
События могут быть полезны для назначения значений до выполнения операции, например:
class Products extends \Phalcon\Mvc\Model
{
public function beforeCreate()
{
//Set the creation date
$this->created_at = date('Y-m-d H:i:s');
}
public function beforeUpdate()
{
//Set the modification date
$this->modified_in = date('Y-m-d H:i:s');
}
}
Использование настраиваемого диспетчера событий
Кроме того, этот компонент интегрирован с Phalcon\Events\Manager. Это значит, что мы можем создавать слушатели, которые запускаются при срабатывании события.
use Phalcon\Mvc\Model,
Phalcon\Events\Manager as EventsManager;
class Robots extends Model
{
public function initialize()
{
$eventsManager = new EventsManager();
//Attach an anonymous function as a listener for "model" events
$eventsManager->attach('model', function($event, $robot) {
if ($event->getType() == 'beforeSave') {
if ($robot->name == 'Scooby Doo') {
echo "Scooby Doo isn't a robot!";
return false;
}
}
return true;
});
//Attach the events manager to the event
$this->setEventsManager($eventsManager);
}
}
В приведённом выше примере EventsManager только служит посредником между объектом и слушателем (анонимной функцией). События будут передаваться слушателю при сохранении «роботов»:
$robot = new Robots(); $robot->name = 'Scooby Doo'; $robot->year = 1969; $robot->save();
Если мы хотим, чтобы все объекты, созданные в нашем приложении, использовали один и тот же EventsManager, то нам нужно назначить его диспетчеру моделей:
//Registering the modelsManager service
$di->setShared('modelsManager', function() {
$eventsManager = new \Phalcon\Events\Manager();
//Attach an anonymous function as a listener for "model" events
$eventsManager->attach('model', function($event, $model){
//Catch events produced by the Robots model
if (get_class($model) == 'Robots') {
if ($event->getType() == 'beforeSave') {
if ($model->name == 'Scooby Doo') {
echo "Scooby Doo isn't a robot!";
return false;
}
}
}
return true;
});
//Setting a default EventsManager
$modelsManager = new ModelsManager();
$modelsManager->setEventsManager($eventsManager);
return $modelsManager;
});
Если слушатель возвращает false, это останавливает выполняемую операцию.
Реализация бизнес-правила
При выполнении вставки, обновления или удаления модель проверяет наличие методов с именами событий, перечисленных в таблице выше.
Мы рекомендуем объявлять методы валидации защищёнными (protected), чтобы предотвратить общедоступность реализации бизнес-логики.
Следующий пример реализует событие, которое проверяет, что год не может быть меньше 0 при обновлении или вставке:
class Robots extends \Phalcon\Mvc\Model
{
public function beforeSave()
{
if ($this->year < 0) {
echo "Year cannot be smaller than zero!";
return false;
}
}
}
Некоторые события возвращают false в качестве указания на остановку текущей операции. Если событие ничего не возвращает, Phalcon\Mvc\Model будет предполагать значение true.
Проверка целостности данных
Phalcon\Mvc\Model предоставляет несколько событий для проверки данных и реализации бизнес-правил. Специальное событие «validation» позволяет вызывать встроенные валидаторы для записи. Phalcon предоставляет несколько встроенных валидаторов, которые могут быть использованы на этом этапе валидации.
Следующий пример показывает, как его использовать:
use Phalcon\Mvc\Model\Validator\InclusionIn,
Phalcon\Mvc\Model\Validator\Uniqueness;
class Robots extends \Phalcon\Mvc\Model
{
public function validation()
{
$this->validate(new InclusionIn(
array(
"field" => "type",
"domain" => array("Mechanical", "Virtual")
)
));
$this->validate(new Uniqueness(
array(
"field" => "name",
"message" => "The robot name must be unique"
)
));
return $this->validationHasFailed() != true;
}
}
В приведённом выше примере выполняется валидация с использованием встроенного валидатора «InclusionIn». Он проверяет значение поля «type» в списке доменов. Если значение не входит в список, то валидатор завершится неудачно и вернёт false. Доступны следующие встроенные валидаторы:
| Имя | Описание | Пример |
|---|---|---|
| PresenceOf | Проверяет, что значение поля не является нулевым или пустой строкой. Этот валидатор автоматически добавляется на основе атрибутов, помеченных как не null в сопоставленной таблице. | Пример |
| Проверяет, что поле содержит корректный формат электронной почты. | Пример | |
| ExclusionIn | Проверяет, что значение не входит в список возможных значений. | Пример |
| InclusionIn | Проверяет, что значение входит в список возможных значений. | Пример |
| Numericality | Проверяет, что поле имеет числовой формат. | Пример |
| Regex | Проверяет, что значение поля соответствует регулярному выражению. | Пример |
| Uniqueness | Проверяет, что поле или комбинация полей не присутствуют более одного раза в существующих записях связанной таблицы. | Пример |
| StringLength | Проверяет длину строки. | Пример |
| Url | Проверяет, что значение имеет корректный формат URL. | Пример |
В дополнение к встроенным валидаторам, вы можете создавать свои собственные валидаторы:
use Phalcon\Mvc\Model\Validator,
Phalcon\Mvc\Model\ValidatorInterface;
class MaxMinValidator extends Validator implements ValidatorInterface
{
public function validate($model)
{
$field = $this->getOption('field');
$min = $this->getOption('min');
$max = $this->getOption('max');
$value = $model->$field;
if ($min <= $value && $value <= $max) {
$this->appendMessage(
"The field doesn't have the right range of values",
$field,
"MaxMinValidator"
);
return false;
}
return true;
}
}
Добавление валидатора к модели:
class Customers extends \Phalcon\Mvc\Model
{
public function validation()
{
$this->validate(new MaxMinValidator(
array(
"field" => "price",
"min" => 10,
"max" => 100
)
));
if ($this->validationHasFailed() == true) {
return false;
}
}
}
Идея создания валидаторов состоит в том, чтобы сделать их повторно используемыми между несколькими моделями. Валидатор также может быть таким простым, как:
use Phalcon\Mvc\Model,
Phalcon\Mvc\Model\Message;
class Robots extends Model
{
public function validation()
{
if ($this->type == "Old") {
$message = new Message(
"Sorry, old robots are not allowed anymore",
"type",
"MyType"
);
$this->appendMessage($message);
return false;
}
return true;
}
}
Предотвращение SQL-инъекций
Каждое значение, назначенное атрибуту модели, экранируется в зависимости от его типа данных. Разработчику не нужно вручную экранировать каждое значение перед его сохранением в базе данных. Phalcon использует внутренне возможность привязки параметров, предоставляемую PDO, для автоматической экранизации каждого значения, которое должно быть сохранено в базе данных.
mysql> desc products; +------------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | NO | PRI | NULL | auto_increment | | product_types_id | int(10) unsigned | NO | MUL | NULL | | | name | varchar(70) | NO | | NULL | | | price | decimal(16,2) | NO | | NULL | | | active | char(1) | YES | | NULL | | +------------------+------------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec)
Если мы используем только PDO для безопасного сохранения записи, нам нужно написать следующий код:
$productTypesId = 1;
$name = 'Artichoke';
$price = 10.5;
$active = 'Y';
$sql = 'INSERT INTO products VALUES (null, :productTypesId, :name, :price, :active)';
$sth = $dbh->prepare($sql);
$sth->bindParam(':productTypesId', $productTypesId, PDO::PARAM_INT);
$sth->bindParam(':name', $name, PDO::PARAM_STR, 70);
$sth->bindParam(':price', doubleval($price));
$sth->bindParam(':active', $active, PDO::PARAM_STR, 1);
$sth->execute();
Хорошая новость в том, что Phalcon делает это автоматически:
$product = new Products(); $product->product_types_id = 1; $product->name = 'Artichoke'; $product->price = 10.5; $product->active = 'Y'; $product->create();
Пропускание столбцов
Чтобы указать Phalcon\Mvc\Model, что всегда пропускать некоторые поля при создании и/или обновлении записей, чтобы делегировать системе баз данных назначение значений с помощью триггера или значения по умолчанию:
class Robots extends \Phalcon\Mvc\Model
{
public function initialize()
{
//Skips fields/columns on both INSERT/UPDATE operations
$this->skipAttributes(array('year', 'price'));
//Skips only when inserting
$this->skipAttributesOnCreate(array('created_at'));
//Skips only when updating
$this->skipAttributesOnUpdate(array('modified_in'));
}
}
Это игнорирует глобально эти поля при каждой операции INSERT/UPDATE во всем приложении. Если вы хотите игнорировать разные атрибуты в разных операциях INSERT/UPDATE, вы можете указать второй параметр (булево значение) — true для замены. Принудительное задание значения по умолчанию можно сделать следующим образом:
$robot = new Robots();
$robot->name = 'Bender';
$robot->year = 1999;
$robot->created_at = new \Phalcon\Db\RawValue('default');
$robot->create();
Для создания условного назначения автоматических значений по умолчанию также можно использовать обратный вызов:
use Phalcon\Mvc\Model,
Phalcon\Db\RawValue;
class Robots extends Model
{
public function beforeCreate()
{
if ($this->price > 10000) {
$this->type = new RawValue('default');
}
}
}
Никогда не используйте \Phalcon\Db\RawValue для назначения внешних данных (например, пользовательского ввода) или переменных данных. Значение этих полей игнорируется при связывании параметров с запросом. Поэтому его можно использовать для атаки приложения, вводя SQL.
Динамическое обновление
SQL-запросы UPDATE по умолчанию создаются со всеми столбцами, определенными в модели (полное обновление всех полей SQL). Вы можете изменить определенные модели, чтобы выполнить динамические обновления, в этом случае используются только поля, которые были изменены, для создания окончательного SQL-запроса.
В некоторых случаях это может улучшить производительность, уменьшив трафик между приложением и сервером базы данных, что особенно полезно, когда таблица содержит поля blob/text:
class Robots extends Phalcon\Mvc\Model
{
public function initialize()
{
$this->useDynamicUpdate(true);
}
}
Удаление записей
Метод Phalcon\Mvc\Model::delete() позволяет удалить запись. Вы можете использовать его следующим образом:
$robot = Robots::findFirst(11);
if ($robot != false) {
if ($robot->delete() == false) {
echo "Sorry, we can't delete the robot right now: \n";
foreach ($robot->getMessages() as $message) {
echo $message, "\n";
}
} else {
echo "The robot was deleted successfully!";
}
}
Вы также можете удалить несколько записей, пройдя по набору результатов с помощью цикла foreach:
foreach (Robots::find("type='mechanical'") as $robot) {
if ($robot->delete() == false) {
echo "Sorry, we can't delete the robot right now: \n";
foreach ($robot->getMessages() as $message) {
echo $message, "\n";
}
} else {
echo "The robot was deleted successfully!";
}
}
Следующие события доступны для определения пользовательских правил обработки, которые могут выполняться при выполнении операции удаления:
| Операция | Имя | Может остановить операцию? | Описание |
|---|---|---|---|
| Удаление | beforeDelete | ДА | Выполняется перед выполнением операции удаления. |
| Удаление | afterDelete | НЕТ | Выполняется после выполнения операции удаления. |
С помощью вышеперечисленных событий можно также определить правила обработки в моделях:
class Robots extends Phalcon\Mvc\Model
{
public function beforeDelete()
{
if ($this->status == 'A') {
echo "The robot is active, it can't be deleted";
return false;
}
return true;
}
}
События при ошибке валидации
Другой тип событий доступен, когда процесс валидации данных обнаруживает какие-либо несоответствия:
| Операция | Имя | Описание |
|---|---|---|
| Вставка или обновление | notSave | Вызывается, когда операция INSERT или UPDATE завершается неудачно по любой причине. |
| Вставка, удаление или обновление | onValidationFails | Вызывается, когда любая операция обработки данных завершается неудачно. |
Поведения
Поведения — это общие правила, которые могут принять несколько моделей, чтобы повторно использовать код. ORM предоставляет API для реализации поведения в ваших моделях. Также вы можете использовать события и обратные вызовы, как показано выше, в качестве альтернативы для реализации поведения с большей свободой.
Поведение должно быть добавлено в инициализатор модели. Модель может иметь ноль или более поведений:
use Phalcon\Mvc\Model\Behavior\Timestampable;
class Users extends \Phalcon\Mvc\Model
{
public $id;
public $name;
public $created_at;
public function initialize()
{
$this->addBehavior(new Timestampable(
array(
'beforeCreate' => array(
'field' => 'created_at',
'format' => 'Y-m-d'
)
)
));
}
}
Следующие встроенные поведения предоставляются фреймворком:
| Имя | Описание |
|---|---|
| Timestampable | Позволяет автоматически обновлять атрибут модели, сохраняя дату и время создания или обновления записи. |
| SoftDelete | Вместо постоянного удаления записи, оно помечает запись как удаленную, изменяя значение столбца флага. |
Timestampable
Это поведение получает массив опций. Ключ первого уровня должен быть именем события, указывающим, когда должен быть назначен столбец:
public function initialize()
{
$this->addBehavior(new Timestampable(
array(
'beforeCreate' => array(
'field' => 'created_at',
'format' => 'Y-m-d'
)
)
));
}
Каждое событие может иметь свои собственные опции. ‘field’ — это имя столбца, который должен быть обновлен. Если ‘format’ — это строка, она будет использоваться в качестве формата функции PHP date. Формат также может быть анонимной функцией, предоставляя вам свободу генерировать любой тип отметки времени:
public function initialize()
{
$this->addBehavior(new Timestampable(
array(
'beforeCreate' => array(
'field' => 'created_at',
'format' => function() {
$datetime = new Datetime(new DateTimeZone('Europe/Stockholm'));
return $datetime->format('Y-m-d H:i:sP');
}
)
)
));
}
Если опция ‘format’ опущена, будет использоваться отметка времени, использующая функцию PHP time.
SoftDelete
Это поведение можно использовать следующим образом:
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class Users extends \Phalcon\Mvc\Model
{
const DELETED = 'D';
const NOT_DELETED = 'N';
public $id;
public $name;
public $status;
public function initialize()
{
$this->addBehavior(new SoftDelete(
array(
'field' => 'status',
'value' => Users::DELETED
)
));
}
}
Это поведение принимает две опции: ‘field’ и ‘value’. ‘field’ определяет, какой столбец должен быть обновлен, а ‘value’ — значение для удаления. Предположим, таблица ‘users’ имеет следующие данные:
mysql> select * from users; +----+---------+--------+ | id | name | status | +----+---------+--------+ | 1 | Lana | N | | 2 | Brandon | N | +----+---------+--------+ 2 rows in set (0.00 sec)
Если мы удалим любой из двух записей, статус будет обновлен вместо удаления записи:
Users::findFirst(2)->delete();
Результат операции — следующие данные в таблице:
mysql> select * from users; +----+---------+--------+ | id | name | status | +----+---------+--------+ | 1 | Lana | N | | 2 | Brandon | D | +----+---------+--------+ 2 rows in set (0.01 sec)
Обратите внимание, что вам необходимо указать условие удаления в ваших запросах, чтобы эффективно игнорировать их как удаленные записи. Это поведение не поддерживает это.
Создание собственных поведений
ORM предоставляет API для создания собственных поведений. Поведение должно быть классом, реализующим Phalcon\Mvc\Model\BehaviorInterface. Также Phalcon\Mvc\Model\Behavior предоставляет большинство необходимых методов для упрощения реализации поведений.
Следующее поведение является примером. Оно реализует поведение Blamable, которое помогает определить пользователя, который выполнил операции над моделью:
use Phalcon\Mvc\Model\Behavior;
use Phalcon\Mvc\Model\BehaviorInterface;
class Blameable extends Behavior implements BehaviorInterface
{
public function notify($eventType, $model)
{
switch ($eventType) {
case 'afterCreate':
case 'afterDelete':
case 'afterUpdate':
$userName = // ... get the current user from session
//Store in a log the username - event type and primary key
file_put_contents(
'logs/blamable-log.txt',
$userName . ' ' . $eventType . ' ' . $model->id
);
break;
default:
/* ignore the rest of events */
}
}
}
Предыдущее — очень простое поведение, но оно демонстрирует, как создать поведение. Теперь давайте добавим это поведение к модели:
class Profiles extends \Phalcon\Mvc\Model
{
public function initialize()
{
$this->addBehavior(new Blamable());
}
}
Поведение также способно перехватывать недостающие методы в ваших моделях:
use Phalcon\Mvc\Model\Behavior,
Phalcon\Mvc\Model\BehaviorInterface;
class Sluggable extends Behavior implements BehaviorInterface
{
public function missingMethod($model, $method, $arguments=array())
{
// if the method is 'getSlug' convert the title
if ($method == 'getSlug') {
return Phalcon\Tag::friendlyTitle($model->title);
}
}
}
Вызов этого метода в модели, которая реализует Sluggable, возвращает SEO-дружественный заголовок:
$title = $post->getSlug();
Использование Traits в качестве поведений
Начиная с PHP 5.4, вы можете использовать Traits для повторного использования кода в ваших классах. Это другой способ реализации пользовательских поведений. Следующий Trait реализует упрощенную версию поведения Timestampable:
trait MyTimestampable
{
public function beforeCreate()
{
$this->created_at = date('r');
}
public function beforeUpdate()
{
$this->updated_at = date('r');
}
}
Затем вы можете использовать его в вашей модели следующим образом:
class Products extends \Phalcon\Mvc\Model
{
use MyTimestampable;
}
Транзакции
Когда процесс выполняет несколько операций с базой данных, часто требуется, чтобы каждый шаг был успешно завершен для поддержания целостности данных. Транзакции предоставляют возможность гарантировать, что все операции с базой данных были выполнены успешно до сохранения данных в базе данных.
Транзакции в Phalcon позволяют вам подтвердить все операции, если они были выполнены успешно, или отменить все операции, если что-то пошло не так.
Ручные транзакции
Если приложение использует только одну соединение, и транзакции не очень сложные, транзакцию можно создать, просто переведя текущее соединение в режим транзакции, выполнив откат или подтверждение, если операция прошла успешно или нет:
class RobotsController extends Phalcon\Mvc\Controller
{
public function saveAction()
{
$this->db->begin();
$robot = new Robots();
$robot->name = "WALL·E";
$robot->created_at = date("Y-m-d");
if ($robot->save() == false) {
$this->db->rollback();
return;
}
$robotPart = new RobotParts();
$robotPart->robots_id = $robot->id;
$robotPart->type = "head";
if ($robotPart->save() == false) {
$this->db->rollback();
return;
}
$this->db->commit();
}
}
Неявные транзакции
Существующие отношения могут использоваться для хранения записей и их связанных экземпляров, этот тип операции неявно создает транзакцию для обеспечения правильного хранения данных:
$robotPart = new RobotParts();
$robotPart->type = "head";
$robot = new Robots();
$robot->name = "WALL·E";
$robot->created_at = date("Y-m-d");
$robot->robotPart = $robotPart;
$robot->save(); //Creates an implicit transaction to store both records
Изолированные транзакции
Изолированные транзакции выполняются в новом соединении, гарантируя, что весь сгенерированный SQL, виртуальные проверки внешних ключей и правила бизнеса изолированы от основного соединения. Этот тип транзакции требует менеджера транзакций, который глобально управляет каждой созданной транзакцией, гарантируя, что они правильно откатываются/подтверждаются перед завершением запроса:
use Phalcon\Mvc\Model\Transaction\Manager as TxManager,
Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
try {
//Create a transaction manager
$manager = new TxManager();
// Request a transaction
$transaction = $manager->get();
$robot = new Robots();
$robot->setTransaction($transaction);
$robot->name = "WALL·E";
$robot->created_at = date("Y-m-d");
if ($robot->save() == false) {
$transaction->rollback("Cannot save robot");
}
$robotPart = new RobotParts();
$robotPart->setTransaction($transaction);
$robotPart->robots_id = $robot->id;
$robotPart->type = "head";
if ($robotPart->save() == false) {
$transaction->rollback("Cannot save robot part");
}
//Everything goes fine, let's commit the transaction
$transaction->commit();
} catch(TxFailed $e) {
echo "Failed, reason: ", $e->getMessage();
}
Транзакции могут использоваться для удаления многих записей согласованным образом:
use Phalcon\Mvc\Model\Transaction\Manager as TxManager,
Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
try {
//Create a transaction manager
$manager = new TxManager();
//Request a transaction
$transaction = $manager->get();
//Get the robots will be deleted
foreach (Robots::find("type = 'mechanical'") as $robot) {
$robot->setTransaction($transaction);
if ($robot->delete() == false) {
//Something goes wrong, we should to rollback the transaction
foreach ($robot->getMessages() as $message) {
$transaction->rollback($message->getMessage());
}
}
}
//Everything goes fine, let's commit the transaction
$transaction->commit();
echo "Robots were deleted successfully!";
} catch(TxFailed $e) {
echo "Failed, reason: ", $e->getMessage();
}
Транзакции повторно используются независимо от того, где был получен объект транзакции. Новая транзакция генерируется только при выполнении commit() или rollback(). Вы можете использовать контейнер сервисов для создания глобального менеджера транзакций для всего приложения:
$di->setShared('transactions', function(){
return new \Phalcon\Mvc\Model\Transaction\Manager();
});
Затем обратитесь к нему из контроллера или представления:
class ProductsController extends \Phalcon\Mvc\Controller
{
public function saveAction()
{
//Obtain the TransactionsManager from the services container
$manager = $this->di->getTransactions();
//Or
$manager = $this->transactions;
//Request a transaction
$transaction = $manager->get();
//...
}
}
Пока активна транзакция, менеджер транзакций всегда будет возвращать ту же транзакцию по всему приложению.
Независимое отображение столбцов
ORM поддерживает независимое отображение столбцов, что позволяет разработчику использовать разные имена столбцов в модели по сравнению с именами в таблице. Phalcon распознает новые имена столбцов и переименовывает их соответственно, чтобы соответствовать соответствующим столбцам в базе данных. Это отличная функция, когда необходимо переименовать поля в базе данных, не беспокоясь обо всех запросах в коде. Изменение отображения столбцов в модели позаботится обо всём остальном. Например:
class Robots extends \Phalcon\Mvc\Model
{
public function columnMap()
{
//Keys are the real names in the table and
//the values their names in the application
return array(
'id' => 'code',
'the_name' => 'theName',
'the_type' => 'theType',
'the_year' => 'theYear'
);
}
}
Затем вы можете естественным образом использовать новые имена в своем коде:
//Find a robot by its name
$robot = Robots::findFirst("theName = 'Voltron'");
echo $robot->theName, "\n";
//Get robots ordered by type
$robot = Robots::find(array('order' => 'theType DESC'));
foreach ($robots as $robot) {
echo 'Code: ', $robot->code, "\n";
}
//Create a robot
$robot = new Robots();
$robot->code = '10101';
$robot->theName = 'Bender';
$robot->theType = 'Industrial';
$robot->theYear = 2999;
$robot->save();
При переименовании столбцов учтите следующее:
- Ссылки на атрибуты в отношениях/валидаторах должны использовать новые имена
- Ссылка на реальные имена столбцов приведет к исключению ORM
Независимое отображение столбцов позволяет:
- Разрабатывать приложения, используя собственные соглашения
- Устранить префиксы/суффиксы поставщика в вашем коде
- Изменять имена столбцов без изменения кода приложения
Операции над наборами результатов
Если набор результатов состоит из полных объектов, набор результатов может выполнять операции над полученными записями простым способом:
Обновление связанных записей
Вместо этого:
foreach ($robots->getParts() as $part) {
$part->stock = 100;
$part->updated_at = time();
if ($part->update() == false) {
foreach ($part->getMessages() as $message) {
echo $message;
}
break;
}
}
Вы можете сделать так:
$robots->getParts()->update(array(
'stock' => 100,
'updated_at' => time()
));
‘update’ также принимает анонимную функцию для фильтрации записей, которые необходимо обновить:
$data = array(
'stock' => 100,
'updated_at' => time()
);
//Update all the parts except these whose type is basic
$robots->getParts()->update($data, function($part) {
if ($part->type == Part::TYPE_BASIC) {
return false;
}
return true;
});
Удаление связанных записей
Вместо этого:
foreach ($robots->getParts() as $part) {
if ($part->delete() == false) {
foreach ($part->getMessages() as $message) {
echo $message;
}
break;
}
}
Вы можете сделать так:
$robots->getParts()->delete();
‘delete’ также принимает анонимную функцию для фильтрации записей, которые необходимо удалить:
//Delete only whose stock is greater or equal than zero
$robots->getParts()->delete(function($part) {
if ($part->stock < 0) {
return false;
}
return true;
});
Снимки записей
Конкретные модели могут быть настроены на поддержание снимка записи при запросе. Вы можете использовать эту функцию для реализации аудита или просто для того, чтобы узнать, какие поля изменены в соответствии с данными, запрошенными из хранилища:
class Robots extends Phalcon\Mvc\Model
{
public function initialize()
{
$this->keepSnapshots(true);
}
}
При активации этой функции приложение потребляет немного больше памяти для отслеживания исходных значений, полученных из хранилища. В моделях, где эта функция активирована, вы можете проверить, какие поля изменились:
//Get a record from the database
$robot = Robots::findFirst();
//Change a column
$robot->name = 'Other name';
var_dump($robot->getChangedFields()); // ['name']
var_dump($robot->hasChanged('name')); // true
var_dump($robot->hasChanged('type')); // false
Метаданные моделей
Для ускорения разработки Phalcon\Mvc\Model помогает запрашивать поля и ограничения из таблиц, связанных с моделями. Для этого доступен Phalcon\Mvc\Model\MetaData для управления и кеширования метаданных таблиц.
Иногда при работе с моделями необходимо получить эти атрибуты. Вы можете получить экземпляр метаданных следующим образом:
$robot = new Robots(); // Get Phalcon\Mvc\Model\Metadata instance $metaData = $robot->getModelsMetaData(); // Get robots fields names $attributes = $metaData->getAttributes($robot); print_r($attributes); // Get robots fields data types $dataTypes = $metaData->getDataTypes($robot); print_r($dataTypes);
Кеширование метаданных
Когда приложение находится в стадии производства, нет необходимости каждый раз запрашивать метаданные таблицы из системы базы данных при использовании таблицы. Это можно сделать, кэшируя метаданные, используя любой из следующих адаптеров:
| Адаптер | Описание | API |
|---|---|---|
| Память | Этот адаптер является по умолчанию. Метаданные кэшируются только во время запроса. По завершении запроса метаданные освобождаются как часть обычной памяти запроса. Этот адаптер идеально подходит для разработки, чтобы обновлять метаданные в каждом запросе, содержащем новые и/или измененные поля. | Phalcon\Mvc\Model\MetaData\Memory |
| Сессия | Этот адаптер сохраняет метаданные в $_SESSION. Этот адаптер рекомендуется только при использовании небольшого числа моделей. Метаданные обновляются каждый раз при запуске новой сессии. Также необходимо использовать session_start() для запуска сессии перед использованием моделей. | Phalcon\Mvc\Model\MetaData\Session |
| Apc | Этот адаптер использует Alternative PHP Cache (APC) для хранения метаданных таблицы. Вы можете указать срок действия метаданных с помощью параметров. Это наиболее рекомендуемый способ хранения метаданных, когда приложение находится в стадии производства. | Phalcon\Mvc\Model\MetaData\Apc |
| XCache | Этот адаптер использует XCache для хранения метаданных таблицы. Вы можете указать срок действия метаданных с помощью параметров. Это наиболее рекомендуемый способ хранения метаданных, когда приложение находится в стадии производства. | Phalcon\Mvc\Model\MetaData\Xcache |
| Файлы | Этот адаптер использует обычные файлы для хранения метаданных. Используя этот адаптер, чтение с диска увеличивается, но доступ к базе данных уменьшается | Phalcon\Mvc\Model\MetaData\Files |
Как и другие зависимости ORM, менеджер метаданных запрашивается из контейнера сервисов:
$di['modelsMetadata'] = function() {
// Create a meta-data manager with APC
$metaData = new \Phalcon\Mvc\Model\MetaData\Apc(array(
"lifetime" => 86400,
"prefix" => "my-prefix"
));
return $metaData;
};
Стратегии метаданных
Как упоминалось выше, стратегия по умолчанию для получения метаданных модели — это интроспекция базы данных. В этой стратегии используется схема информации, чтобы узнать поля в таблице, первичный ключ, поля nullable, типы данных и т. д.
Вы можете изменить интроспекцию метаданных по умолчанию следующим образом:
$di['modelsMetadata'] = function() {
// Instantiate a meta-data adapter
$metaData = new \Phalcon\Mvc\Model\MetaData\Apc(array(
"lifetime" => 86400,
"prefix" => "my-prefix"
));
//Set a custom meta-data introspection strategy
$metaData->setStrategy(new MyInstrospectionStrategy());
return $metaData;
};
Стратегия интроспекции базы данных
Эта стратегия не требует каких-либо настроек и неявно используется всеми адаптерами метаданных.
Стратегия аннотаций
Эта стратегия использует аннотации для описания столбцов в модели:
class Robots extends \Phalcon\Mvc\Model
{
/**
* @Primary
* @Identity
* @Column(type="integer", nullable=false)
*/
public $id;
/**
* @Column(type="string", length=70, nullable=false)
*/
public $name;
/**
* @Column(type="string", length=32, nullable=false)
*/
public $type;
/**
* @Column(type="integer", nullable=false)
*/
public $year;
}
Аннотации должны размещаться в свойствах, сопоставленных со столбцами в сопоставленном источнике. Свойства без аннотации @Column обрабатываются как простые атрибуты класса.
Поддерживаются следующие аннотации:
| Имя | Описание |
|---|---|
| Primary | Помечает поле как часть первичного ключа таблицы |
| Identity | Поле является столбцом auto_increment/serial |
| Column | Помечает атрибут как сопоставленный столбец |
Аннотация @Column поддерживает следующие параметры:
| Имя | Описание |
|---|---|
| type | Тип столбца (строка, целое число, десятичное, логическое) |
| length | Длина столбца, если есть |
| nullable | Устанавливает, может ли столбец принимать значения NULL или нет |
Стратегия аннотаций может быть настроена следующим образом:
use Phalcon\Mvc\Model\MetaData\Apc as ApcMetaData,
Phalcon\Mvc\Model\MetaData\Strategy\Annotations as StrategyAnnotations;
$di['modelsMetadata'] = function() {
// Instantiate a meta-data adapter
$metaData = new ApcMetaData(array(
"lifetime" => 86400,
"prefix" => "my-prefix"
));
//Set a custom meta-data database introspection
$metaData->setStrategy(new StrategyAnnotations());
return $metaData;
};
Ручная метаданные
Phalcon может автоматически получать метаданные для каждой модели без необходимости разработчику устанавливать их вручную, используя любую из представленных выше стратегий интроспекции.
Разработчик также может определить метаданные вручную. Эта стратегия переопределяет любую установленную в менеджере метаданных стратегию. Новые столбцы, добавленные/измененные/удаленные из сопоставленной таблицы, также должны быть добавлены/изменены/удалены для правильной работы.
Следующий пример показывает, как определить метаданные вручную:
use Phalcon\Mvc\Model,
Phalcon\Db\Column,
Phalcon\Mvc\Model\MetaData;
class Robots extends Model
{
public function metaData()
{
return array(
//Every column in the mapped table
MetaData::MODELS_ATTRIBUTES => array(
'id', 'name', 'type', 'year'
),
//Every column part of the primary key
MetaData::MODELS_PRIMARY_KEY => array(
'id'
),
//Every column that isn't part of the primary key
MetaData::MODELS_NON_PRIMARY_KEY => array(
'name', 'type', 'year'
),
//Every column that doesn't allows null values
MetaData::MODELS_NOT_NULL => array(
'id', 'name', 'type', 'year'
),
//Every column and their data types
MetaData::MODELS_DATA_TYPES => array(
'id' => Column::TYPE_INTEGER,
'name' => Column::TYPE_VARCHAR,
'type' => Column::TYPE_VARCHAR,
'year' => Column::TYPE_INTEGER
),
//The columns that have numeric data types
MetaData::MODELS_DATA_TYPES_NUMERIC => array(
'id' => true,
'year' => true,
),
//The identity column, use boolean false if the model doesn't have
//an identity column
MetaData::MODELS_IDENTITY_COLUMN => 'id',
//How every column must be bound/casted
MetaData::MODELS_DATA_TYPES_BIND => array(
'id' => Column::BIND_PARAM_INT,
'name' => Column::BIND_PARAM_STR,
'type' => Column::BIND_PARAM_STR,
'year' => Column::BIND_PARAM_INT,
),
//Fields that must be ignored from INSERT SQL statements
MetaData::MODELS_AUTOMATIC_DEFAULT_INSERT => array(
'year' => true
),
//Fields that must be ignored from UPDATE SQL statements
MetaData::MODELS_AUTOMATIC_DEFAULT_UPDATE => array(
'year' => true
)
);
}
}
Указание на другую схему
Если модель сопоставлена с таблицей, которая находится в другой схеме/базе данных, отличной от по умолчанию. Вы можете использовать метод getSchema для ее определения:
class Robots extends \Phalcon\Mvc\Model
{
public function getSchema()
{
return "toys";
}
}
Настройка нескольких баз данных
В Phalcon все модели могут принадлежать одному соединению с базой данных или иметь индивидуальное. Фактически, когда Phalcon\Mvc\Model необходимо подключиться к базе данных, он запрашивает сервис «db» в контейнере сервисов приложения. Вы можете переопределить этот сервис, установив его в методе initialize:
//This service returns a MySQL database
$di->set('dbMysql', function() {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
});
//This service returns a PostgreSQL database
$di->set('dbPostgres', function() {
return new \Phalcon\Db\Adapter\Pdo\PostgreSQL(array(
"host" => "localhost",
"username" => "postgres",
"password" => "",
"dbname" => "invo"
));
});
Затем в методе Initialize мы определяем сервис подключения для модели:
class Robots extends \Phalcon\Mvc\Model
{
public function initialize()
{
$this->setConnectionService('dbPostgres');
}
}
Но Phalcon предлагает вам большую гибкость, вы можете определить соединение, которое должно использоваться для чтения и для записи. Это особенно полезно для балансировки нагрузки на базы данных, реализуя архитектуру master-slave:
class Robots extends \Phalcon\Mvc\Model
{
public function initialize()
{
$this->setReadConnectionService('dbSlave');
$this->setWriteConnectionService('dbMaster');
}
}
ORM также предоставляет средства горизонтального фрагментирования, позволяющие реализовать выбор фрагмента в зависимости от текущих условий запроса:
class Robots extends Phalcon\Mvc\Model
{
/**
* Dynamically selects a shard
*
* @param array $intermediate
* @param array $bindParams
* @param array $bindTypes
*/
public function selectReadConnection($intermediate, $bindParams, $bindTypes)
{
//Check if there is a 'where' clause in the select
if (isset($intermediate['where'])) {
$conditions = $intermediate['where'];
//Choose the possible shard according to the conditions
if ($conditions['left']['name'] == 'id') {
$id = $conditions['right']['value'];
if ($id > 0 && $id < 10000) {
return $this->getDI()->get('dbShard1');
}
if ($id > 10000) {
return $this->getDI()->get('dbShard2');
}
}
}
//Use a default shard
return $this->getDI()->get('dbShard0');
}
}
Метод ‘selectReadConnection’ используется для выбора правильного соединения, этот метод перехватывает любой новый запрос, выполненный:
$robot = Robots::findFirst('id = 101');
Ведение журнала низкоуровневых SQL-заявлений
При использовании компонентов высокого уровня абстракции, таких как Phalcon\Mvc\Model для доступа к базе данных, сложно понять, какие запросы в конечном итоге отправляются в систему базы данных. Phalcon\Mvc\Model поддерживается внутренне компонентом Phalcon\Db. Компонент Phalcon\Logger взаимодействует с Phalcon\Db, предоставляя возможности ведения журнала (логирования) на уровне абстракции базы данных, что позволяет нам регистрировать SQL-запросы по мере их выполнения.
use Phalcon\Logger,
Phalcon\Db\Adapter\Pdo\Mysql as Connection,
Phalcon\Events\Manager,
Phalcon\Logger\Adapter\File as FileLogger;
$di->set('db', function() {
$eventsManager = new EventsManager();
$logger = new FileLogger("app/logs/debug.log");
//Listen all the database events
$eventsManager->attach('db', function($event, $connection) use ($logger) {
if ($event->getType() == 'beforeQuery') {
$logger->log($connection->getSQLStatement(), Logger::INFO);
}
});
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
//Assign the eventsManager to the db adapter instance
$connection->setEventsManager($eventsManager);
return $connection;
});
Так как модели обращаются к подключению к базе данных по умолчанию, все SQL-запросы, отправляемые в систему базы данных, будут регистрироваться в файле:
$robot = new Robots();
$robot->name = "Robby the Robot";
$robot->created_at = "1956-07-21";
if ($robot->save() == false) {
echo "Cannot save robot";
}
Как и выше, файл app/logs/db.log будет содержать что-то вроде этого:
[Mon, 30 Apr 12 13:47:18 -0500][DEBUG][Resource Id #77] INSERT INTO robots
(name, created_at) VALUES ('Robby the Robot', '1956-07-21')
Профилирование SQL-запросов
Благодаря Phalcon\Db, базовому компоненту Phalcon\Mvc\Model, можно профилировать SQL-запросы, генерируемые ORM, для анализа производительности операций с базой данных. Это позволяет диагностировать проблемы с производительностью и обнаруживать узкие места.
$di->set('profiler', function(){
return new \Phalcon\Db\Profiler();
}, true);
$di->set('db', function() use ($di) {
$eventsManager = new \Phalcon\Events\Manager();
//Get a shared instance of the DbProfiler
$profiler = $di->getProfiler();
//Listen all the database events
$eventsManager->attach('db', function($event, $connection) use ($profiler) {
if ($event->getType() == 'beforeQuery') {
$profiler->startProfile($connection->getSQLStatement());
}
if ($event->getType() == 'afterQuery') {
$profiler->stopProfile();
}
});
$connection = new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
//Assign the eventsManager to the db adapter instance
$connection->setEventsManager($eventsManager);
return $connection;
});
Профилирование некоторых запросов:
// Send some SQL statements to the database
Robots::find();
Robots::find(array("order" => "name"));
Robots::find(array("limit" => 30));
//Get the generated profiles from the profiler
$profiles = $di->get('profiler')->getProfiles();
foreach ($profiles as $profile) {
echo "SQL Statement: ", $profile->getSQLStatement(), "\n";
echo "Start Time: ", $profile->getInitialTime(), "\n";
echo "Final Time: ", $profile->getFinalTime(), "\n";
echo "Total Elapsed Time: ", $profile->getTotalElapsedSeconds(), "\n";
}
Каждый созданный профиль содержит время выполнения каждой инструкции в миллисекундах, а также сгенерированный SQL-запрос.
Ввод сервисов в модели
Вам может потребоваться доступ к сервисам приложения внутри модели. Следующий пример поясняет, как это сделать:
class Robots extends \Phalcon\Mvc\Model
{
public function notSave()
{
//Obtain the flash service from the DI container
$flash = $this->getDI()->getFlash();
//Show validation messages
foreach ($this->getMessages() as $message) {
$flash->error($message);
}
}
}
Событие «notSave» срабатывает каждый раз, когда действие «create» или «update» завершается неудачно. Таким образом, мы отображаем сообщения валидации, получая службу «flash» из контейнера DI. Благодаря этому нам не нужно выводить сообщения после каждого сохранения.
Отключение/Включение функций
В ORM реализован механизм, позволяющий включать/отключать определенные функции или параметры глобально в режиме реального времени. В зависимости от того, как вы используете ORM, вы можете отключить те, которыми не пользуетесь. Эти параметры также можно временно отключить при необходимости:
\Phalcon\Mvc\Model::setup(array(
'events' => false,
'columnRenaming' => false
));
Доступные параметры:
| Параметр | Описание | По умолчанию |
|---|---|---|
| events | Включает/Отключает обратные вызовы, хуки и уведомления о событиях для всех моделей | true |
| columnRenaming | Включает/Отключает переименование столбцов | true |
| notNullValidations | ORM автоматически проверяет столбцы not null, присутствующие в сопоставленной таблице | true |
| virtualForeignKeys | Включает/Отключает виртуальные внешние ключи | true |
| phqlLiterals | Включает/Отключает литералы в PHQL-парсере | true |
Самостоятельный компонент
Использование Phalcon\Mvc\Model в автономном режиме показано ниже:
use Phalcon\DI,
Phalcon\Db\Adapter\Pdo\Sqlite as Connection,
Phalcon\Mvc\Model\Manager as ModelsManager,
Phalcon\Mvc\Model\Metadata\Memory as MetaData,
Phalcon\Mvc\Model;
$di = new DI();
//Setup a connection
$di->set('db', new Connection(array(
"dbname" => "sample.db"
)));
//Set a models manager
$di->set('modelsManager', new ModelsManager());
//Use the memory meta-data adapter or other
$di->set('modelsMetadata', new MetaData());
//Create a model
class Robots extends Model
{
}
//Use the model
echo Robots::count();
© 2011–2016 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/2.0.0/reference/models.html