Работа с моделями (Продвинутый уровень)
Режимы гидратации
Как упоминалось ранее, наборы результатов представляют собой коллекции полных объектов, что означает, что каждый возвращённый результат — это объект, представляющий строку в базе данных. Эти объекты можно изменять и сохранять обратно в хранилище:
use Store\Toys\Robots;
$robots = Robots::find();
// Manipulating a resultset of complete objects
foreach ($robots as $robot) {
$robot->year = 2000;
$robot->save();
}
Иногда записи получаются только для отображения пользователю в режиме только для чтения, в этих случаях может быть полезно изменить способ представления записей для удобства их обработки. Стратегия представления объектов, возвращаемых в наборе результатов, называется «режимом гидратации»:
use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;
$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 a 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;
use Store\Toys\Robots;
$robots = Robots::find(
[
"hydration" => Resultset::HYDRATE_ARRAYS,
]
);
foreach ($robots as $robot) {
echo $robot["year"], PHP_EOL;
}
Автоматически генерируемые столбцы идентификатора
Некоторые модели могут иметь столбцы идентификатора. Эти столбцы обычно являются первичным ключом сопоставленной таблицы. 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():
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function getSequenceName()
{
return "robots_sequence_name";
}
}
Пропуск столбцов
Чтобы указать Phalcon\Mvc\Model, что всегда пропускать некоторые поля при создании и/или обновлении записей, чтобы делегировать системе базы данных назначение значений с помощью триггера или значения по умолчанию:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
// Skips fields/columns on both INSERT/UPDATE operations
$this->skipAttributes(
[
"year",
"price",
]
);
// Skips only when inserting
$this->skipAttributesOnCreate(
[
"created_at",
]
);
// Skips only when updating
$this->skipAttributesOnUpdate(
[
"modified_in",
]
);
}
}
Это приведет к глобальному игнорированию этих полей при каждой операции INSERT/UPDATE во всей приложении. Если вы хотите игнорировать разные атрибуты при разных операциях INSERT/UPDATE, вы можете указать второй параметр (булево значение) — true для замены. Принудительное задание значения по умолчанию можно выполнить следующим образом:
use Store\Toys\Robots;
use Phalcon\Db\RawValue;
$robot = new Robots();
$robot->name = "Bender";
$robot->year = 1999;
$robot->created_at = new RawValue("default");
$robot->create();
Также можно использовать обратный вызов для создания условного назначения автоматических значений по умолчанию:
namespace Store\Toys;
use Phalcon\Mvc\Model;
use 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:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->useDynamicUpdate(true);
}
}
Независимое сопоставление столбцов
ORM поддерживает независимое сопоставление столбцов, что позволяет разработчику использовать разные имена столбцов в модели по сравнению с именами в таблице. Phalcon распознает новые имена столбцов и переименует их соответственно, чтобы соответствовать соответствующим столбцам в базе данных. Это отличная функция, когда нужно переименовать поля в базе данных, не беспокоясь обо всех запросах в коде. Изменение сопоставления столбцов в модели позаботится обо всём остальном. Например:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $code;
public $theName;
public $theType;
public $theYear;
public function columnMap()
{
// Keys are the real names in the table and
// the values their names in the application
return [
"id" => "code",
"the_name" => "theName",
"the_type" => "theType",
"the_year" => "theYear",
];
}
}
Затем вы можете естественным образом использовать новые имена в своём коде:
use Store\Toys\Robots;
// Find a robot by its name
$robot = Robots::findFirst(
"theName = 'Voltron'"
);
echo $robot->theName, "\n";
// Get robots ordered by type
$robot = Robots::find(
[
"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
Независимое сопоставление столбцов позволяет:
- Разрабатывать приложения, используя собственные соглашения
- Исключить префиксы/суффиксы поставщика из вашего кода
- Изменять имена столбцов без изменения кода вашего приложения
Снимки записей
Определённые модели могут быть настроены на сохранение снимка записи при запросе. Вы можете использовать эту функцию для реализации аудита или просто для того, чтобы узнать, какие поля изменены в соответствии с данными, запрошенными из хранилища:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->keepSnapshots(true);
}
}
При активации этой функции приложение потребляет немного больше памяти для отслеживания исходных значений, полученных из хранилища. В моделях, у которых эта функция активирована, вы можете проверить, какие поля изменились:
use Store\Toys\Robots;
// 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
Указание на другую схему
Если модель сопоставлена с таблицей, которая находится в другой схеме/базе данных, отличной от стандартной. Вы можете использовать метод setSchema() для определения этого:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setSchema("toys");
}
}
Настройка нескольких баз данных
В Phalcon все модели могут принадлежать одному соединению с базой данных или иметь индивидуальное соединение. Фактически, когда Phalcon\Mvc\Model нуждается в подключении к базе данных, он запрашивает сервис «db» в контейнере сервисов приложения. Вы можете переопределить этот сервис, настроив его в методе initialize():
use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
use Phalcon\Db\Adapter\Pdo\PostgreSQL as PostgreSQLPdo;
// This service returns a MySQL database
$di->set(
"dbMysql",
function () {
return new MysqlPdo(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
);
// This service returns a PostgreSQL database
$di->set(
"dbPostgres",
function () {
return new PostgreSQLPdo(
[
"host" => "localhost",
"username" => "postgres",
"password" => "",
"dbname" => "invo",
]
);
}
);
Затем, в методе initialize(), мы определяем сервис подключения для модели:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setConnectionService("dbPostgres");
}
}
Но Phalcon предлагает вам большую гибкость, вы можете определить соединение, которое должно использоваться для «чтения» и для «записи». Это особенно полезно для балансировки нагрузки на ваши базы данных, реализуя архитектуру мастер-раб.
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setReadConnectionService("dbSlave");
$this->setWriteConnectionService("dbMaster");
}
}
ORM также предоставляет средства горизонтального фрагментирования, позволяющие вам реализовать выбор «фрагмента» в соответствии с текущими условиями запроса:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends 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() используется для выбора правильного соединения. Этот метод перехватывает любой новый запрос, который выполняется:
use Store\Toys\Robots;
$robot = Robots::findFirst('id = 101');
Вставка сервисов в модели
Вам может потребоваться получить доступ к сервисам приложения внутри модели. Следующий пример объясняет, как это сделать:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function notSaved()
{
// Obtain the flash service from the DI container
$flash = $this->getDI()->getFlash();
$messages = $this->getMessages();
// Show validation messages
foreach ($messages as $message) {
$flash->error($message);
}
}
}
Событие «notSaved» срабатывает всякий раз, когда действие «create» или «update» завершается ошибкой. Таким образом, мы выводим сообщения об ошибках проверки, получая службу «flash» из контейнера DI. Делая это, нам не нужно отображать сообщения после каждого сохранения.
Отключение/Включение функций
В ORM реализован механизм, позволяющий включать/отключать определённые функции или параметры глобально на лету. В зависимости от того, как вы используете ORM, вы можете отключить то, что не используете. Эти параметры также можно временно отключить, если это необходимо:
use Phalcon\Mvc\Model;
Model::setup(
[
"events" => false,
"columnRenaming" => false,
]
);
Доступные параметры:
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
| events | Включает/Отключает обратные вызовы, хуки и уведомления о событиях для всех моделей | true |
| columnRenaming | Включает/Отключает переименование столбцов | true |
| notNullValidations | ORM автоматически проверяет столбцы not null, присутствующие в сопоставленной таблице | true |
| virtualForeignKeys | Включает/Отключает виртуальные внешние ключи | true |
| phqlLiterals | Включает/Отключает литералы в парсере PHQL | true |
| lateStateBinding | Включает/Отключает позднюю привязку состояния метода Mvc\Model::cloneResultMap() | false |
Компонент автономного режима
Использование Phalcon\Mvc\Model в автономном режиме показано ниже:
use Phalcon\Di;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager as ModelsManager;
use Phalcon\Db\Adapter\Pdo\Sqlite as Connection;
use Phalcon\Mvc\Model\Metadata\Memory as MetaData;
$di = new Di();
// Setup a connection
$di->set(
"db",
new Connection(
[
"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–2017 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/latest/reference/models-advanced.html