ODM(对象文档映射器)
最后更新于:2022-04-02 05:14:21
[TOC]
>[info] 请注意,如果您使用的是PHP 7提供的Mongo驱动程序,则ODM将不适合您。有一个孵化器适配器,但必须重写所有Mongo代码(新的Bson类型而不是数组,没有MongoId,没有MongoDate等)。请确保在升级到PHP 7和/或Phalcon 3+之前测试代码
# ODM(对象文档映射器)
除了能够在关系数据库中映射表之外,Phalcon还可以映射来自NoSQL数据库的文档。ODM提供CRUD功能,事件,其他服务的验证。
由于缺少SQL查询和规划器,NoSQL数据库可以使用Phalcon方法看到性能的真正改进。此外,没有SQL构建减少SQL注入的可能性。
支持以下NoSQL数据库:
|名称 | 描述 |
| ---------------------------------- | -------------------------------------------------------------------- |
| [MongoDB](http://www.mongodb.org/) | MongoDB是一个可扩展的高性能开源NoSQL数据库。|
## 创建模型
模型是从`Phalcon\Mvc\Collection`扩展的类。它必须放在models目录中。模型文件必须包含单个类;它的类名应该是驼峰表示法:
```php
setSource('the_robots');
}
}
```
## 了解文档到对象
模型的每个实例都代表集合中的文档。您可以通过读取对象属性轻松访问集合数据。例如,对于带有文档的集合`robots`:
```bash
$ 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 }
>
```
## 命名空间中的模型
命名空间可用于避免类名冲突。在这种情况下,有必要使用`setSource()`方法指示相关集合的名称:
```php
setSource('robots');
}
}
```
您可以通过其ID找到某个文档,然后打印其名称:
```php
name;
```
记录在内存中后,您可以对其数据进行修改,然后保存更改:
```php
'Astro Boy',
]
]
);
$robot->name = 'Voltron';
$robot->save();
```
## 设置连接
从服务容器中检索连接。默认情况下,Phalcon尝试在名为`mongo`的服务中查找连接:
```php
set(
'mongo',
function () {
$mongo = new MongoClient();
return $mongo->selectDB('store');
},
true
);
// 连接到域套接字,回退到localhost连接
$di->set(
'mongo',
function () {
$mongo = new MongoClient(
'mongodb:///tmp/mongodb-27017.sock,localhost:27017'
);
return $mongo->selectDB('store');
},
true
);
```
## 查找文档
由于 `Phalcon\Mvc\Collection` 依赖于Mongo PHP扩展,因此您可以使用相同的工具来查询文档并将它们透明地转换为模型实例:
```php
'mechanical',
]
]
);
echo 'There are ', count($robots), "\n";
// 获取并打印按名称向上排序的机械机器人
$robots = Robots::find(
[
[
'type' => 'mechanical',
],
'sort' => [
'name' => 1,
],
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
// 按名称订购前100台机械机器人
$robots = Robots::find(
[
[
'type' => 'mechanical',
],
'sort' => [
'name' => 1,
],
'limit' => 100,
]
);
foreach ($robots as $robot) {
echo $robot->name, "\n";
}
```
您还可以使用`findFirst()`方法仅获取与给定条件匹配的第一条记录:
```php
name, "\n";
// 机器人集合中第一台机械机器人是什么?
$robot = Robots::findFirst(
[
[
'type' => 'mechanical',
]
]
);
echo 'The first mechanical robot name is ', $robot->name, "\n";
```
`find()` 和 `findFirst()` 方法都接受指定搜索条件的关联数组:
```php
[
'type' => 'mechanical',
'year' => '1999',
],
]
);
// 所有虚拟机器人按名称向下排序
$robots = Robots::find(
[
'conditions' => [
'type' => 'virtual',
],
'sort' => [
'name' => -1,
],
]
);
// 使用where条件查找所有拥有4个以上朋友的机器人
$robots = Robots::find(
[
'conditions' => [
'$where' => 'this.friends.length > 4',
]
]
);
```
可用的查询选项包括:
| 参数 | 描述 | 示例 |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `conditions` | 搜索查找操作的条件。用于仅提取满足指定标准的记录。默认情况下,`Phalcon\Model`假定第一个参数是条件。 | `'conditions' => array('$gt' => 1990)` |
| `fields` | 返回特定列而不是集合中的完整字段。使用此选项时,将返回不完整的对象 | `'fields' => array('name' => true)` |
| `sort` | 它用于对结果集进行排序。使用一个或多个字段作为数组中的每个元素,1表示向上排序,-1表示向下排序 | `'sort' => array('name' => -1, 'status' => 1)` |
| `limit` | 将查询结果限制为某个范围的结果 | `'limit' => 10` |
| `skip` | 跳过一些结果 | `'skip' => 50` |
如果您有使用SQL数据库的经验,则可能需要检查[SQL到Mongo Mapping Chart](http://www.php.net/manual/en/mongo.sqltomongo.php)。
## 查询特定字段
要使用Phalcon ODM从MongoDB数据库查询特定字段的特定字段,您只需要:
```php
$myRobots = Robots:find(
[
'fields' => ['name' => 1]
]
];
```
上面的`find()`只返回一个`name`。它也可以与`condition`结合:
```php
$myRobots = Robots:find(
[
['type' => 'maid'],
'fields' => ['name' => 1]
]
];
```
上面的示例返回`type = 'maid'`的机器人名称。
## 聚合
模型可以使用Mongo提供的[聚合框架](http://docs.mongodb.org/manual/applications/aggregation/)返回计算。无需使用MapReduce即可计算聚合值。使用此选项可以轻松执行诸如总计或平均字段值之类的任务:
```php
[
'category' => 1,
],
],
[
'\$group' => [
'_id' => [
'category' => '\$category'
],
'id' => [
'\$max' => '\$_id',
],
],
],
]
);
```
## 创建更新/记录
`Phalcon\Mvc\Collection::save()` 方法允许您根据文档是否已存在于与模型关联的集合中来创建/更新文档。`save()` 方法由 `Phalcon\Mvc\Collection`的create和update方法在内部调用。
此方法还执行模型中定义的关联验证器和事件:
```php
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!';
}
```
使用驱动程序创建的[MongoId](http://www.php.net/manual/en/class.mongoid.php)对象自动更新`_id`属性:
```php
save();
echo 'The generated id is: ', $robot->getId();
```
### 验证消息
`Phalcon\Mvc\Collection` 有一个消息传递子系统,它提供了一种灵活的方式来输出或存储插入/更新过程中生成的验证消息。
每条消息都包含 `Phalcon\Mvc\Model\Message`类的实例。可以使用 `getMessages()`方法检索生成的消息集。
每条消息都提供扩展信息,例如生成消息的字段名称或消息类型:
```php
save() === false) {
$messages = $robot->getMessages();
foreach ($messages as $message) {
echo 'Message: ', $message->getMessage();
echo 'Field: ', $message->getField();
echo 'Type: ', $message->getType();
}
}
```
### 验证事件和事件管理器
模型允许您实现在执行插入或更新时将抛出的事件。它们有助于为特定模型定义业务规则。以下是`Phalcon\Mvc\Collection`支持的事件及其执行顺序:
| 操作 | 名称 | Can stop operation? | 说明 |
| ------------------ | -------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ |
| 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
created_at = date('Y-m-d H:i:s');
}
public function beforeUpdate()
{
// 设置修改日期
$this->modified_in = date('Y-m-d H:i:s');
}
}
```
此外,该组件与Phalcon事件管理器(`Phalcon\Events\Manager`)集成,这意味着我们可以创建在触发事件时运行的侦听器。
```php
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,那么我们需要将它分配给Models Manager:
```php
set(
'collectionManager',
function () {
$eventsManager = new EventsManager();
// 附加匿名函数作为“模型”事件的侦听器
$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;
}
);
// 设置默认的EventsManager
$modelsManager = new CollectionManager();
$modelsManager->setEventsManager($eventsManager);
return $modelsManager;
},
true
);
```
### 实现业务规则
执行插入,更新或删除时,模型将验证是否存在具有上表中列出的事件名称的任何方法。
我们建议声明`protected`的验证方法,以防止公开暴露业务逻辑实现。
以下示例实现了一个事件,该事件在更新或插入时验证年份不能小于0:
```php
year < 0) {
echo 'Year cannot be smaller than zero!';
return false;
}
}
}
```
某些事件返回false作为停止当前操作的指示。如果事件没有返回任何内容,`Phalcon\Mvc\Collection`将采用true值。
### 验证数据完整性
`Phalcon\Mvc\Collection` 提供了几个事件来验证数据和实现业务规则。特殊验证事件允许我们在记录上调用内置验证器。Phalcon公开了一些可在此验证阶段使用的内置验证器。
以下示例显示了如何使用它:
```php
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`执行验证。它检查字段`type`的值是否在`domain`列表中。如果该值未包含在列表中,则验证器将失败并返回false。
>[warning] 有关验证器的更多信息,请参阅[验证文档]。
## 删除记录
`Phalcon\Mvc\Collection::delete()` 方法允许您删除文档。
您可以按如下方式使用它:
```php
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
'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 | `beforeDelete` | YES | 在执行删除操作之前运行 |
| Deleting | `afterDelete` | NO | 删除操作完成后运行 |
## 验证失败的事件
当数据验证过程发现任何不一致时,可以使用另一种类型的事件:
| 操作 | 名称 | 说明 |
| ------------------------ | ------------------- | --------------------------------------------------------------- |
| Insert or Update | `notSave` | 插入/更新操作因任何原因失败时触发 |
| Insert, Delete or Update | `onValidationFails` | 任何数据操作操作失败时触发 |
## 隐式ID与用户主键
默认情况下,`Phalcon\Mvc\Collection` 假定使用[MongoIds](http://www.php.net/manual/en/class.mongoid.php)自动生成 `_id` 属性。 如果模型使用自定义主键,则可以覆盖此行为
```php
useImplicitObjectIds(false);
}
}
```
## 设置多个数据库
在Phalcon中,所有模型都可以共享相同的数据库连接或为每个模型指定连接。实际上,当`Phalcon\Mvc\Collection` 需要连接到数据库时,它会在应用程序的服务容器中请求 `mongo` 服务。 您可以通过在 `initialize()`方法中设置它来覆盖此服务:
```php
set(
'mongo1',
function () {
$mongo = new MongoClient(
'mongodb://scott:nekhen@192.168.1.100'
);
return $mongo->selectDB('management');
},
true
);
// 此服务在localhost返回mongo数据库
$di->set(
'mongo2',
function () {
$mongo = new MongoClient(
'mongodb://localhost'
);
return $mongo->selectDB('invoicing');
},
true
);
```
然后,在`initialize()` 方法中,我们定义模型的连接服务:
```php
setConnectionService('mongo1');
}
}
```
## 将服务注入模型
您可能需要访问模型中的应用程序服务,以下示例说明如何执行此操作:
```php
getDI()->getShared('flash');
$messages = $this->getMessages();
// 显示验证消息
foreach ($messages as $message) {
$flash->error(
(string) $message
);
}
}
}
```
只要创建或更新操作失败,就会触发`notSave`事件。 我们正在刷新验证消息,从DI容器中获取闪存服务。通过这样做,我们不必在每次保存后打印消息。
';