[TOC]
# 模型關系
## 模型之間的關系
有四種類型的關系:一對一,一對多,多對一和多對多。關系可以是單向的或雙向的,并且每個可以是簡單的(一對一模型)或更復雜的(模型的組合)。模型管理器管理這些關系的外鍵約束,這些關系的定義有助于參照完整性以及相關記錄對模型的輕松快速訪問。通過關系的實現,可以很容易地以統一的方式從每個記錄中訪問相關模型中的數據。
### 單向關系
單向關系是彼此相關而不是相反生成的關系。
### 雙向關系
兩個模型和每個模型中的雙向關系構建關系定義了另一個的反向關系。
### 定義關系
在Phalcon中,必須在模型的 `initialize()` 方法中定義關系。方法`belongsTo()`,`hasOne()`,`hasMany()` 和 `hasManyToMany()` 定義從當前模型到另一個模型中的字段的一個或多個字段之間的關系。這些方法中的每一個都需要3個參數:本地字段,引用模型,引用字段。
| 方法 | 描述 |
| ------------- | -------------------------- |
| hasMany | 定義1對n的關系 |
| hasOne | 定義1對1的關系 |
| belongsTo | 定義n對1的關系 |
| hasManyToMany | 定義n對n的關系 |
以下模式顯示了3個表,其關系將作為關系的示例提供給我們:
```sql
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)
);
```
* The model `Robots` has many `RobotsParts`.
* The model `Parts` has many `RobotsParts`.
* The model `RobotsParts` belongs to both `Robots` and `Parts` models as a many-to-one relation.
* The model `Robots` has a relation many-to-many to `Parts` through `RobotsParts`.
利用EER圖以更好地理解關系:

具有關系的模型可以實現如下:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany(
'id',
'RobotsParts',
'robots_id'
);
}
}
```
```php
<?php
use Phalcon\Mvc\Model;
class Parts extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany(
'id',
'RobotsParts',
'parts_id'
);
}
}
```
```php
<?php
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個模型并定義關系中涉及的屬性:
```php
<?php
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'
);
}
}
```
### 利用關系
在明確定義模型之間的關系時,很容易找到特定記錄的相關記錄。
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst(2);
foreach ($robot->robotsParts as $robotPart) {
echo $robotPart->parts->name, "\n";
}
```
Phalcon使用魔術方法`__set`/`__get`/`__call` 來使用關系存儲或檢索相關數據。
通過訪問與關系同名的屬性將檢索其所有相關記錄。
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst();
// All the related records in RobotsParts
$robotsParts = $robot->robotsParts;
```
此外,你可以使用魔術getter:
```php
<?php
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()`結果。以下示例將使用魔術方法和不使用魔術方法檢索相關結果進行比較:
```php
<?php
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;
```
手動獲取相關記錄:
```php
<?php
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()`:
| 類型 | 描述 | 隱含方法 |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------- |
| Belongs-To | 直接返回相關記錄的模型實例 | findFirst |
| Has-One | 直接返回相關記錄的模型實例 | findFirst |
| Has-Many | 返回引用模型的模型實例的集合 | find |
| Has-Many-to-Many | 返回引用模型的模型實例的集合,它隱含地與所涉及的模型進行“內部聯接” | (complex query) |
您還可以使用 `count` 前綴返回一個表示相關記錄計數的整數:
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst(2);
echo 'The robot has ', $robot->countRobotsParts(), " parts\n";
```
### 別名關系
為了更好地解釋別名的工作原理,讓我們檢查以下示例:
`robots_similar` 表具有定義哪些機器人與其他機器人相似的功能:
```sql
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`都與模型機器人有關:

映射此表及其關系的模型如下:
```php
<?php
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'
);
}
}
```
由于兩個關系都指向同一個模型(Robots),因此獲取與關系相關的記錄不能清楚:
```php
<?php
$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?
```
別名允許我們重命名這兩個關系來解決這些問題:
```php
<?php
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',
]
);
}
}
```
通過別名,我們可以輕松獲得相關記錄。您還可以使用`getRelated()`方法使用別名來訪問關系:
```php
<?php
$robotsSimilar = RobotsSimilar::findFirst();
// Returns the related record based on the column (robots_id)
$robot = $robotsSimilar->getRobot();
$robot = $robotsSimilar->robot;
$robot = $robotsSimilar->getRelated('Robot');
// Returns the related record based on the column (similar_robots_id)
$similarRobot = $robotsSimilar->getSimilarRobot();
$similarRobot = $robotsSimilar->similarRobot;
$similarRobot = $robotsSimilar->getRelated('SimilarRobot');
```
#### Magic Getters vs.明確的方法
大多數具有自動完成功能的IDE和編輯器在使用魔法getter(方法和屬性)時都無法推斷出正確的類型。要解決這個問題,您可以使用類docblock來指定可用的魔術操作,從而幫助IDE生成更好的自動完成:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
/**
* Model class for the robots table.
* @property Simple|RobotsParts[] $robotsParts
* @method Simple|RobotsParts[] getRobotsParts($parameters = null)
* @method integer countRobotsParts()
*/
class Robots extends Model
{
public $id;
public $name;
public function initialize()
{
$this->hasMany(
'id',
'RobotsParts',
'robots_id'
);
}
}
```
## 條件語句
您還可以根據條件創建關系。根據關系查詢時,條件將自動附加到查詢中:
```php
<?php
use Phalcon\Mvc\Model;
// Companies have invoices issued to them (paid/unpaid)
// Invoices model
class Invoices extends Model
{
}
// Companies model
class Companies extends Model
{
public function initialize()
{
// All invoices relationship
$this->hasMany(
'id',
'Invoices',
'inv_id',
[
'alias' => 'Invoices'
]
);
// Paid invoices relationship
$this->hasMany(
'id',
'Invoices',
'inv_id',
[
'alias' => 'InvoicesPaid',
'params' => [
'conditions' => "inv_status = 'paid'"
]
]
);
// Unpaid invoices relationship + bound parameters
$this->hasMany(
'id',
'Invoices',
'inv_id',
[
'alias' => 'InvoicesUnpaid',
'params' => [
'conditions' => "inv_status <> :status:",
'bind' => ['status' => 'unpaid']
]
]
);
}
}
```
此外,在從模型對象訪問關系時,可以使用 `getRelated()` 的第二個參數來進一步過濾或排序關系:
```php
<?php
// Unpaid Invoices
$company = Companies::findFirst(
[
'conditions' => 'id = :id:',
'bind' => ['id' => 1],
]
);
$unpaidInvoices = $company->InvoicesUnpaid;
$unpaidInvoices = $company->getInvoicesUnpaid();
$unpaidInvoices = $company->getRelated('InvoicesUnpaid');
$unpaidInvoices = $company->getRelated(
'Invoices',
['conditions' => "inv_status = 'paid'"]
);
// Also ordered
$unpaidInvoices = $company->getRelated(
'Invoices',
[
'conditions' => "inv_status = 'paid'",
'order' => 'inv_created_date ASC',
]
);
```
## 虛擬外鍵
默認情況下,關系不像數據庫外鍵,也就是說,如果您嘗試在引用的模型中插入/更新值而沒有有效值,Phalcon將不會生成驗證消息。您可以通過在定義關系時添加第四個參數來修改此行為。
可以更改RobotsPart模型以演示此功能:
```php
<?php
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()` ,它將驗證如果在引用的模型上使用該記錄,則無法刪除記錄。
```php
<?php
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',
]
]
);
}
}
```
可以設置虛擬外鍵以允許空值,如下所示:
```php
<?php
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',
]
]
);
}
}
```
### 級聯/限制操作
默認情況下充當虛擬外鍵的關系會限制記錄的創建/更新/刪除以維護數據的完整性:
```php
<?php
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,
]
]
);
}
}
```
如果刪除主記錄(robot),上面的代碼設置為刪除所有引用的記錄(部分)。
## 存儲相關記錄
Magic屬性可用于存儲記錄及其相關屬性:
```php
<?php
// 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();
```
將記錄及其相關記錄保存在有多個關系中:
```php
<?php
// 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();
```
保存album和artist同時隱含地使用事務,因此如果保存相關記錄出現任何問題,父級也不會保存。消息將傳遞回用戶以獲取有關任何錯誤的信息。
注意:無法通過重載以下方法添加相關實體:
* `Phalcon\Mvc\Model::beforeSave()`
* `Phalcon\Mvc\Model::beforeCreate()`
* `Phalcon\Mvc\Model::beforeUpdate()`
您需要重載`Phalcon\Mvc\Model::save()`才能在模型中工作。
## 對結果集的操作
如果結果集由完整對象組成,則它可以對記錄執行操作:
### 更新相關記錄
不要這樣做:
```php
<?php
$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;
}
}
```
你可以這樣做:
```php
<?php
$robots->getParts()->update(
[
'stock' => 100,
'updated_at' => time(),
]
);
```
`update` 還接受匿名函數來過濾必須更新的記錄:
```php
<?php
$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;
}
);
```
### 刪除相關記錄
不要這樣做:
```php
<?php
$parts = $robots->getParts();
foreach ($parts as $part) {
if ($part->delete() === false) {
$messages = $part->getMessages();
foreach ($messages as $message) {
echo $message;
}
break;
}
}
```
你可以這樣做:
```php
<?php
$robots->getParts()->delete();
```
`delete()` 還接受匿名函數來過濾必須刪除的記錄:
```php
<?php
// Delete only whose stock is greater or equal than zero
$robots->getParts()->delete(
function ($part) {
if ($part->stock < 0) {
return false;
}
return true;
}
);
```
- 常規
- Welcome
- 貢獻
- 生成回溯
- 測試重現
- 單元測試
- 入門
- 安裝
- Web服務器設置
- WAMP
- XAMPP
- 教程
- 基礎教程
- 教程:創建一個簡單的REST API
- 教程:V?kuró
- 提升性能
- 教程:INVO
- 開發環境
- Phalcon Compose (Docker)
- Nanobox
- Phalcon Box (Vagrant)
- 開發工具
- Phalcon開發者工具的安裝
- Phalcon開發者工具的使用
- 調試應用程序
- 核心
- MVC應用
- 微應用
- 創建命令行(CLI)應用程序
- 依賴注入與服務定位
- MVC架構
- 服務
- 使用緩存提高性能
- 讀取配置
- 上下文轉義
- 類加載器
- 使用命名空間
- 日志
- 隊列
- 數據庫
- 數據庫抽象層
- Phalcon查詢語言(PHQL)
- ODM(對象文檔映射器)
- 使用模型
- 模型行為
- ORM緩存
- 模型事件
- 模型元數據
- 模型關系
- 模型事務
- 驗證模型
- 數據庫遷移
- 分頁
- 前端
- Assets管理
- 閃存消息
- 表單
- 圖像
- 視圖助手(標簽)
- 使用視圖
- Volt:模板引擎
- 業務邏輯
- 訪問控制列表(ACL)
- 注解解析器
- 控制器
- 調度控制器
- 事件管理器
- 過濾與清理
- 路由
- 在session中存儲數據
- 生成URL和路徑
- 驗證
- HTTP
- Cookies管理
- 請求環境
- 返回響應
- 安全
- 加密/解密
- 安全
- 國際化
- 國際化
- 多語言支持