Взаимосвязи моделей
Взаимосвязи между моделями
Существует четыре типа взаимосвязей: один к одному, один ко многим, много к одному и много ко многим. Взаимосвязь может быть однонаправленной или двунаправленной, и каждая может быть простой (модель один к одному) или более сложной (комбинация моделей). Менеджер моделей управляет ограничениями внешних ключей для этих взаимосвязей, определение которых помогает обеспечить реляционную целостность, а также легкий и быстрый доступ к связанным записям для модели. Благодаря реализации отношений легко получить доступ к данным в связанных моделях из каждой записи единообразным способом.
Однонаправленные взаимосвязи
Однонаправленные отношения — это такие, которые генерируются по отношению друг к другу, но не наоборот.
Двунаправленные взаимосвязи
Двунаправленные взаимосвязи строят взаимосвязи в обеих моделях, и каждая модель определяет обратное отношение другой.
Определение взаимосвязей
В 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`)
);
- Модель «Роботы» имеет много «ДеталейРоботов».
- Модель «Детали» имеет много «ДеталейРоботов».
- Модель «ДеталиРоботов» относится к моделям «Роботы» и «Детали» как отношение много-к-одному.
- Модель «Роботы» имеет отношение много-ко-многим к «Детали» через «ДеталиРоботов».
Проверьте диаграмму ER для лучшего понимания отношений:
Модели с их взаимосвязями могут быть реализованы следующим образом:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany(
"id",
"RobotsParts",
"robots_id"
);
}
}
use Phalcon\Mvc\Model;
class Parts extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany(
"id",
"RobotsParts",
"parts_id"
);
}
}
use Phalcon\Mvc\Model;
class RobotsParts extends Model
{
public $id;
public $robots_id;
public $parts_id;
public function initialize()
{
$this->belongsTo(
"robots_id",
"Store\\Toys\\Robots",
"id"
);
$this->belongsTo(
"parts_id",
"Parts",
"id"
);
}
}
Первый параметр указывает поле локальной модели, используемое в взаимосвязи; второй — имя модели-ссылки, а третий — имя поля в модели-ссылке. Вы также можете использовать массивы для определения нескольких полей в взаимосвязи.
Взаимосвязи много ко многим требуют 3 моделей и определяют атрибуты, участвующие во взаимосвязи:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasManyToMany(
"id",
"RobotsParts",
"robots_id", "parts_id",
"Parts",
"id"
);
}
}
Использование взаимосвязей
При явном определении взаимосвязей между моделями легко найти связанные записи для конкретной записи.
use Store\Toys\Robots;
$robot = Robots::findFirst(2);
foreach ($robot->robotsParts as $robotPart) {
echo $robotPart->parts->name, "\n";
}
Phalcon использует магические методы __set/__get/__call для хранения или извлечения связанных данных с помощью взаимосвязей.
Обращение к атрибуту с тем же именем, что и взаимосвязь, позволит получить все связанные записи.
use Store\Toys\Robots; $robot = Robots::findFirst(); // All the related records in RobotsParts $robotsParts = $robot->robotsParts;
Также можно использовать магический геттер:
use Store\Toys\Robots;
$robot = Robots::findFirst();
// All the related records in RobotsParts
$robotsParts = $robot->getRobotsParts();
// Passing parameters
$robotsParts = $robot->getRobotsParts(
[
"limit" => 5,
]
);
Если вызываемый метод имеет префикс «get», Phalcon\Mvc\Model вернёт результат findFirst()/find(). Следующий пример сравнивает получение связанных результатов с использованием магических методов и без них:
use Store\Toys\Robots;
$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 = :date:",
"bind" => [
"date" => "2015-03-15"
]
]
);
$robotPart = RobotsParts::findFirst(1);
// RobotsParts model has a n-1 (belongsTo)
// relationship to RobotsParts then
$robot = $robotPart->robots;
Получение связанных записей вручную:
use Store\Toys\Robots;
$robot = Robots::findFirst(2);
// Robots model has a 1-n (hasMany)
// relationship to RobotsParts, then
$robotsParts = RobotsParts::find(
[
"robots_id = :id:",
"bind" => [
"id" => $robot->id,
]
]
);
// Only parts that match conditions
$robotsParts = RobotsParts::find(
[
"robots_id = :id: AND created_at = :date:",
"bind" => [
"id" => $robot->id,
"date" => "2015-03-15",
]
]
);
$robotPart = RobotsParts::findFirst(1);
// RobotsParts model has a n-1 (belongsTo)
// relationship to RobotsParts then
$robot = Robots::findFirst(
[
"id = :id:",
"bind" => [
"id" => $robotPart->robots_id,
]
]
);
Префикс «get» используется для find()/findFirst() связанных записей. В зависимости от типа взаимосвязи он будет использовать find() или findFirst().
| Тип | Описание | Неявный метод |
|---|---|---|
| Принадлежит-к | Возвращает экземпляр модели связанной записи напрямую | findFirst |
| Один-к-одному | Возвращает экземпляр модели связанной записи напрямую | findFirst |
| Один-ко-многим | Возвращает коллекцию экземпляров моделей сопоставленной модели | find |
| Много-ко-многим | Возвращает коллекцию экземпляров моделей сопоставленной модели, неявно выполняет «внутренние соединения» со связанными моделями | (сложное запросы) |
Вы также можете использовать префикс «count», чтобы получить целое число, обозначающее количество связанных записей:
use Store\Toys\Robots; $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",
"Store\\Toys\\Robots",
"id"
);
$this->belongsTo(
"similar_robots_id",
"Store\\Toys\\Robots",
"id"
);
}
}
Поскольку оба отношения указывают на одну и ту же модель (Роботы), получение записей, связанных с отношением, может быть неясным:
$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?
Псевдонимы позволяют переименовать оба отношения для решения этих проблем:
use Phalcon\Mvc\Model;
class RobotsSimilar extends Model
{
public function initialize()
{
$this->belongsTo(
"robots_id",
"Store\\Toys\\Robots",
"id",
[
"alias" => "Robot",
]
);
$this->belongsTo(
"similar_robots_id",
"Store\\Toys\\Robots",
"id",
[
"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 могла создавать более точный автозаполнения:
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends 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 может быть изменена, чтобы продемонстрировать эту функцию:
use Phalcon\Mvc\Model;
class RobotsParts extends Model
{
public $id;
public $robots_id;
public $parts_id;
public function initialize()
{
$this->belongsTo(
"robots_id",
"Store\\Toys\\Robots",
"id",
[
"foreignKey" => true
]
);
$this->belongsTo(
"parts_id",
"Parts",
"id",
[
"foreignKey" => [
"message" => "The part_id does not exist on the Parts model"
]
]
);
}
}
Если вы измените взаимосвязь belongsTo(), чтобы она действовала как внешний ключ, она будет проверять, что вставленные/обновлённые значения в этих полях имеют допустимое значение в связанной модели. Аналогично, если взаимосвязь hasMany()/hasOne() изменена, она будет проверять, что записи не могут быть удалены, если эта запись используется в связанной модели.
use Phalcon\Mvc\Model;
class Parts extends Model
{
public function initialize()
{
$this->hasMany(
"id",
"RobotsParts",
"parts_id",
[
"foreignKey" => [
"message" => "The part cannot be deleted because other robots are using it",
]
]
);
}
}
Виртуальный внешний ключ можно настроить, чтобы разрешить нулевые значения следующим образом:
use Phalcon\Mvc\Model;
class RobotsParts extends Model
{
public $id;
public $robots_id;
public $parts_id;
public function initialize()
{
$this->belongsTo(
"parts_id",
"Parts",
"id",
[
"foreignKey" => [
"allowNulls" => true,
"message" => "The part_id does not exist on the Parts model",
]
]
);
}
}
Действия «каскадное удаление»/«ограничение»
Взаимосвязи, которые действуют как виртуальные внешние ключи по умолчанию, ограничивают создание/обновление/удаление записей для поддержания целостности данных:
namespace Store\Toys;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Relation;
class Robots extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany(
"id",
"Parts",
"robots_id",
[
"foreignKey" => [
"action" => Relation::ACTION_CASCADE,
]
]
);
}
}
Вышеприведенный код настроен на удаление всех связанных записей (запчастей), если удаляется главная запись (робот).
Сохранение связанных записей
Магические свойства могут использоваться для сохранения записи и её связанных свойств:
// 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 = [];
// 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() в модели.
Операции над наборами результатов
Если набор результатов состоит из полных объектов, набор результатов способен выполнять операции над полученными записями простым способом:
Обновление связанных записей
Вместо этого:
$parts = $robots->getParts();
foreach ($parts as $part) {
$part->stock = 100;
$part->updated_at = time();
if ($part->update() === false) {
$messages = $part->getMessages();
foreach ($messages as $message) {
echo $message;
}
break;
}
}
можно сделать так:
$robots->getParts()->update(
[
"stock" => 100,
"updated_at" => time(),
]
);
«update» также принимает анонимную функцию для фильтрации записей, которые должны быть обновлены:
$data = [
"stock" => 100,
"updated_at" => time(),
];
// Update all the parts except those whose type is basic
$robots->getParts()->update(
$data,
function ($part) {
if ($part->type === Part::TYPE_BASIC) {
return false;
}
return true;
}
);
Удаление связанных записей
Вместо этого:
$parts = $robots->getParts();
foreach ($parts as $part) {
if ($part->delete() === false) {
$messages = $part->getMessages();
foreach ($messages 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;
}
);
© 2011–2017 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/latest/reference/model-relationships.html