对象文档映射 ODMODM (Object-Document Mapper)

除了可以 map tables 映射关系数据库的表之外,Phalcon还可以使用NoSQL数据库如MongoDB等。Phalcon中的ODM具有可以非常容易的实现如下功能:CRUD,事件,验证等。

In addition to its ability to map tables in relational databases, Phalcon can map documents from NoSQL databases. The ODM offers a CRUD functionality, events, validations among other services.

因为NoSQL数据库中无sql查询及计划等操作故可以提高数据操作的性能。再者,由于无SQL语句创建的操作故可以减少SQL注入的危险。

Due to the absence of SQL queries and planners, NoSQL databases can see real improvements in performance using the Phalcon approach. Additionally, there are no SQL building reducing the possibility of SQL injections.

当前Phalcon中支持的NosSQL数据库如下:

The following NoSQL databases are supported:

NameDescription
MongoDBMongoDB is a scalable, high-performance, open source NoSQL database.

创建模型Creating Models

NoSQL中的模型类扩展自 Phalcon\Mvc\Collection.模型必须要放入模型文件夹中而且每个模型文件必须只能有一个模型类; 模型类名应该为小驼峰法书写:

A model is a class that extends from Phalcon\Mvc\Collection. It must be placed in the models directory. A model file must contain a single class; its class name should be in camel case notation:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. }

如果PHP版本为5.4/5.5或更高版本,为了提高性能节省内存开销,最好在模型类文件中定义每个字段。

If you’re using PHP 5.4/5.5 is recommended declare each column that makes part of the model in order to save memory and reduce the memory allocation.

模型Robots默认和数据库中的robots表格映射。如果想使用别的名字映射数据库中的表格则只需要重写getSource()方法即可:

By default model “Robots” will refer to the collection “robots”. If you want to manually specify another name for the mapping collection, you can use the getSource() method:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function getSource()
  6. {
  7. return "the_robots";
  8. }
  9. }

理解文档对象Understanding Documents To Objects

每个模型的实例和数据库表中的一个文档(记录)相对应。我们可以非常容易的通过读取对象属性来访问表格的数据。例如访问robots表格:

Every instance of a model represents a document in the collection. You can easily access collection data by reading object properties. For example, for a collection “robots” with the documents:

  1. $ mongo test
  2. MongoDB shell version: 1.8.2
  3. connecting to: test
  4. > db.robots.find()
  5. { "_id" : ObjectId("508735512d42b8c3d15ec4e1"), "name" : "Astro Boy", "year" : 1952,
  6. "type" : "mechanical" }
  7. { "_id" : ObjectId("5087358f2d42b8c3d15ec4e2"), "name" : "Bender", "year" : 1999,
  8. "type" : "mechanical" }
  9. { "_id" : ObjectId("508735d32d42b8c3d15ec4e3"), "name" : "Wall-E", "year" : 2008 }
  10. >

模型中使用命名空间Models in Namespaces

我们在这里可以使用命名空间来避免类名冲突。这个例子中我们使用getSource方法来标明要使用的数据库表:

Namespaces can be used to avoid class name collision. In this case it is necessary to indicate the name of the related collection using getSource:

  1. <?php
  2. namespace Store\Toys;
  3. use Phalcon\Mvc\Collection;
  4. class Robots extends Collection
  5. {
  6. public function getSource()
  7. {
  8. return "robots";
  9. }
  10. }

我们可以通过对象的ID查找到对象然后打印出其名字:

You could find a certain document by its id and then print its name:

  1. <?php
  2. // Find record with _id = "5087358f2d42b8c3d15ec4e2"
  3. $robot = Robots::findById("5087358f2d42b8c3d15ec4e2");
  4. // Prints "Bender"
  5. echo $robot->name;

一旦记录被加载到内存中,我们就可以对这些数据进行修改了,修改之后还可以保存:

Once the record is in memory, you can make modifications to its data and then save changes:

  1. <?php
  2. $robot = Robots::findFirst(array(
  3. array('name' => 'Astroy Boy')
  4. ));
  5. $robot->name = "Voltron";
  6. $robot->save();

设置连接 Setting a Connection

这里的MongoDB服务是从服务容器中取得的。默认,Phalcon会使mongo作服务名:

Connections are retrieved from the services container. By default, Phalcon tries to find the connection in a service called “mongo”:

  1. <?php
  2. // Simple database connection to localhost
  3. $di->set('mongo', function() {
  4. $mongo = new MongoClient();
  5. return $mongo->selectDB("store");
  6. }, true);
  7. // Connecting to a domain socket, falling back to localhost connection
  8. $di->set('mongo', function() {
  9. $mongo = new MongoClient("mongodb:///tmp/mongodb-27017.sock,localhost:27017");
  10. return $mongo->selectDB("store");
  11. }, true);

查找文档Finding Documents

Phalcon\Mvc\Collection 依赖mongo的PHP扩展,我们有同样的功能区查询文档并在模型中进行转换:

As Phalcon\Mvc\Collection relies on the Mongo PHP extension you have the same facilities to query documents and convert them transparently to model instances:

  1. <?php
  2. // How many robots are there?
  3. $robots = Robots::find();
  4. echo "There are ", count($robots), "\n";
  5. // How many mechanical robots are there?
  6. $robots = Robots::find(array(
  7. array("type" => "mechanical")
  8. ));
  9. echo "There are ", count($robots), "\n";
  10. // Get and print mechanical robots ordered by name upward
  11. $robots = Robots::find(array(
  12. array("type" => "mechanical"),
  13. "sort" => array("name" => 1)
  14. ));
  15. foreach ($robots as $robot) {
  16. echo $robot->name, "\n";
  17. }
  18. // Get first 100 mechanical robots ordered by name
  19. $robots = Robots::find(array(
  20. array("type" => "mechanical"),
  21. "sort" => array("name" => 1),
  22. "limit" => 100
  23. ));
  24. foreach ($robots as $robot) {
  25. echo $robot->name, "\n";
  26. }

这里我们可以使用findFirst()来取得配置查询的第一条记录:

You could also use the findFirst() method to get only the first record matching the given criteria:

  1. <?php
  2. // What's the first robot in robots collection?
  3. $robot = Robots::findFirst();
  4. echo "The robot name is ", $robot->name, "\n";
  5. // What's the first mechanical robot in robots collection?
  6. $robot = Robots::findFirst(array(
  7. array("type" => "mechanical")
  8. ));
  9. echo "The first mechanical robot name is ", $robot->name, "\n";

find()和findFirst方法都接收一个关联数据组为查询的条件:

Both find() and findFirst() methods accept an associative array specifying the search criteria:

  1. <?php
  2. // First robot where type = "mechanical" and year = "1999"
  3. $robot = Robots::findFirst(array(
  4. "conditions" => array(
  5. "type" => "mechanical",
  6. "year" => "1999"
  7. )
  8. ));
  9. // All virtual robots ordered by name downward
  10. $robots = Robots::find(array(
  11. "conditions" => array("type" => "virtual"),
  12. "sort" => array("name" => -1)
  13. ));

可用的查询选项:

The available query options are:

ParameterDescriptionExample
conditionsSearch conditions for the find operation. Is used to extract only those records that fulfill a specified criterion. By default Phalcon_model assumes the first parameter are the conditions.“conditions” => array(‘$gt’ => 1990)
fieldsReturns specific columns instead of the full fields in the collection. When using this option an incomplete object is returned“fields” => array(‘name’ => true)
sortIt’s used to sort the resultset. Use one or more fields as each element in the array, 1 means ordering upwards, -1 downward“sort” => array(“name” => -1, “status” => 1)
limitLimit the results of the query to results to certain range“limit” => 10
skipSkips a number of results“skip” => 50

如果你有使用sql(关系)数据库的经验,你也许想查看二者的映射表格 SQL to Mongo Mapping Chart .

If you have experience with SQL databases, you may want to check the SQL to Mongo Mapping Chart.

聚合Aggregations

我们可以使用Mongo提供的 aggregation framework 方法使用Mongo模型返回聚合结果。聚合结果不是使用MapReduce来计算的。基于此,我们可以非常容易的取得聚合值,比如总计或平均值等:

A model can return calculations using aggregation framework provided by Mongo. The aggregated values are calculate without having to use MapReduce. With this option is easy perform tasks such as totaling or averaging field values:

  1. <?php
  2. $data = Article::aggregate(array(
  3. array(
  4. '$project' => array('category' => 1)
  5. ),
  6. array(
  7. '$group' => array(
  8. '_id' => array('category' => '$category'),
  9. 'id' => array('$max' => '$_id')
  10. )
  11. )
  12. ));

创建和更新记录Creating Updating/Records

Phalcon\Mvc\Collection::save()方法可以用来保存数据,Phalcon会根据当前数据库中的数据来对比以确定是新加一条数据还是更新数据。在Phalcon内部会直接使用 Phalcon\Mvc\Collection 的save或update方法来进行操作。 当然这个方法内部也会调用我们在模型中定义的验证方法或事件等:

The method Phalcon\Mvc\Collection::save() allows you to create/update documents according to whether they already exist in the collection associated with a model. The ‘save’ method is called internally by the create and update methods of Phalcon\Mvc\Collection.

Also the method executes associated validators and events that are defined in the model:

  1. <?php
  2. $robot = new Robots();
  3. $robot->type = "mechanical";
  4. $robot->name = "Astro Boy";
  5. $robot->year = 1952;
  6. if ($robot->save() == false) {
  7. echo "Umh, We can't store robots right now: \n";
  8. foreach ($robot->getMessages() as $message) {
  9. echo $message, "\n";
  10. }
  11. } else {
  12. echo "Great, a new robot was saved successfully!";
  13. }

“_id”属性会被Mongo驱动自动的随 MongId_ 而更新。

The “_id” property is automatically updated with the MongoId object created by the driver:

  1. <?php
  2. $robot->save();
  3. echo "The generated id is: ", $robot->getId();

验证信息Validation Messages

Phalcon\Mvc\Collection 提供了一个信息子系统,使用此系统开发者可以非常容易的实现在数据处理中的验证信息的显示及保存。 每条信息即是一个 Phalcon\Mvc\Model\Message 类的对象实例。我们使用getMessages来取得此信息。每条信息中包含了如哪个字段产生的消息,或是消息类型等信息:

Phalcon\Mvc\Collection has a messaging subsystem that provides a flexible way to output or store the validation messages generated during the insert/update processes.

Each message consists of an instance of the class Phalcon\Mvc\Model\Message. The set of messages generated can be retrieved with the method getMessages(). Each message provides extended information like the field name that generated the message or the message type:

  1. <?php
  2. if ($robot->save() == false) {
  3. foreach ($robot->getMessages() as $message) {
  4. echo "Message: ", $message->getMessage();
  5. echo "Field: ", $message->getField();
  6. echo "Type: ", $message->getType();
  7. }
  8. }

验证事件和事件管理 Validation Events and Events Manager

在模型类的数据操作过程中可以产生一些事件。我们可以在这些事件中定义一些业务规则。下面是 Phalcon\Mvc\Collection 所支持的事件及其执行顺序:

Models allow you to implement events that will be thrown when performing an insert or update. They help define business rules for a certain model. The following are the events supported by Phalcon\Mvc\Collection and their order of execution:

OperationNameCan stop operation?Explanation
Inserting/UpdatingbeforeValidationYESIs executed before the validation process and the final insert/update to the database
InsertingbeforeValidationOnCreateYESIs executed before the validation process only when an insertion operation is being made
UpdatingbeforeValidationOnUpdateYESIs executed before the fields are validated for not nulls or foreign keys when an updating operation is being made
Inserting/UpdatingonValidationFailsYES (already stopped)Is executed before the validation process only when an insertion operation is being made
InsertingafterValidationOnCreateYESIs executed after the validation process when an insertion operation is being made
UpdatingafterValidationOnUpdateYESIs executed after the validation process when an updating operation is being made
Inserting/UpdatingafterValidationYESIs executed after the validation process
Inserting/UpdatingbeforeSaveYESRuns before the required operation over the database system
UpdatingbeforeUpdateYESRuns before the required operation over the database system only when an updating operation is being made
InsertingbeforeCreateYESRuns before the required operation over the database system only when an inserting operation is being made
UpdatingafterUpdateNORuns after the required operation over the database system only when an updating operation is being made
InsertingafterCreateNORuns after the required operation over the database system only when an inserting operation is being made
Inserting/UpdatingafterSaveNORuns after the required operation over the database system

为了响应一个事件,我们需在模型中实现同名方法:

To make a model to react to an event, we must to implement a method with the same name of the event:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function beforeValidationOnCreate()
  6. {
  7. echo "This is executed before creating a Robot!";
  8. }
  9. }

在执行操作之前先在指定的事件中设置值有时是非常有用的:

Events can be useful to assign values before performing an operation, for example:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Products extends Collection
  4. {
  5. public function beforeCreate()
  6. {
  7. // Set the creation date
  8. $this->created_at = date('Y-m-d H:i:s');
  9. }
  10. public function beforeUpdate()
  11. {
  12. // Set the modification date
  13. $this->modified_in = date('Y-m-d H:i:s');
  14. }
  15. }

另外,这个组件也可以和 Phalcon\Events\Manager 进行集成,这就意味着我们在事件触发创建监听器

Additionally, this component is integrated with Phalcon\Events\Manager, this means we can create listeners that run when an event is triggered.

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. $eventsManager = new EventsManager();
  4. //Attach an anonymous function as a listener for "model" events
  5. $eventsManager->attach('collection', function($event, $robot) {
  6. if ($event->getType() == 'beforeSave') {
  7. if ($robot->name == 'Scooby Doo') {
  8. echo "Scooby Doo isn't a robot!";
  9. return false;
  10. }
  11. }
  12. return true;
  13. });
  14. $robot = new Robots();
  15. $robot->setEventsManager($eventsManager);
  16. $robot->name = 'Scooby Doo';
  17. $robot->year = 1969;
  18. $robot->save();

上面的例子中EventsManager仅在对象和监听器(匿名函数)之间扮演了一个桥接器的角色。如果我们想在创建应用时使用同一个EventsManager,我们需要把这个EventsManager对象设置到 collectionManager服务中:

In the example given above the EventsManager only acted as a bridge between an object and a listener (the anonymous function). If we want all objects created in our application use the same EventsManager, then we need to assign this to the Models Manager:

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. use Phalcon\Mvc\Collection\Manager as CollectionManager;
  4. //Registering the collectionManager service
  5. $di->set('collectionManager', function() {
  6. $eventsManager = new EventsManager();
  7. // Attach an anonymous function as a listener for "model" events
  8. $eventsManager->attach('collection', function($event, $model) {
  9. if (get_class($model) == 'Robots') {
  10. if ($event->getType() == 'beforeSave') {
  11. if ($model->name == 'Scooby Doo') {
  12. echo "Scooby Doo isn't a robot!";
  13. return false;
  14. }
  15. }
  16. }
  17. return true;
  18. });
  19. // Setting a default EventsManager
  20. $modelsManager = new CollectionManager();
  21. $modelsManager->setEventsManager($eventsManager);
  22. return $modelsManager;
  23. }, true);

实现业务规则Implementing a Business Rule

当插入或更新删除等执行时,模型会检查上面表格中列出的方法是否存在。我们建议定义模型里的验证方法以避免业务逻辑暴露出来。下面的例子中实现了在保存或更新时对年份的验证,年份不能小于0年:

When an insert, update or delete is executed, the model verifies if there are any methods with the names of the events listed in the table above.

We recommend that validation methods are declared protected to prevent that business logic implementation from being exposed publicly.

The following example implements an event that validates the year cannot be smaller than 0 on update or insert:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function beforeSave()
  6. {
  7. if ($this->year < 0) {
  8. echo "Year cannot be smaller than zero!";
  9. return false;
  10. }
  11. }
  12. }

在响应某些事件时返回了false则会停止当前的操作。 如果事实响应未返回任何值, Phalcon\Mvc\Collection 会假定返回了true值。

Some events return false as an indication to stop the current operation. If an event doesn’t return anything, Phalcon\Mvc\Collection will assume a true value.

验证数据完整性Validating Data Integrity

Phalcon\Mvc\Collection 提供了若干个事件用于验证数据和实现业务逻辑。特定的事件中我们可以调用内建的验证器 Phalcon提供了一些验证器可以用在此阶段的验证上。

Phalcon\Mvc\Collection provides several events to validate data and implement business rules. The special “validation” event allows us to call built-in validators over the record. Phalcon exposes a few built-in validators that can be used at this stage of validation.

下面的例子中展示了如何使用:

The following example shows how to use it:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. use Phalcon\Mvc\Model\Validator\InclusionIn;
  4. use Phalcon\Mvc\Model\Validator\Numericality;
  5. class Robots extends Collection
  6. {
  7. public function validation()
  8. {
  9. $this->validate(new InclusionIn(
  10. array(
  11. "field" => "type",
  12. "message" => "Type must be: mechanical or virtual",
  13. "domain" => array("Mechanical", "Virtual")
  14. )
  15. ));
  16. $this->validate(new Numericality(
  17. array(
  18. "field" => "price",
  19. "message" => "Price must be numeric"
  20. )
  21. ));
  22. return $this->validationHasFailed() != true;
  23. }
  24. }

上面的例子使用了内建的”InclusionIn”验证器。这个验证器检查了字段的类型是否在指定的范围内。如果值不在范围内即验证失败会返回false. 下面支持的内验证器:

The example given above performs a validation using the built-in validator “InclusionIn”. It checks the value of the field “type” in a domain list. If the value is not included in the method, then the validator will fail and return false. The following built-in validators are available:

NameExplanationExample
EmailValidates that field contains a valid email formatExample
ExclusionInValidates that a value is not within a list of possible valuesExample
InclusionInValidates that a value is within a list of possible valuesExample
NumericalityValidates that a field has a numeric formatExample
RegexValidates that the value of a field matches a regular expressionExample
StringLengthValidates the length of a stringExample

除了内建的验证器外,我们还可以创建自己的验证器:

In addition to the built-in validators, you can create your own validators:

  1. <?php
  2. use \Phalcon\Mvc\Model\Validator as CollectionValidator;
  3. class UrlValidator extends CollectionValidator
  4. {
  5. public function validate($model)
  6. {
  7. $field = $this->getOption('field');
  8. $value = $model->$field;
  9. $filtered = filter_var($value, FILTER_VALIDATE_URL);
  10. if (!$filtered) {
  11. $this->appendMessage("The URL is invalid", $field, "UrlValidator");
  12. return false;
  13. }
  14. return true;
  15. }
  16. }

添加验证器到模型:

Adding the validator to a model:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Customers extends Collection
  4. {
  5. public function validation()
  6. {
  7. $this->validate(new UrlValidator(array(
  8. "field" => "url",
  9. )));
  10. if ($this->validationHasFailed() == true) {
  11. return false;
  12. }
  13. }
  14. }

创建验证器的目的即是使之在多个模型间重复利用以实现代码重用。验证器可简单如下:

The idea of creating validators is make them reusable across several models. A validator can also be as simple as:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. use Phalcon\Mvc\Model\Message as ModelMessage;
  4. class Robots extends Collection
  5. {
  6. public function validation()
  7. {
  8. if ($this->type == "Old") {
  9. $message = new ModelMessage(
  10. "Sorry, old robots are not allowed anymore",
  11. "type",
  12. "MyType"
  13. );
  14. $this->appendMessage($message);
  15. return false;
  16. }
  17. return true;
  18. }
  19. }

删除记录Deleting Records

Phalcon\Mvc\Collection::delete()方法用来删除记录条目。我们可以如下使用:

The method Phalcon\Mvc\Collection::delete() allows to delete a document. You can use it as follows:

  1. <?php
  2. $robot = Robots::findFirst();
  3. if ($robot != false) {
  4. if ($robot->delete() == false) {
  5. echo "Sorry, we can't delete the robot right now: \n";
  6. foreach ($robot->getMessages() as $message) {
  7. echo $message, "\n";
  8. }
  9. } else {
  10. echo "The robot was deleted successfully!";
  11. }
  12. }

也可以使用遍历的方式删除多个条目的数据:

You can also delete many documents by traversing a resultset with a foreach:

  1. <?php
  2. $robots = Robots::find(array(
  3. array("type" => "mechanical")
  4. ));
  5. foreach ($robots as $robot) {
  6. if ($robot->delete() == false) {
  7. echo "Sorry, we can't delete the robot right now: \n";
  8. foreach ($robot->getMessages() as $message) {
  9. echo $message, "\n";
  10. }
  11. } else {
  12. echo "The robot was deleted successfully!";
  13. }
  14. }

当删除操作执行时我们可以执行如下事件,以实现定制业务逻辑的目的:

The following events are available to define custom business rules that can be executed when a delete operation is performed:

OperationNameCan stop operation?Explanation
DeletingbeforeDeleteYESRuns before the delete operation is made
DeletingafterDeleteNORuns after the delete operation was made

验证失败事件Validation Failed Events

验证失败时依据不同的情形下列事件会触发:

Another type of events is available when the data validation process finds any inconsistency:

OperationNameExplanation
Insert or UpdatenotSaveTriggered when the insert/update operation fails for any reason
Insert, Delete or UpdateonValidationFailsTriggered when any data manipulation operation fails

固有 Id 和 用户主键Implicit Ids vs. User Primary Keys

默认PhalconMvcCollection会使用MongoIds_来产生_id.如果用户想自定义主键也可以只需:

By default Phalcon\Mvc\Collection assumes that the _id attribute is automatically generated using MongoIds. If a model uses custom primary keys this behavior can be overridden:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function initialize()
  6. {
  7. $this->useImplicitObjectIds(false);
  8. }
  9. }

设置多个数据库Setting multiple databases

Phalcon中,所有的模可以只属于一个数据库或是每个模型有一个数据。事实上当 Phalcon\Mvc\Collection 试图连接数据库时 Phalcon会从DI中取名为mongo的服务。当然我们可在模型的initialize方法中进行连接设置:

In Phalcon, all models can belong to the same database connection or have an individual one. Actually, when Phalcon\Mvc\Collection needs to connect to the database it requests the “mongo” service in the application’s services container. You can overwrite this service setting it in the initialize method:

  1. <?php
  2. // This service returns a mongo database at 192.168.1.100
  3. $di->set('mongo1', function() {
  4. $mongo = new MongoClient("mongodb://scott:nekhen@192.168.1.100");
  5. return $mongo->selectDB("management");
  6. }, true);
  7. // This service returns a mongo database at localhost
  8. $di->set('mongo2', function() {
  9. $mongo = new MongoClient("mongodb://localhost");
  10. return $mongo->selectDB("invoicing");
  11. }, true);

然后在初始化方法,我们定义了模型的连接:

Then, in the Initialize method, we define the connection service for the model:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function initialize()
  6. {
  7. $this->setConnectionService('mongo1');
  8. }
  9. }

注入服务到模型Injecting services into Models

我们可能需要在模型内使用应用的服务,下面的例子中展示了如何去做:

You may be required to access the application services within a model, the following example explains how to do that:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function notSave()
  6. {
  7. // Obtain the flash service from the DI container
  8. $flash = $this->getDI()->getShared('flash');
  9. // Show validation messages
  10. foreach ($this->getMessages() as $message){
  11. $flash->error((string) $message);
  12. }
  13. }
  14. }

notSave事件在创建和更新失败时触发。我们使用flash服务来处理验证信息。如此做我们无需在每次保存后打印消息出来。

The “notSave” event is triggered whenever a “creating” or “updating” action fails. We’re flashing the validation messages obtaining the “flash” service from the DI container. By doing this, we don’t have to print messages after each saving.