[TOC]
# 使用模型
模型表示應用程序的信息(數據)和操作該數據的規則。模型主要用于管理與相應數據庫表的交互規則。在大多數情況下,數據庫中的每個表都對應于應用程序中的一個模型。應用程序的大部分業務邏輯將集中在模型中。
`Phalcon\Mvc\Model` 是Phalcon應用程序中所有模型的基礎。它提供數據庫獨立性,基本CRUD功能,高級查找功能以及將模型相互關聯的功能,以及其他服務。 `Phalcon\Mvc\Model` 避免了必須使用SQL語句的需要,因為它將方法動態轉換為相應的數據庫引擎操作。
>[warning] 模型旨在在高層抽象上使用數據庫。如果您需要使用較低級別的數據庫,請查看`Phalcon\Db`組件文檔。
## 創建模型
模型是從 `Phalcon\Mvc\Model`擴展的類。它的類名應該是駝峰表示法:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class RobotParts extends Model
{
}
```
>[warning] 如果您使用的是PHP 5.4 / 5.5,建議您聲明構成模型一部分的每一列,以節省內存并減少內存分配。
默認情況下,模型 `Store\Toys\RobotParts` 將映射到表 `robot_parts`。如果要為映射表手動指定其他名稱,可以使用 `setSource()` 方法:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class RobotParts extends Model
{
public function initialize()
{
$this->setSource('toys_robot_parts');
}
}
```
`RobotParts`模型現在映射到`toys_robot_parts`表。`initialize()`方法有助于使用自定義行為(即不同的表)設置此模型。
`initialize()`方法僅在請求期間調用一次。此方法旨在執行適用于在應用程序中創建的模型的所有實例的初始化。如果要為創建的每個實例執行初始化任務,可以使用`onConstruct()`方法:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class RobotParts extends Model
{
public function onConstruct()
{
// ...
}
}
```
### 公共屬性與Setters / Getters
模型可以實現公共屬性,這意味著可以從已實例化該模型類的代碼的任何部分讀取/更新每個屬性:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public $price;
}
```
另一個實現是使用getter和setter函數,它們控制哪些屬性可以公開用于該模型。使用getter和setter的好處是開發人員可以對模型設置的值執行轉換和驗證檢查,這在使用公共屬性時是不可能的。此外,getter和setter允許將來更改,而無需更改模型類的接口。因此,如果字段名稱發生更改,則所需的唯一更改將在相關getter / setter中引用的模型的private屬性中,并且代碼中沒有其他位置。
```php
<?php
namespace Store\Toys;
use InvalidArgumentException;
use Phalcon\Mvc\Model;
class Robots extends 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)
{
// 不允許負價
if ($price < 0) {
throw new InvalidArgumentException(
"Price can't be negative"
);
}
$this->price = $price;
}
public function getPrice()
{
// 在使用之前將值轉換為double
return (double) $this->price;
}
}
```
公共屬性提供較少的開發復雜性。但是,getter / setter可以大大提高應用程序的可測試性,可擴展性和可維護性。開發人員可以根據應用程序的需要決定哪種策略更適合他們正在創建的應用程序。ORM與定義屬性的兩種方案兼容。
>[warning] 使用getter和setter時,屬性名稱中的下劃線可能會出現問題。
如果在屬性名稱中使用下劃線,則必須在getter / setter聲明中使用camel case以與magic方法一起使用。(例如`$model->getPropertyName`而不是`$model->getProperty_name`,`$model->findByPropertyName`而不是`$model->findByProperty_name`等)。由于系統需要駝峰大多數情況,并且通常會刪除下劃線,因此建議您按照整個文檔中顯示的方式命名屬性。您可以使用列映射(如上所述)來確保將屬性正確映射到數據庫對應項。
## 了解對象的記錄
模型的每個實例代表表中的一行。您可以通過讀取對象屬性輕松訪問記錄數據。例如,對于包含記錄的表'robots':
```sql
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)
```
您可以通過其主鍵找到某個記錄,然后打印其名稱:
```php
<?php
use Store\Toys\Robots;
// Find record with id = 3
$robot = Robots::findFirst(3);
// Prints 'Terminator'
echo $robot->name;
```
記錄在內存中后,您可以對其數據進行修改,然后保存更改:
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst(3);
$robot->name = 'RoboCop';
$robot->save();
```
如您所見,不需要使用原始SQL語句。`Phalcon\Mvc\Model` 為Web應用程序提供高度數據庫抽象。
## 查找記錄
`Phalcon\Mvc\Model` 還提供了幾種查詢記錄的方法。以下示例將向您展示如何從模型中查詢一個或多個記錄:
```php
<?php
use Store\Toys\Robots;
// 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(
[
"type = 'virtual'",
'order' => 'name',
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// Get first 100 virtual robots ordered by name
$robots = Robots::find(
[
"type = 'virtual'",
'order' => 'name',
'limit' => 100,
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
```
>[warning] 如果要通過外部數據(例如用戶輸入)或可變數據查找記錄,則必須使用“綁定參數”
您還可以使用`findFirst()`方法僅獲取與給定條件匹配的第一條記錄:
```php
<?php
use Store\Toys\Robots;
// 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(
[
"type = 'virtual'",
'order' => 'name',
]
);
echo 'The first virtual robot name is ', $robot->name, "\n";
```
`find()`和`findFirst()`方法都接受指定搜索條件的關聯數組:
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst(
[
"type = 'virtual'",
'order' => 'name DESC',
'limit' => 30,
]
);
$robots = Robots::find(
[
'conditions' => 'type = ?1',
'bind' => [
1 => 'virtual',
]
]
);
```
可用的查詢選項包括:
| 參數 | 描述 | 示例 |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `conditions` | 搜索查找操作的條件。用于僅提取滿足指定標準的記錄。默認情況下,`Phalcon\Mvc\Model`假定第一個參數是條件。 | `'conditions' => "name LIKE 'steve%'"` |
| `columns` | 返回特定列而不是模型中的完整列。使用此選項時,將返回不完整的對象。 | `'columns' => 'id, name'` |
| `bind` | 綁定與選項一起使用,通過替換占位符和轉義值,從而提高安全性。 | `'bind' => ['status' => 'A', 'type' => 'some-time']` |
| `bindTypes` | 綁定參數時,可以使用此參數為綁定參數定義其他強制轉換,從而提高安全性。 | `'bindTypes' => [Column::BIND_PARAM_STR, Column::BIND_PARAM_INT]` |
| `order` | 用于對結果集進行排序。使用逗號分隔的一個或多個字段。 | `'order' => 'name DESC, status'` |
| `limit` | 將查詢結果限制為某個范圍。 | `'limit' => 10` |
| `offset` | 將查詢結果偏移一定量。 | `'offset' => 5` |
| `group` | 允許跨多個記錄收集數據,并按一列或多列對結果進行分組。 | `'group' => 'name, status'` |
| `for_update` | 使用此選項,`Phalcon\Mvc\Model` 將讀取最新的可用數據,并在其讀取的每一行上設置獨占鎖。 | `'for_update' => true` |
| `shared_lock` | 使用此選項, `Phalcon\Mvc\Model` 將讀取最新的可用數據,并在其讀取的每一行上設置共享鎖。 | `'shared_lock' => true` |
| `cache` | 緩存結果集,減少對關系系統的連續訪問。 | `'cache' => ['lifetime' => 3600, 'key' => 'my-find-key']` |
| `hydration` | 設置hydration策略以表示結果中的每個返回記錄。 | `'hydration' => Resultset::HYDRATE_OBJECTS` |
如果您愿意,還可以使用一種以面向對象的方式創建查詢的方法,而不是使用參數數組:
```php
<?php
use Store\Toys\Robots;
$robots = Robots::query()
->where('type = :type:')
->andWhere('year < 2000')
->bind(['type' => 'mechanical'])
->order('name')
->execute();
```
靜態方法`query()`返回與IDE自動完成程序友好的`Phalcon\Mvc\Model\Criteria` 對象。
所有查詢都在內部處理為PHQL查詢。PHQL是一種高級,面向對象和類似SQL的語言。此語言為您提供了更多功能來執行查詢,如加入其他模型,定義分組,添加聚合等。
最后,有 `findFirstBy<property-name>()`方法。此方法擴展了前面提到的`findFirst()`方法。它允許您通過使用方法本身中的屬性名稱并向其傳遞包含要在該列中搜索的數據的參數,從表中快速執行檢索。一個例子是有序的,所以采用前面提到的機器人模型:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public $price;
}
```
我們在這里有三個屬性:`$id`,`$name`和`$price` 。所以,假設您要檢索表中名為“Terminator”的第一條記錄。這可以寫成
```php
<?php
use Store\Toys\Robots;
$name = 'Terminator';
$robot = Robots::findFirstByName($name);
if ($robot) {
echo 'The first robot with the name ' . $name . ' cost ' . $robot->price . '.';
} else {
echo '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`的最大特色之一是,在任何時候內存中只有一條記錄。這極大地有助于內存管理,尤其是在處理大量數據時。
```php
<?php
use Store\Toys\Robots;
// 獲取所有 robots
$robots = Robots::find();
// foreach遍歷
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// while遍歷
$robots->rewind();
while ($robots->valid()) {
$robot = $robots->current();
echo $robot->name, "\n";
$robots->next();
}
// 計算結果集
echo count($robots);
// 計算結果集的替代方法
echo $robots->count();
// 將內部光標移動到第三個 robot
$robots->seek(2);
$robot = $robots->current();
//通過結果集中的位置訪問 robot
$robot = $robots[5];
// 檢查某個位置是否有記錄
if (isset($robots[3])) {
$robot = $robots[3];
}
// 獲取結果集中的第一條記錄
$robot = $robots->getFirst();
// 獲取最后一條記錄
$robot = $robots->getLast();
```
Phalcon的結果集模擬可滾動游標,只需訪問其位置或尋找指向特定位置的內部指針即可獲得任何行。請注意,某些數據庫系統不支持可滾動游標,這會強制重新執行查詢,以便將游標倒回到開頭并在請求的位置獲取記錄。同樣,如果遍歷結果集多次,則查詢必須執行相同的次數。
由于在內存中存儲大型查詢結果可能會消耗許多資源,因此從數據庫中以32行為塊獲取結果集 - 在幾種情況下減少了重新執行請求的需要。
請注意,結果集可以序列化并存儲在緩存后端中。`Phalcon\Cache`可以幫助完成該任務。但是,序列化數據會導致`Phalcon\Mvc\Model`從數組中檢索數據庫中的所有數據,從而在此過程中消耗更多內存。
```php
<?php
// 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;
}
```
### 自定義結果集
有時,應用程序邏輯需要在從數據庫中檢索數據時對數據進行額外的操作。以前,我們只是擴展模型并將功能封裝在模型或特征中的類中,通常返回給調用者一組轉換后的數據。
使用自定義結果集,您不再需要這樣做。 自定義結果集將封裝模型中的功能,并且可以由其他模型重用,從而保持代碼[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)。 這樣,`find()`方法將不再返回默認的 `Phalcon\Mvc\Model\Resultset`,而是返回自定義的結果集。Phalcon 允許您通過在模型中使用 `getResultsetClass()` 來完成此操作。
首先,我們需要定義結果集類:
```php
<?php
namespace Application\Mvc\Model\Resultset;
use \Phalcon\Mvc\Model\Resultset\Simple;
class Custom extends Simple
{
public function getSomeData() {
/** CODE */
}
}
```
在模型中,我們在`getResultsetClass()`中設置類,如下所示:
```php
<?php
namespace Phalcon\Test\Models\Statistics;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function getSource()
{
return 'robots';
}
public function getResultsetClass()
{
return 'Application\Mvc\Model\Resultset\Custom';
}
}
```
最后在你的代碼中你會有這樣的東西:
```php
<?php
/**
* Find the robots
*/
$robots = Robots::find(
[
'conditions' => 'date between "2017-01-01" AND "2017-12-31"',
'order' => 'date'
]
);
/**
* 將數據傳遞給視圖
*/
$this->view->mydata = $robots->getSomeData();
```
### 過濾結果集
過濾結果集過濾數據的最有效方法是設置一些搜索條件,數據庫將使用在表上設置的索引來更快地返回數據。Phalcon還允許您使用PHP將數據庫中的不可用方案來過濾數據:
```php
<?php
$customers = Customers::find();
$customers = $customers->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注入攻擊的可能性。支持字符串和整數占位符。綁定參數可以簡單地實現如下:
```php
<?php
use Store\Toys\Robots;
// 查詢robots使用字符串占位符綁定參數
// 參數key與占位符相同的參數
$robots = Robots::find(
[
'name = :name: AND type = :type:',
'bind' => [
'name' => 'Robotina',
'type' => 'maid',
],
]
);
// 查詢robots使用整數占位符綁定參數
$robots = Robots::find(
[
'name = ?1 AND type = ?2',
'bind' => [
1 => 'Robotina',
2 => 'maid',
],
]
);
// 查詢robots使用字符串和整數占位符綁定參數
// 參數key與占位符相同的參數
$robots = Robots::find(
[
'name = :name: AND type = ?1',
'bind' => [
'name' => 'Robotina',
1 => 'maid',
],
]
);
```
使用數字占位符時,您需要將它們定義為整數,即`1`或`2`.在這種情況下,`'1'`或`'2'`被視為字符串而不是數字,因此無法成功替換占位符。
使用[PDO](http://php.net/manual/en/pdo.prepared-statements.php)自動轉義字符串。此函數考慮了連接字符集,因此建議在連接參數或數據庫配置中定義正確的字符集,因為錯誤的字符集在存儲或檢索數據時會產生不良影響。
此外,您可以設置參數 `bindTypes`,這允許根據數據類型定義參數的綁定方式:
```php
<?php
use Phalcon\Db\Column;
use Store\Toys\Robots;
// 綁定參數
$parameters = [
'name' => 'Robotina',
'year' => 2008,
];
// Casting Types
$types = [
'name' => Column::BIND_PARAM_STR,
'year' => Column::BIND_PARAM_INT,
];
// 查詢robots使用字符串占位符綁定參數
$robots = Robots::find(
[
'name = :name: AND year = :year:',
'bind' => $parameters,
'bindTypes' => $types,
]
);
```
>[warning] 由于默認的綁定類型是 `Phalcon\Db\Column::BIND_PARAM_STR`,因此如果所有列都屬于該類型,則無需指定bindTypes參數。
如果在綁定參數中綁定數組,請記住,鍵必須從`0`開始編號:
```php
<?php
use Store\Toys\Robots;
$array = ['a','b','c']; // $array: [[0] => 'a', [1] => 'b', [2] => 'c']
unset($array[1]); // $array: [[0] => 'a', [2] => 'c']
// Now we have to renumber the keys
$array = array_values($array); // $array: [[0] => 'a', [1] => 'c']
$robots = Robots::find(
[
'letter IN ({letter:array})',
'bind' => [
'letter' => $array
]
]
);
```
>[warning] 綁定參數可用于所有查詢方法,例如`find()`和`findFirst()`,但也可用于`count()`,`sum()`,`average()`等計算方法。
如果你正在使用“finders”,例如`find()`,`findFirst()`等,自動使用綁定參數:
```php
<?php
use Store\Toys\Robots;
// 使用綁定參數的顯式查詢
$robots = Robots::find(
[
'name = ?0',
'bind' => [
'Ultron',
],
]
);
// 使用綁定參數的隱式查詢
$robots = Robots::findByName('Ultron');
```
## 初始化/準備獲取記錄
可能是這樣的情況:在從數據庫獲取記錄之后,必須在應用程序的其余部分使用之前初始化數據。您可以在模型中實現 `afterFetch()` 方法,此事件將在創建實例后立即執行并將數據分配給它:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public $status;
public function beforeSave()
{
// 將數組轉換為字符串
$this->status = join(',', $this->status);
}
public function afterFetch()
{
// 將字符串轉換為數組
$this->status = explode(',', $this->status);
}
public function afterSave()
{
// 將字符串轉換為數組
$this->status = explode(',', $this->status);
}
}
```
如果您使用getter / setter而不是/或與公共屬性一起使用,則可以在訪問字段后初始化該字段:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public $id;
public $name;
public $status;
public function getStatus()
{
return explode(',', $this->status);
}
}
```
## 生成計算
計算(或聚合)是數據庫系統常用功能的輔助工具,例如`COUNT`,`SUM`,`MAX`,`MIN`或`AVG`。`Phalcon\Mvc\Model`允許直接從公開的方法使用這些函數。
Count 示例:
```php
<?php
// How many employees are?
$rowcount = Employees::count();
// How many different areas are assigned to employees?
$rowcount = Employees::count(
[
'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(
[
'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(
[
'group' => 'area',
'order' => 'rowcount',
]
);
// Avoid SQL injections using bound parameters
$group = Employees::count(
[
'type > ?0',
'bind' => [
$type
],
]
);
```
Sum 示例:
```php
<?php
// How much are the salaries of all employees?
$total = Employees::sum(
[
'column' => 'salary',
]
);
// How much are the salaries of all employees in the Sales area?
$total = Employees::sum(
[
'column' => 'salary',
'conditions' => "area = 'Sales'",
]
);
// Generate a grouping of the salaries of each area
$group = Employees::sum(
[
'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(
[
'column' => 'salary',
'group' => 'area',
'order' => 'sumatory DESC',
]
);
// Avoid SQL injections using bound parameters
$group = Employees::sum(
[
'conditions' => 'area > ?0',
'bind' => [
$area
],
]
);
```
Average 示例:
```php
<?php
// What is the average salary for all employees?
$average = Employees::average(
[
'column' => 'salary',
]
);
// What is the average salary for the Sales's area employees?
$average = Employees::average(
[
'column' => 'salary',
'conditions' => "area = 'Sales'",
]
);
// Avoid SQL injections using bound parameters
$average = Employees::average(
[
'column' => 'age',
'conditions' => 'area > ?0',
'bind' => [
$area
],
]
);
```
Max/Min 示例:
```php
<?php
// What is the oldest age of all employees?
$age = Employees::maximum(
[
'column' => 'age',
]
);
// What is the oldest of employees from the Sales area?
$age = Employees::maximum(
[
'column' => 'age',
'conditions' => "area = 'Sales'",
]
);
// What is the lowest salary of all employees?
$salary = Employees::minimum(
[
'column' => 'salary',
]
);
```
## 創建/更新記錄
`Phalcon\Mvc\Model::save()`方法允許您根據它們是否已存在于與模型關聯的表中來創建/更新記錄。`save()`方法由`Phalcon\Mvc\Model`的`create`和`update`方法在內部調用。為了使其按預期工作,必須在實體中正確定義主鍵,以確定是否應更新或創建記錄。
該方法還執行關聯的驗證器,虛擬外鍵和模型中定義的事件:
```php
<?php
use Store\Toys\Robots;
$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";
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'Great, a new robot was saved successfully!';
}
```
可以傳遞一個數組進行保存以避免手動分配每個列。`Phalcon\Mvc\Model`將檢查是否為數組中傳遞的列實現了setter優先級,而不是直接分配屬性的值:
```php
<?php
use Store\Toys\Robots;
$robot = new Robots();
$robot->save(
[
'type' => 'mechanical',
'name' => 'Astro Boy',
'year' => 1952,
]
);
```
直接或通過屬性數組分配的值將根據相關屬性數據類型進行轉義 / 清理。因此,您可以傳遞一個不安全的數組,而不必擔心可能的SQL注入:
```php
<?php
use Store\Toys\Robots;
$robot = new Robots();
$robot->save($_POST);
```
>[warning] 如果沒有預防措施,批量分配可能允許攻擊者設置任何數據庫列的值。如果要允許用戶插入/更新模型中的每個列,請僅使用此功能,即使這些字段不在提交的表單中。
您可以在`save`設置其他參數,以設置僅在執行批量分配時必須考慮的字段白名單:
```php
<?php
use Store\Toys\Robots;
$robot = new Robots();
$robot->save(
$_POST,
[
'name',
'type',
]
);
```
### 強制創建/更新記錄
當一個應用程序有并發,我們可能期望`create`一個記錄,但它實際上是`update`的。如果我們使用`Phalcon\Mvc\Model::save()` 來保存數據庫中的記錄,就會發生這種情況。如果我們想要絕對確定創建或更新記錄,我們可以使用 `create()` 或`update()`更改`save()`調用:
```php
<?php
use Store\Toys\Robots;
$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";
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'Great, a new robot was created successfully!';
}
```
方法`create`和`update`還接受一個值數組作為參數。
## 刪除記錄
`Phalcon\Mvc\Model::delete()` 方法允許刪除記錄。您可以按如下方式使用它:
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst(11);
if ($robot !== false) {
if ($robot->delete() === false) {
echo "Sorry, we can't delete the robot right now: \n";
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'The robot was deleted successfully!';
}
}
```
您還可以通過使用`foreach`遍歷結果集來刪除許多記錄:
```php
<?php
use Store\Toys\Robots;
$robots = Robots::find(
"type = 'mechanical'"
);
foreach ($robots as $robot) {
if ($robot->delete() === false) {
echo "Sorry, we can't delete the robot right now: \n";
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo $message, "\n";
}
} else {
echo 'The robot was deleted successfully!';
}
}
```
以下事件可用于定義在執行刪除操作時可以執行的自定義業務規則:
| 操作 | 名稱 | Can stop operation? | 說明 |
| --------- | ------------ |:-------------------:| ---------------------------------------- |
| Deleting | afterDelete | No | 刪除操作完成后運行 |
| Deleting | beforeDelete | Yes | 刪除操作完成前運行 |
通過上述事件,還可以在模型中定義業務規則:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function beforeDelete()
{
if ($this->status === 'A') {
echo "The robot is active, it can't be deleted";
return false;
}
return true;
}
}
```
## Hydration Modes
如前所述,結果集是完整對象的集合,這意味著每個返回的結果都是表示數據庫中行的對象。可以修改這些對象并將其再次保存為持久性:
```php
<?php
use Store\Toys\Robots;
$robots = Robots::find();
// Manipulating a resultset of complete objects
foreach ($robots as $robot) {
$robot->year = 2000;
$robot->save();
}
```
有時記錄只能以只讀模式呈現給用戶,在這些情況下,改變表示記錄的方式以便于處理它們可能是有用的。用于表示結果集中返回的對象的策略稱為“hydration mode”:
```php
<?php
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;
}
```
Hydration mode 也可以作為`find`的參數傳遞:
```php
<?php
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\Manager`和方法`setModelPrefix()`:
```php
<?php
use Phalcon\Mvc\Model\Manager;
use Phalcon\Mvc\Model;
class Robots extends Model
{
}
$manager = new Manager();
$manager->setModelPrefix('wp_');
$robots = new Robots(null, null, $manager);
echo $robots->getSource(); // will return wp_robots
```
## 自動生成的標識列
某些模型可能具有標識列。這些列通常是映射表的主鍵。`Phalcon\Mvc\Model` 可以識別在生成的SQL `INSERT`中省略它的標識列,因此數據庫系統可以為它生成自動生成的值。始終在創建記錄后,身份字段將注冊為數據庫系統中為其生成的值:
```php
<?php
$robot->save();
echo 'The generated id is: ', $robot->id;
```
`Phalcon\Mvc\Model` 能夠識別標識列。根據數據庫系統的不同,這些列可能是PostgreSQL中的串行列,也可能是MySQL中的auto_increment列。
PostgreSQL使用序列生成自動數值,默認情況下,Phalcon嘗試從序列`table_field_seq`獲取生成的值,例如: `robots_id_seq`,如果該序列具有不同的名稱,則需要實現 `getSequenceName()`方法:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function getSequenceName()
{
return 'robots_sequence_name';
}
}
```
## 跳過列
告訴`Phalcon\Mvc\Model`總是省略創建和/或更新記錄中的某些字段,以便委托數據庫系統通過觸發器或默認值來分配值:
```php
<?php
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表示替換。強制默認值可以按如下方式完成:
```php
<?php
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();
```
回調也可用于創建自動默認值的條件分配:
```php
<?php
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');
}
}
}
```
>[danger] 切勿使用`Phalcon\Db\RawValue`分配外部數據(例如用戶輸入)或可變數據。將參數綁定到查詢時,將忽略這些字段的值。所以它可以用來攻擊注入SQL的應用程序。
## 動態更新
默認情況下,SQL `UPDATE`語句是使用模型中定義的每個列創建的(完整的全字段SQL更新)。您可以更改特定模型以進行動態更新,在這種情況下,只有已更改的字段用于創建最終的SQL語句。
在某些情況下,這可以通過減少應用程序和數據庫服務器之間的流量來提高性能,這在表有blob / text字段時特別有用:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->useDynamicUpdate(true);
}
}
```
## 獨立列映射
ORM支持獨立的列映射,允許開發人員在模型中使用表中的列名。Phalcon將識別新的列名稱,并相應地重命名它們以匹配數據庫中的相應列。當需要重命名數據庫中的字段而不必擔心代碼中的所有查詢時,這是一個很棒的功能。模型中列映射的更改將處理其余部分。例如:
```php
<?php
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',
];
}
}
```
然后您可以在代碼中自然地使用新名稱:
```php
<?php
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發生異常
獨立的列映射允許您:
* 使用自己的約定編寫應用程序
* 消除代碼中的vendor前綴/后綴
* 更改列名而不更改應用程序代碼
## 記錄快照
可以設置特定模型以在查詢時維護記錄快照。您可以使用此功能來實現審核,或者只是根據持久性中查詢的數據了解哪些字段已更改:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->keepSnapshots(true);
}
}
```
激活此功能時,應用程序會消耗更多內存,以跟蹤從持久性中獲取的原始值。在激活此功能的模型中,您可以檢查更改的字段如下:
```php
<?php
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
```
在模型創建/更新時更新快照。使用`hasUpdated()`和`getUpdatedFields()`可以檢查字段是否在創建/保存/更新后更新,但如果在`afterUpdate()`,`afterSave()` 或 `afterCreate()`中執行 `getChangedFields()`,則可能會對應用程序造成問題()。
您可以使用以下命令禁用此功能:
```php
Phalcon\Mvc\Model::setup(
[
'updateSnapshotOnSave' => false,
]
);
```
或者如果你喜歡在你的`php.ini`中設置它
```ini
phalcon.orm.update_snapshot_on_save = 0
```
使用此功能將產生以下影響:
```php
<?php
use Phalcon\Mvc\Model;
class User extends Model
{
public function initialize()
{
$this->keepSnapshots(true);
}
}
$user = new User();
$user->name = 'Test User';
$user->create();
var_dump($user->getChangedFields());
$user->login = 'testuser';
var_dump($user->getChangedFields());
$user->update();
var_dump($user->getChangedFields());
```
在Phalcon 3.1.0及更高版本上,它是:
```php
array(0) {
}
array(1) {
[0]=>
string(5) "login"
}
array(0) {
}
```
`getUpdatedFields()` 將正確返回更新的字段,或者如上所述,您可以通過設置相關的ini值返回到先前的行為。
## 指向不同的架構
如果模型映射到與默認模式/數據庫不同的模式/數據庫中的表。您可以使用`setSchema()`方法來定義:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setSchema('toys');
}
}
```
## 設置多個數據庫
在Phalcon中,所有模型都可以屬于同一個數據庫連接或具有單獨的數據庫連接。實際上,當`Phalcon\Mvc\Model`需要連接到數據庫時,它會在應用程序的服務容器中請求`db` 服務。您可以在`initialize()` 方法中覆蓋此服務設置:
```php
<?php
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()` 方法中,我們定義模型的連接服務:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setConnectionService('dbPostgres');
}
}
```
但Phalcon為您提供了更大的靈活性,您可以定義必須用于`read`和`write` 的連接。這對于平衡實現主從架構的數據庫的負載特別有用:
```php
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model
{
public function initialize()
{
$this->setReadConnectionService('dbSlave');
$this->setWriteConnectionService('dbMaster');
}
}
```
ORM還提供水平分片功能,允許您根據當前查詢條件實現“shard”選擇:
```php
<?php
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()` 方法來選擇正確的連接,此方法攔截執行的任何新查詢:
```php
<?php
use Store\Toys\Robots;
$robot = Robots::findFirst('id = 101');
```
## 將服務注入模型
您可能需要訪問模型中的應用程序服務,以下示例說明如何執行此操作:
```php
<?php
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);
}
}
}
```
每次`create`或`update`操作失敗時都會觸發`notSaved`事件。因此,我們正在刷新驗證消息,從DI容器中獲取`flash`服務。通過這樣做,我們不必在每次保存后打印消息。
## 禁用/啟用功能
在ORM中,我們實現了一種機制,允許您動態地全局啟用/禁用特定功能或選項。根據您使用ORM的方式,您可以禁用您不使用的ORM。如果需要,也可以暫時禁用這些選項:
```php
<?php
use Phalcon\Mvc\Model;
Model::setup(
[
'events' => false,
'columnRenaming' => false,
]
);
```
可用選項包括:
| 選項 | 描述 | 默認 |
| --------------------- | ----------------------------------------------------------------------------------------- |:-------:|
| astCache | 啟用/禁用所有模型的回調,鉤子和事件通知 | `null` |
| cacheLevel | 設置ORM的緩存級別 | `3` |
| castOnHydrate | | `false` |
| columnRenaming | 啟用/禁用列重命名renaming | `true` |
| disableAssignSetters | 允許在模型中禁用setter | `false` |
| enableImplicitJoins | | `true` |
| enableLiterals | | `true` |
| escapeIdentifiers | | `true` |
| events | 啟用/禁用所有模型的回調,鉤子和事件通知 | `true` |
| exceptionOnFailedSave | 在 `save()` 失敗時啟用/禁用拋出異常 | `false` |
| forceCasting | | `false` |
| ignoreUnknownColumns | 啟用/禁用忽略模型上的未知列 | `false` |
| lateStateBinding | 啟用/禁用 `Phalcon\Mvc\Model::cloneResultMap()` 方法的后期狀態綁定 | `false` |
| notNullValidations | ORM自動驗證映射表中存在的非空列 | `true` |
| parserCache | | `null` |
| phqlLiterals | 在PHQL解析器中啟用/禁用字面量 | `true` |
| uniqueCacheId | | `3` |
| updateSnapshotOnSave | 在`save()`上啟用/禁用更新快照 | `true` |
| virtualForeignKeys | 啟用/禁用虛擬外鍵 | `true` |
>[warning] **NOTE** `Phalcon\Mvc\Model::assign()` (在創建/更新/保存模型時也使用它)總是使用setter,如果它們在傳遞數據參數時存在,即使它是必需的或必要的。這將為您的應用程序增加一些額外的開銷。您可以通過將 `phalcon.orm.disable_assign_setters = 1` 添加到您的ini文件來更改此行為,它只需使用 `$this->property = value`
。
## 獨立組件
在獨立模式下使用 `Phalcon\Mvc\Model` 可以在下面演示:
```php
<?php
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();
```
- 常規
- 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管理
- 請求環境
- 返回響應
- 安全
- 加密/解密
- 安全
- 國際化
- 國際化
- 多語言支持