# 對象文檔映射 ODM (Object-Document Mapper)
除了可以[映射關系數據庫的表](http://docs.iphalcon.cn/reference/models.html)之外,Phalcon還可以使用NoSQL數據庫如MongoDB等。Phalcon中的ODM具有可以非常容易的實現如下功能:CRUD,事件,驗證等。
因為NoSQL數據庫中無sql查詢及計劃等操作故可以提高數據操作的性能。再者,由于無SQL語句創建的操作故可以減少SQL注入的危險。
當前Phalcon中支持的NosSQL數據庫如下:
| 名稱 | 描述 |
| --- | --- |
| [MongoDB](http://www.mongodb.org/) | MongoDB是一個穩定的高性能的開源的NoSQL數據 |
## 創建模型(Creating Models)
NoSQL中的模型類擴展自[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html).模型必須要放入模型文件夾中而且每個模型文件必須只能有一個模型類; 模型類名應該為小駝峰法書寫:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
}
~~~
> 如果PHP版本為5.4/5.5或更高版本,為了提高性能節省內存開銷,最好在模型類文件中定義每個字段。
>
> 模型Robots默認和數據庫中的robots表格映射。如果想使用別的名字映射數據庫中的表格則只需要重寫`setSource()`方法即可:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function initialize()
{
$this->setSource("the_robots");
}
}
~~~
## 理解文檔對象(Understanding Documents To Objects)
每個模型的實例和數據庫表中的一個文檔(記錄)相對應。我們可以非常容易的通過讀取對象屬性來訪問表格的數據。例如訪問robots表格:
~~~
$ mongo test
MongoDB shell version: 1.8.2
connecting to: test
> db.robots.find()
{ "_id" : ObjectId("508735512d42b8c3d15ec4e1"), "name" : "Astro Boy", "year" : 1952,
"type" : "mechanical" }
{ "_id" : ObjectId("5087358f2d42b8c3d15ec4e2"), "name" : "Bender", "year" : 1999,
"type" : "mechanical" }
{ "_id" : ObjectId("508735d32d42b8c3d15ec4e3"), "name" : "Wall-E", "year" : 2008 }
>
~~~
## 模型中使用命名空間(Models in Namespaces)
我們在這里可以使用命名空間來避免類名沖突。這個例子中我們使用:code:[`](http://docs.iphalcon.cn/reference/odm.html#id1)setSource()`方法來標明要使用的數據庫表:
~~~
<?php
namespace Store\Toys;
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function initialize()
{
$this->setSource("robots");
}
}
~~~
我們可以通過對象的ID查找到對象然后打印出其名字:
~~~
<?php
// Find record with _id = "5087358f2d42b8c3d15ec4e2"
$robot = Robots::findById("5087358f2d42b8c3d15ec4e2");
// Prints "Bender"
echo $robot->name;
~~~
一旦記錄被加載到內存中,我們就可以對這些數據進行修改了,修改之后還可以保存:
~~~
<?php
$robot = Robots::findFirst(
[
[
"name" => "Astro Boy",
]
]
);
$robot->name = "Voltron";
$robot->save();
~~~
## 設置連接(Setting a Connection)
這里的MongoDB服務是從服務容器中取得的。默認,Phalcon會使mongo作服務名:
~~~
<?php
// Simple database connection to localhost
$di->set(
"mongo",
function () {
$mongo = new MongoClient();
return $mongo->selectDB("store");
},
true
);
// Connecting to a domain socket, falling back to localhost connection
$di->set(
"mongo",
function () {
$mongo = new MongoClient(
"mongodb:///tmp/mongodb-27017.sock,localhost:27017"
);
return $mongo->selectDB("store");
},
true
);
~~~
## 查找文檔(Finding Documents)
[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)依賴于Mongo的PHP擴展,這樣我們就可以直接從數據庫中查詢出文檔記錄然后Phalcon會 透明的(我們無需關心過程和方法)為我們轉換為模型的實例。[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)
~~~
<?php
// 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 mechanical robots ordered by name upward
$robots = Robots::find(
[
[
"type" => "mechanical",
],
"sort" => [
"name" => 1,
],
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// Get first 100 mechanical robots ordered by name
$robots = Robots::find(
[
[
"type" => "mechanical",
],
"sort" => [
"name" => 1,
],
"limit" => 100,
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
~~~
這里我們可以使用`findFirst()`來取得配置查詢的第一條記錄:
~~~
<?php
// What's the first robot in robots collection?
$robot = Robots::findFirst();
echo "The robot name is ", $robot->name, "\n";
// What's the first mechanical robot in robots collection?
$robot = Robots::findFirst(
[
[
"type" => "mechanical",
]
]
);
echo "The first mechanical robot name is ", $robot->name, "\n";
~~~
`find()`和`findFirst()`方法都接收一個關聯數據組為查詢的條件:
~~~
<?php
// First robot where type = "mechanical" and year = "1999"
$robot = Robots::findFirst(
[
"conditions" => [
"type" => "mechanical",
"year" => "1999",
],
]
);
// All virtual robots ordered by name downward
$robots = Robots::find(
[
"conditions" => [
"type" => "virtual",
],
"sort" => [
"name" => -1,
],
]
);
~~~
可用的查詢選項:
| 參數 | 描述 | 例子 |
| --- | --- | --- |
| `conditions`(條件) | 搜索條件,用于取只滿足要求的數,默認情況下Phalcon\_model會假定關聯數據的第一個參數為查詢條 | `"conditions"=>array('$gt'=>1990)` |
| `fields`(字段) | 若指定則返回指定的字段而非全部字段,當設置此字段時會返回非完全版本的對象 | `"fields"=>array('name'=>true)` |
| `sort`(排序) | 這個選項用來對查詢結果進行排序,使用一個或多個字段作為排序的標準,使用數組來表格,1代表升序,-1代表降 | `"order"=>array("name"=>-1,"status"=>1)` |
| `limit`(限制) | 限制查詢結果集到指定的范圍 | `"limit"=>10` |
| `skip`(間隔) | 跳過指定的條目選取結果 | `"skip"=>50` |
如果你有使用sql(關系)數據庫的經驗,你也許想查看二者的映射表格[SQL to Mongo Mapping Chart](http://www.php.net/manual/en/mongo.sqltomongo.php).
## 聚合(Aggregations)
我們可以使用Mongo提供的方法使用Mongo模型返回聚合結果。聚合結果不是使用MapReduce來計算的。基于此,我們可以非常容易的取得聚合值,比如總計或平均值等:
~~~
<?php
$data = Article::aggregate(
[
[
"\$project" => [
"category" => 1,
],
],
[
"\$group" => [
"_id" => [
"category" => "\$category"
],
"id" => [
"\$max" => "\$_id",
],
],
],
]
);
~~~
## 創建和更新記錄(Creating Updating/Records)
`Phalcon\Mvc\Collection::save()`方法可以用來保存數據,Phalcon會根據當前數據庫中的數據來對比以確定是新加一條數據還是更新數據。在Phalcon內部會直接使用[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)的save或update方法來進行操作。
當然這個方法內部也會調用我們在模型中定義的驗證方法或事件等:
~~~
<?php
$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!";
}
~~~
“\_id”屬性會被Mongo驅動自動的隨MongId\_而更新。
~~~
<?php
$robot->save();
echo "The generated id is: ", $robot->getId();
~~~
### 驗證信息(Validation Messages)
[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)提供了一個信息子系統,使用此系統開發者可以非常容易的實現在數據處理中的驗證信息的顯示及保存。
每條信息即是一個[Phalcon\\Mvc\\Model\\Message](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model_Message.html)類的對象實例。我們使用getMessages來取得此信息。每條信息中包含了 如哪個字段產生的消息,或是消息類型等信息:
~~~
<?php
if ($robot->save() === false) {
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo "Message: ", $message->getMessage();
echo "Field: ", $message->getField();
echo "Type: ", $message->getType();
}
}
~~~
### 驗證事件和事件管理(Validation Events and Events Manager)
在模型類的數據操作過程中可以產生一些事件。我們可以在這些事件中定義一些業務規則。下面是[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)所支持的事件及其執行順序:
| 操作 | 名稱 | 能否停止操作 | 解釋 |
| --- | --- | --- | --- |
| Inserting/Updating | `beforeValidation` | YES | 在驗證和最終插入/更新進行之執行 |
| Inserting | `beforeValidationOnCreate` | YES | 僅當創建新條目驗證之前執行 |
| Updating | `beforeValidationOnUpdate` | YES | 僅在更新條目驗證之前 |
| Inserting/Updating | `onValidationFails` | YES (already stopped) | 驗證執行失敗后執行 |
| Inserting | `afterValidationOnCreate` | YES | 新建條目驗證之后執行 |
| Updating | `afterValidationOnUpdate` | YES | 更新條目后執行 |
| Inserting/Updating | `afterValidation` | YES | 在驗證進行之前執 |
| Inserting/Updating | `beforeSave` | YES | 在請示的操作(保存)運行之前 |
| Updating | `beforeUpdate` | YES | 更新操作執行之前運行 |
| Inserting | `beforeCreate` | YES | 創建操作執行之前運行 |
| Updating | `afterUpdate` | NO | 更新執行之后執行 |
| Inserting | `afterCreate` | NO | 創建執行之后 |
| Inserting/Updating | `afterSave` | NO | 保存執行之后 |
為了響應一個事件,我們需在模型中實現同名方法:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function beforeValidationOnCreate()
{
echo "This is executed before creating a Robot!";
}
}
~~~
在執行操作之前先在指定的事件中設置值有時是非常有用的:
~~~
<?php
use Phalcon\Mvc\Collection;
class Products extends Collection
{
public function beforeCreate()
{
// Set the creation date
$this->created_at = date("Y-m-d H:i:s");
}
public function beforeUpdate()
{
// Set the modification date
$this->modified_in = date("Y-m-d H:i:s");
}
}
~~~
另外,這個組件也可以和[Phalcon\\Events\\Manager](http://docs.iphalcon.cn/reference/events.html)進行集成,這就意味著我們可以通過事件觸發創建監聽器。
~~~
<?php
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
$eventsManager = new EventsManager();
// Attach an anonymous function as a listener for "model" events
$eventsManager->attach(
"collection:beforeSave",
function (Event $event, $robot) {
if ($robot->name === "Scooby Doo") {
echo "Scooby Doo isn't a robot!";
return false;
}
return true;
}
);
$robot = new Robots();
$robot->setEventsManager($eventsManager);
$robot->name = "Scooby Doo";
$robot->year = 1969;
$robot->save();
~~~
上面的例子中EventsManager僅在對象和監聽器(匿名函數)之間扮演了一個橋接器的角色。如果我們想在創建應用時使用同一個EventsManager,我們需要把這個EventsManager對象設置到 collectionManager服務中:
~~~
<?php
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\Collection\Manager as CollectionManager;
// Registering the collectionManager service
$di->set(
"collectionManager",
function () {
$eventsManager = new EventsManager();
// Attach an anonymous function as a listener for "model" events
$eventsManager->attach(
"collection:beforeSave",
function (Event $event, $model) {
if (get_class($model) === "Robots") {
if ($model->name === "Scooby Doo") {
echo "Scooby Doo isn't a robot!";
return false;
}
}
return true;
}
);
// Setting a default EventsManager
$modelsManager = new CollectionManager();
$modelsManager->setEventsManager($eventsManager);
return $modelsManager;
},
true
);
~~~
### 實現業務規則(Implementing a Business Rule)
當插入或更新刪除等執行時,模型會檢查上面表格中列出的方法是否存在。
我們建議定義模型里的驗證方法以避免業務邏輯暴露出來。
下面的例子中實現了在保存或更新時對年份的驗證,年份不能小于0年:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function beforeSave()
{
if ($this->year < 0) {
echo "Year cannot be smaller than zero!";
return false;
}
}
}
~~~
在響應某些事件時返回了false則會停止當前的操作。 如果事實響應未返回任何值,[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)會假定返回了true值。
### 驗證數據完整性(Validating Data Integrity)
[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)提供了若干個事件用于驗證數據和實現業務邏輯。特定的事件中我們可以調用內建的驗證器, Phalcon提供了一些驗證器可以用在此階段的驗證上。
下面的例子中展示了如何使用:
~~~
<?php
use Phalcon\Mvc\Collection;
use Phalcon\Validation;
use Phalcon\Validation\Validator\InclusionIn;
use Phalcon\Validation\Validator\Numericality;
class Robots extends Collection
{
public function validation()
{
$validation = new Validation();
$validation->add(
"type",
new InclusionIn(
[
"message" => "Type must be: mechanical or virtual",
"domain" => [
"Mechanical",
"Virtual",
],
]
)
);
$validation->add(
"price",
new Numericality(
[
"message" => "Price must be numeric"
]
)
);
return $this->validate($validation);
}
}
~~~
上面的例子使用了內建的”InclusionIn”驗證器。這個驗證器檢查了字段的類型是否在指定的范圍內。如果值不在范圍內即驗證失敗會返回false.
> For more information on validators, see the[Validation documentation](http://docs.iphalcon.cn/reference/validation.html).
## 刪除記錄(Deleting Records)
`Phalcon\Mvc\Collection::delete()`方法用來刪除記錄條目。我們可以如下使用:
~~~
<?php
$robot = Robots::findFirst();
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!";
}
}
~~~
也可以使用遍歷的方式刪除多個條目的數據:
~~~
<?php
$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!";
}
}
~~~
當刪除操作執行時我們可以執行如下事件,以實現定制業務邏輯的目的:
| 操作 | 名稱 | 是否可停止 | 解釋 |
| --- | --- | --- | --- |
| 刪除 | `beforeDelete` | 是 | 刪除之前執行 |
| 刪除 | `afterDelete` | 否 | 刪除之后執行 |
## 驗證失敗事件(Validation Failed Events)
驗證失敗時依據不同的情形下列事件會觸發:
| 操作 | 名稱 | 解釋 |
| --- | --- | --- |
| 插入和或更新 | `notSave` | 當插入/更新操作失敗時觸 |
| 插入刪除或更新 | `onValidationFails` | 當數據操作失敗時觸發 |
## 固有 Id 和 用戶主鍵(Implicit Ids vs. User Primary Keys)
默認[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)會使用MongoIds\_來產生`_id`.如果用戶想自定義主鍵也可以只需:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function initialize()
{
$this->useImplicitObjectIds(false);
}
}
~~~
## 設置多個數據庫(Setting multiple databases)
Phalcon中,所有的模型可以只屬于一個數據庫或是多個數據庫。事實上當[Phalcon\\Mvc\\Collection](http://docs.iphalcon.cn/api/Phalcon_Mvc_Collection.html)試圖連接數據庫時 Phalcon會從DI中取名為mongo的服務。當然我們可在模型的initialize方法中進行連接設置:
~~~
<?php
// This service returns a mongo database at 192.168.1.100
$di->set(
"mongo1",
function () {
$mongo = new MongoClient(
"mongodb://scott:nekhen@192.168.1.100"
);
return $mongo->selectDB("management");
},
true
);
// This service returns a mongo database at localhost
$di->set(
"mongo2",
function () {
$mongo = new MongoClient(
"mongodb://localhost"
);
return $mongo->selectDB("invoicing");
},
true
);
~~~
然后在初始化方法,我們定義了模型的連接:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function initialize()
{
$this->setConnectionService("mongo1");
}
}
~~~
## 注入服務到模型(Injecting services into Models)
我們可能需要在模型內使用應用的服務,下面的例子中展示了如何去做:
~~~
<?php
use Phalcon\Mvc\Collection;
class Robots extends Collection
{
public function notSave()
{
// Obtain the flash service from the DI container
$flash = $this->getDI()->getShared("flash");
$messages = $this->getMessages();
// Show validation messages
foreach ($messages as $message) {
$flash->error(
(string) $message
);
}
}
}
~~~
notSave事件在創建和更新失敗時觸發。我們使用flash服務來處理驗證信息。如此做我們無需在每次保存后打印消息出來。
- 簡介
- 安裝
- 安裝(installlation)
- XAMPP下的安裝
- WAMP下安裝
- Nginx安裝說明
- Apache安裝說明
- Cherokee 安裝說明
- 使用 PHP 內置 web 服務器
- Phalcon 開發工具
- Linux 系統下使用 Phalcon 開發工具
- Mac OS X 系統下使用 Phalcon 開發工具
- Windows 系統下使用 Phalcon 開發工具
- 教程
- 教程 1:讓我們通過例子來學習
- 教程 2:INVO簡介
- 教程 3: 保護INVO
- 教程4: 使用CRUD
- 教程5: 定制INVO
- 教程 6: V?kuró
- 教程 7:創建簡單的 REST API
- 組件
- 依賴注入與服務定位器
- MVC架構
- 使用控制器
- 使用模型
- 模型關系
- 事件與事件管理器
- Behaviors
- 模型元數據
- 事務管理
- 驗證數據完整性
- Workingwith Models
- Phalcon查詢語言
- 緩存對象關系映射
- 對象文檔映射 ODM
- 使用視圖
- 視圖助手
- 資源文件管理
- Volt 模版引擎
- MVC 應用
- 路由
- 調度控制器
- Micro Applications
- 使用命名空間
- 事件管理器
- Request Environmen
- 返回響應
- Cookie 管理
- 生成 URL 和 路徑
- 閃存消息
- 使用 Session 存儲數據
- 過濾與清理
- 上下文編碼
- 驗證Validation
- 表單_Forms
- 讀取配置
- 分頁 Pagination
- 使用緩存提高性能
- 安全
- 加密與解密 Encryption/Decryption
- 訪問控制列表
- 多語言支持
- 類加載器 Class Autoloader
- 日志記錄_Logging
- 注釋解析器 Annotations Parser
- 命令行應用 Command Line Applications
- Images
- 隊列 Queueing
- 數據庫抽象層
- 國際化
- 數據庫遷移
- 調試應用程序
- 單元測試
- 進階技巧與延伸閱讀
- 提高性能:下一步該做什么?
- Dependency Injection Explained
- Understanding How Phalcon Applications Work
- Api
- Abstract class Phalcon\Acl