## 二、模型
模型代表了應用程序中的信息(數據)和處理數據的規則。模型主要用于管理與相應數據庫表進行交互的規則。 大多數情況中,在應用程序中,數據庫中每個表將對應一個模型。 應用程序中的大部分業務邏輯都將集中在模型里。
Phalcon\\Mvc\\Model 是 Phalcon 應用程序中所有模型的基類。它保證了數據庫的獨立性,基本的 CURD 操作, 高級的查詢功能,多表關聯等功能。 Phalcon\\Mvc\\Model 不需要直接使用 SQL 語句,因為它的轉換方法,會動態的調用相應的數據庫引擎進行處理。
### 2.1 映射其他表
默認情況下,模型 “Store\\Toys\\RobotParts” 對應的是數據庫表 “robot\_parts”, 如果想映射到其他數據庫表,可以使用 `setSource()` 方法:
```
namespace Store\Toys;
use Phalcon\Mvc\Model;
class RobotParts extends Model {
public function initialize() {
$this->setSource("toys_robot_parts");
}
}
```
### 2.2 Setters/Getters
#### 2.2.1 不使用
模型可以通過公共屬性的方式實現,意味著模型的所有屬性在實例化該模型的地方可以無限制的讀取和更新。
```
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model{
public $id;
public $name;
public $price;
}
```
#### 2.2.2 使用
通過使用 getters/setters 方法,可以控制哪些屬性可以公開訪問,并且對屬性值執行不同的形式的轉換,同時可以保存在模型中的數據添加相應的驗證規則。
```
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){
// 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;
}
}
```
#### 2.2.3 比較
- 公共屬性的方式可以在開發中降低復雜度。
- getters/setters 的實現方式可以顯著的增強應用的可測試性、擴展性和可維護性。
- ORM同時兼容這兩種方法。
#### 2.2.4 注意
1. 如果你在字段名中使用了下劃線,那么你必須使用駝峰命名法去代替下劃線來訪問屬性todo
- `$model->getPropertyName` 代替 `$model->getProperty_name`
- `$model->findByPropertyName` 代替 `$model->findByProperty_name`
2. 在模型名中也是同樣的:例如,數據庫名node\_body對應的模型文件名和類名應該是NodeBody而不是Node\_body
3. 如果你不想遵循這些規則,那么您可以通過列映射來正確讓模型訪問數據庫
### 2.3 理解記錄對象
#### 2.3.1 model中查詢記錄
通過主鍵找到某一條記錄并且打印它的名稱
表結構如下:
```
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)
```
##### 1. 查詢值
```
use Store\Toys\Robots;
// Find record with id = 3
$robot = Robots::findFirst(3);
// Prints "Terminator"
echo $robot->name;
```
##### 2. 修改值
`findFirst()`方法查詢的值會加載到內存中,可以直接修改其中部分或全部值,進行`save()`操作更新
```
use Store\Toys\Robots;
$robot = Robots::findFirst(3);
$robot->name = "RoboCop";
$robot->save();
```
#### 2.3.2 查找記錄
##### 1. find()基本查詢
`find()`方法可以從一個模型中查找一條或多條記錄
```
// 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";
}
```
##### 2. 查詢條件
可用的查詢選項如下:
參數
描述 舉例 conditions 查詢操作的搜索條件。用于提取只有那些滿足指定條件的記錄。默認情況下 [Phalcon\\Mvc\\Model](http://www.iphalcon.cn/api/Phalcon_Mvc_Model.html) 假定第一個參數就是查詢條件。 `"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 the results of the query by a certain amount `"offset"?=>?5` group 從多條記錄中獲取數據并且根據一個或多個字段對結果進行分組。 `"group"?=>?"name,?status"` for\_update 通過這個選項, [Phalcon\\Mvc\\Model](http://www.iphalcon.cn/api/Phalcon_Mvc_Model.html) 讀取最新的可用數據,并且為讀到的每條記錄設置獨占鎖。 `"for_update"?=>?true` shared\_lock 通過這個選項, [Phalcon\\Mvc\\Model](http://www.iphalcon.cn/api/Phalcon_Mvc_Model.html) 讀取最新的可用數據,并且為讀到的每條記錄設置共享鎖。 `"shared_lock"?=>?true` cache 緩存結果集,減少了連續訪問數據庫。 `"cache"?=>?["lifetime"?=>3600,?"key"?=>?"my-find-key"]` hydration Sets the hydration strategy to represent each returned record in the result `"hydration"?=>Resultset::HYDRATE_OBJECTS`1. 可以通過字符串查詢
```
$robot = Robots::findFirst("type = 'mechanical'");
```
2. 通過關聯數組
```
$robot = Robots::findFirst(
[
"type = 'virtual'",
"order" => "name",
]
);
```
3. 通過面向對象
```
$robots = Robots::query()
->where("type = :type:")
->andWhere("year < 2000")
->bind(["type" => "mechanical"])
->order("name")
->execute();
```
靜態方法 query() 返回一個對IDE自動完成友好的 Phalcon\\Mvc\\Model\\Criteria 對象。
4. 通過屬性名稱
Phalcon提供了一個`findFirstBy<property-name>()`方法
這個方法擴展了前面提及的 findFirst() 方法。它允許您利用方法名中的屬性名稱,通過將要搜索的該字段的內容作為參數傳給它,來快速從一個表執行檢索操作
```
$name = "Terminator";
$robot = Robots::findFirstByName($name);
```
5. 總結
- 所有查詢在內部都以 **PHQL** 查詢的方式處理。
- PHQL是一個高層的、面向對象的類SQL語言。
- 通過PHQL語言你可以使用更多的比如join其他模型、定義分組、添加聚集等特性。
##### 3. 綁定查詢特性
- [Phalcon\\Mvc\\Model](http://www.iphalcon.cn/api/Phalcon_Mvc_Model.html) 中支持綁定參數
- 使用綁定參數對性能有一點很小的影響
- 可以減少消除代碼受SQL注入攻擊的可能性
- 綁定參數支持字符串和整數占位符
##### 4. 綁定查詢方式
1. 字符串占位符
```
// Query robots binding parameters with string placeholders
// Parameters whose keys are the same as placeholders
$robots = Robots::find(
[
"name = :name: AND type = :type:",
"bind" => [
"name" => "Robotina",
"type" => "maid",
],
]
);
```
2. 整數占位符
```
// Query robots binding parameters with integer placeholders
$robots = Robots::find(
[
"name = ?1 AND type = ?2",
"bind" => [
1 => "Robotina",
2 => "maid",
],
]
);
```
3. 混合字符串
```
// Query robots binding parameters with both string and integer placeholders
// Parameters whose keys are the same as placeholders
$robots = Robots::find(
[
"name = :name: AND type = ?1",
"bind" => [
"name" => "Robotina",
1 => "maid",
],
]
);
```
4. 注意
- 如果是數字占位符,則必須把它們定義成整型(如1或者2)。若是定義為字符串型(如”1”或者”2”),則這個占位符不會被替換。
- 使用PDO\_的方式會自動轉義字符串。它依賴于字符集編碼,因此建議在連接參數或者數據庫配置中設置正確的字符集編碼。 若是設置錯誤的字符集編碼,在存儲數據或檢索數據時,可能會出現亂碼。
5. 設置參數的“bindTypes”
1. 使用bindTypes允許你根據數據類型來定義參數應該如何綁定
```
use Phalcon\Db\Column;
use Store\Toys\Robots;
// Bind parameters
$parameters = [
"name" => "Robotina",
"year" => 2008,
];
// Casting Types
$types = [
"name" => Column::BIND_PARAM_STR,
"year" => Column::BIND_PARAM_INT,
];
// Query robots binding parameters with string placeholders
$robots = Robots::find(
[
"name = :name: AND year = :year:",
"bind" => $parameters,
"bindTypes" => $types,
]
);
```
> 默認的參數綁定類型是 Phalcon\\Db\\Column::BIND\_PARAM\_STR , 若所有字段都是string類型,則不用特意去設置參數的“bindTypes”.
2. 如果你的綁定參數是array數組,那么數組索引必須從數字0開始
```
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
]
]
);
```
> 參數綁定的方式適用于所有與查詢相關的方法,如 find() , findFirst() 等等, 同時也適用于與計算相關的方法,如 count(), sum(), average() 等等.
6. 隱式的參數綁定
若使用如下方式,phalcon也會自動為你進行參數綁定:
```
use Store\Toys\Robots;
// Explicit query using bound parameters
$robots = Robots::find(
[
"name = ?0",
"bind" => [
"Ultron",
],
]
);
// Implicit query using bound parameters(隱式的參數綁定)
$robots = Robots::findByName("Ultron");
```
#### 2.3.4 模型結果集
- `findFirst()` 方法直接返回一個被調用對象的實例(如果有結果返回的話)
- `find()` 方法返回一個 [Phalcon\\Mvc\\Model\\Resultset\\Simple](http://www.iphalcon.cn/api/Phalcon_Mvc_Model_Resultset_Simple.html) 對象。這個對象也封裝進了所有結果集的功能,比如遍歷、查找特定的記錄、統計等等。
這些對象比一般數組功能更強大。最大的特點是 [Phalcon\\Mvc\\Model\\Resultset](http://www.iphalcon.cn/api/Phalcon_Mvc_Model_Resultset.html) **每時每刻只有一個結果在內存中**。這對操作大數據量時的內存管理相當有幫助。
##### 1. 假設結果集
```
use Store\Toys\Robots;
// Get all robots
$robots = Robots::find();
```
##### 2. 循環結果集
```
// 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();
}
```
##### 3. 結果集總數
```
// Count the resultset
echo count($robots);
// Alternative way to count the resultset
echo $robots->count();
```
##### 4. 結果集指針操作
```
// 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();
```
##### 5. 結果集總結
- Phalcon 的結果集模擬了可滾動的游標,你可以 **通過位置,或者內部指針去訪問任何一條特定的記錄**
- 注意有一些數據庫系統不支持滾動游標(MySQL支持),這就使得查詢會被重復執行, 以便回放光標到最開始的位置,然后獲得相應的記錄。類似地,如果多次遍歷結果集,那么必須執行相同的查詢次數。
- 將大數據量的查詢結果存儲在內存會消耗很多資源,正因為如此,分成每32行一塊從數據庫中獲得結果集,以減少重復執行查詢請求的次數,在一些情況下也節省內存。
- 注意結果集可以序列化后保存在一個后端緩存里面。 [Phalcon\\Cache](http://www.iphalcon.cn/reference/cache.html) 可以用來實現這個。但是,序列化數據會導致 [Phalcon\\Mvc\\Model](http://www.iphalcon.cn/api/Phalcon_Mvc_Model.html) 將從數據庫檢索到的所有數據以一個數組的方式保存,因此在這樣執行的地方 **會消耗更多的內存**。
```
// 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;
}
```
#### 2.3.5 過濾結果集
過濾數據最有效的方法是設置一些查詢條件,數據庫會利用表的索引快速返回數據。Phalcon 額外的允許你通過任何數據庫不支持的方式過濾數據。
```
$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;
}
}
);
```
#### 2.3.6 獲取記錄的初始化以及準備
有時從數據庫中獲取了一條記錄之后, **在被應用程序使用之前,需要對數據進行初始化**。 你可以在模型中實現”`afterFetch`”方法,在模型實例化之后會執行這個方法,并將數據分配給它:
```
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends 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);
}
public function afterSave() {
// Convert the string to an array
$this->status = explode(",", $this->status);
}
}
```
如果使用`getters/setters`方法代替公共屬性的取/賦值,你能在它被調用時,對成員屬性進行初始化:
```
namespace Store\Toys;
use Phalcon\Mvc\Model;
class Robots extends Model {
public $id;
public $name;
public $status;
public function getStatus() {
return explode(",", $this->status);
}
}
```
#### 2.3.7 生成運算
Phalcon提供了一些計算的函數,如`COUNT, SUM, MAX, MIN or AVG`
##### 1. count
```
// 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
],
]
);
```
##### 2. Sum
```
// 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
],
]
);
```
##### 3. Average
```
// 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
],
]
);
```
##### 4. Max/Min
```
// 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'",
]
);
```
#### 2.3.8 創建與更新記錄
```
public boolean save ([array $data], [array $whiteList])
```
保存和更新均使用:`Phalcon\Mvc\Model::save()`
##### 1. 單獨屬性賦值保存
```
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!";
}
```
##### 2. 數組賦值保存
```
$robot->save(
[
"type" => "mechanical",
"name" => "Astro Boy",
"year" => 1952,
]
);
```
##### 3. form傳值保存
```
$robot->save($_POST);
// 如上代碼不安全,沒有經過過濾,什么值都可以寫入到數據庫
$robot->save(
$_POST,
[
"name",
"type",
]
);
```
#### 2.3.9 創建與更新結果判斷
- `save()`方法可以拆分成`create()`或`update()`
> create() will try to INSERT your data, while save() will check if it already exists (by primary key), and will INSERT it if not and UPDATE it if it does. The third relevant method is update(), respectively [參考](https://forum.phalconphp.com/discussion/14859/what-the-difference-between-save-and-create-while-saving-data-in)
#### 2.3.10 刪除記錄
##### 1. 刪除一條記錄
`Phalcon\Mvc\Model::delete()` :刪除一條記錄
```
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!";
}
}
```
##### 2. 刪除多條記錄
使用循環
```
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!";
}
}
```
##### 3. 刪除時可執行方法
Operation Name Can stop operation? Explanation Deleting beforeDelete YES Runs before the delete operation is made Deleting afterDelete NO Runs after the delete operation was made
```
public function beforeDelete() {
if ($this->status === "A") {
echo "The robot is active, it can't be deleted";
return false;
}
return true;
}
```