使用模型Working with Models

模型代表了应用程序中的信息(数据)和处理数据的规则。模型主要用于管理与相应数据库表进行交互的规则。 大多数情况中,在应用程序中,数据库中每个表将对应一个模型。 应用程序中的大部分业务逻辑都将集中在模型里。

A model represents the information (data) of the application and the rules to manipulate that data. Models are primarily used for managing the rules of interaction with a corresponding database table. In most cases, each table in your database will correspond to one model in your application. The bulk of your application’s business logic will be concentrated in the models.

Phalcon\Mvc\Model 是 Phalcon 应用程序中所有模型的基类。它保证了数据库的独立性,基本的 CURD 操作, 高级的查询功能,多表关联等功能。 PhalconMvcModel 不需要直接使用 SQL 语句,因为它的转换方法,会动态的调用相应的数据库引擎进行处理。

Phalcon\Mvc\Model is the base for all models in a Phalcon application. It provides database independence, basic CRUD functionality, advanced finding capabilities, and the ability to relate models to one another, among other services. Phalcon\Mvc\Model avoids the need of having to use SQL statements because it translates methods dynamically to the respective database engine operations.

模型是数据库的高级抽象层。如果您想进行低层次的数据库操作,您可以查看 Phalcon\Db 组件文档。

Models are intended to work on a database high layer of abstraction. If you need to work with databases at a lower level check out the Phalcon\Db component documentation.

创建模型Creating Models

模型是一个继承自 Phalcon\Mvc\Model 的一个类。 它必须放到 models 文件夹。一个模型文件必须包含一个类, 同时它的类名必须符合驼峰命名法:

A model is a class that extends from Phalcon\Mvc\Model. 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\Model;
  3. class Robots extends Model
  4. {
  5. }

上面的例子显示了 “Robots” 模型的实现。 需要注意的是 Robots 继承自 Phalcon\Mvc\Model 。 因此,Robots 模型拥有了大量继承自该组件功能,包括基本的数据库 CRUD (Create, Read, Update, Delete) 操作,数据验证以及复杂的搜索支持,并且可以同时关联多个模型。

The above example shows the implementation of the “Robots” model. Note that the class Robots inherits from Phalcon\Mvc\Model. This component provides a great deal of functionality to models that inherit it, including basic database CRUD (Create, Read, Update, Delete) operations, data validation, as well as sophisticated search support and the ability to relate multiple models with each other.

如果使用 PHP 5.4/5.5 建议在模型中预先定义好所有的列,这样可以减少模型内存的开销以及内存分配。

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

默认情况下,模型 “Robots” 对应的是数据库表 “robots”, 如果想映射到其他数据库表,可以使用 getSource() 方法:

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

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

模型 Robots 现在映射到了 “the_robots” 表。initialize() 方法可以帮助在模型中建立自定义行为,例如指定不同的数据库表。 initialize() 方法在请求期间只被调用一次。

The model Robots now maps to “the_robots” table. The initialize() method aids in setting up the model with a custom behavior i.e. a different table. The initialize() method is only called once during the request.

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

initialize() 方法在请求期间仅会被调用一次,目的是为应用中所有该模型的实例进行初始化。如果需要为每一个实例在创建的时候单独进行初始化, 可以使用 ‘onConstruct’ 事件:

The initialize() method is only called once during the request, it’s intended to perform initializations that apply for all instances of the model created within the application. If you want to perform initialization tasks for every instance created you can ‘onConstruct’:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public function onConstruct()
  6. {
  7. //...
  8. }
  9. }

公共属性对比设置与取值 Setters/GettersPublic properties vs. Setters/Getters

模型可以通过公共属性的方式实现,意味着模型的所有属性在实例化该模型的地方可以无限制的读取和更新。

Models can be implemented with properties of public scope, meaning that each property can be read/updated from any part of the code that has instantiated that model class without any restrictions:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public $price;
  8. }

通过使用 getters/setters 方法,可以控制哪些属性可以公开访问,并且对属性值执行不同的形式的转换,同时可以保存在模型中的数据添加相应的验证规则。

By using getters and setters you can control which properties are visible publicly perform various transformations to the data (which would be impossible otherwise) and also add validation rules to the data stored in the object:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. protected $id;
  6. protected $name;
  7. protected $price;
  8. public function getId()
  9. {
  10. return $this->id;
  11. }
  12. public function setName($name)
  13. {
  14. //The name is too short?
  15. if (strlen($name) < 10) {
  16. throw new \InvalidArgumentException('The name is too short');
  17. }
  18. $this->name = $name;
  19. }
  20. public function getName()
  21. {
  22. return $this->name;
  23. }
  24. public function setPrice($price)
  25. {
  26. //Negative prices aren't allowed
  27. if ($price < 0) {
  28. throw new \InvalidArgumentException('Price can\'t be negative');
  29. }
  30. $this->price = $price;
  31. }
  32. public function getPrice()
  33. {
  34. //Convert the value to double before be used
  35. return (double) $this->price;
  36. }
  37. }

公共属性的方式可以在开发中降低复杂度。而 getters/setters 的实现方式可以显著的增强应用的可测试性、扩展性和可维护性。 开发人员可以自己决定哪一种策略更加适合自己开发的应用。ORM同时兼容这两种方法。

Public properties provide less complexity in development. However getters/setters can heavily increase the testability, extensibility and maintainability of applications. Developers can decide which strategy is more appropriate for the application they are creating. The ORM is compatible with both schemes of defining properties.

模型放入命名空间Models in Namespaces

命名空间可以用来避免类名的冲突。ORM通过类名来映射相应的表名。比如 ‘Robots’:

Namespaces can be used to avoid class name collision. The mapped table is taken from the class name, in this case ‘Robots’:

  1. <?php
  2. namespace Store\Toys;
  3. use Phalcon\Mvc\Model;
  4. class Robots extends Model
  5. {
  6. }

理解记录对象Understanding Records To Objects

每个模型的实例对应一条数据表中的记录。可以方便的通过读取对象的属性来访问相应的数据。比如, 一个表 “robots” 有如下数据:

Every instance of a model represents a row in the table. You can easily access record data by reading object properties. For example, for a table “robots” with the records:

  1. mysql> select * from robots;
  2. +----+------------+------------+------+
  3. | id | name | type | year |
  4. +----+------------+------------+------+
  5. | 1 | Robotina | mechanical | 1972 |
  6. | 2 | Astro Boy | mechanical | 1952 |
  7. | 3 | Terminator | cyborg | 2029 |
  8. +----+------------+------------+------+
  9. 3 rows in set (0.00 sec)

你可以通过主键找到某一条记录并且打印它的名称:

You could find a certain record by its primary key and then print its name:

  1. <?php
  2. // Find record with id = 3
  3. $robot = Robots::findFirst(3);
  4. // Prints "Terminator"
  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(3);
  3. $robot->name = "RoboCop";
  4. $robot->save();

如上所示,不需要写任何SQL语句。 Phalcon\Mvc\Model 为web应用提供了高层数据库抽象。

As you can see, there is no need to use raw SQL statements. Phalcon\Mvc\Model provides high database abstraction for web applications.

查找记录Finding Records

Phalcon\Mvc\Model 为数据查询提供了多种方法。下面的例子将演示如何从一个模型中查找一条或者多条记录:

Phalcon\Mvc\Model also offers several methods for querying records. The following examples will show you how to query one or more records from a model:

  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("type = 'mechanical'");
  7. echo "There are ", count($robots), "\n";
  8. // Get and print virtual robots ordered by name
  9. $robots = Robots::find(array(
  10. "type = 'virtual'",
  11. "order" => "name"
  12. ));
  13. foreach ($robots as $robot) {
  14. echo $robot->name, "\n";
  15. }
  16. // Get first 100 virtual robots ordered by name
  17. $robots = Robots::find(array(
  18. "type = 'virtual'",
  19. "order" => "name",
  20. "limit" => 100
  21. ));
  22. foreach ($robots as $robot) {
  23. echo $robot->name, "\n";
  24. }

如果想通过额外的数据(用户输入数据)查询记录必须要使用`Binding Parameters`_。

If you want find record by external data (such as user input) or variable data you must use `Binding Parameters`_.

你可以使用 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 table?
  3. $robot = Robots::findFirst();
  4. echo "The robot name is ", $robot->name, "\n";
  5. // What's the first mechanical robot in robots table?
  6. $robot = Robots::findFirst("type = 'mechanical'");
  7. echo "The first mechanical robot name is ", $robot->name, "\n";
  8. // Get first virtual robot ordered by name
  9. $robot = Robots::findFirst(array("type = 'virtual'", "order" => "name"));
  10. echo "The first virtual robot name is ", $robot->name, "\n";

find() 和 findFirst() 方法都接受关联数组作为查询条件:

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

  1. <?php
  2. $robot = Robots::findFirst(array(
  3. "type = 'virtual'",
  4. "order" => "name DESC",
  5. "limit" => 30
  6. ));
  7. $robots = Robots::find(array(
  8. "conditions" => "type = ?1",
  9. "bind" => array(1 => "virtual")
  10. ));

可用的查询选项如下

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\Mvc\Model assumes the first parameter are the conditions.“conditions” => “name LIKE ‘steve%’”
columnsReturn specific columns instead of the full columns in the model. When using this option an incomplete object is returned“columns” => “id, name”
bindBind is used together with options, by replacing placeholders and escaping values thus increasing security“bind” => array(“status” => “A”, “type” => “some-time”)
bindTypesWhen binding parameters, you can use this parameter to define additional casting to the bound parameters increasing even more the security“bindTypes” => array(Column::BIND_PARAM_STR, Column::BIND_PARAM_INT)
orderIs used to sort the resultset. Use one or more fields separated by commas.“order” => “name DESC, status”
limitLimit the results of the query to results to certain range“limit” => 10 / “limit” => array(“number” => 10, “offset” => 5)
groupAllows to collect data across multiple records and group the results by one or more columns“group” => “name, status”
for_updateWith this option, Phalcon\Mvc\Model reads the latest available data, setting exclusive locks on each row it reads“for_update” => true
shared_lockWith this option, Phalcon\Mvc\Model reads the latest available data, setting shared locks on each row it reads“shared_lock” => true
cacheCache the resultset, reducing the continuous access to the relational system“cache” => array(“lifetime” => 3600, “key” => “my-find-key”)
hydrationSets the hydration strategy to represent each returned record in the result“hydration” => Resultset::HYDRATE_OBJECTS

如果你愿意,除了使用数组作为查询参数外,还可以通过一种面向对象的方式来创建查询:

If you prefer, there is also available a way to create queries in an object-oriented way, instead of using an array of parameters:

  1. <?php
  2. $robots = Robots::query()
  3. ->where("type = :type:")
  4. ->andWhere("year < 2000")
  5. ->bind(array("type" => "mechanical"))
  6. ->order("name")
  7. ->execute();

静态方法 query() 返回一个对IDE自动完成友好的 :doc:`Phalcon\Mvc\Model\Criteria <../api/Phalcon_Mvc_Model_Criteria>`对象。

The static method query() returns a Phalcon\Mvc\Model\Criteria object that is friendly with IDE autocompleters.

所有查询在内部都以 PHQL 查询的方式处理。PHQL是一个高层的、面向对象的类SQL语言。通过PHQL语言你可以使用更多的比如join其他模型、定义分组、添加聚集等特性。

All the queries are internally handled as PHQL queries. PHQL is a high-level, object-oriented and SQL-like language. This language provide you more features to perform queries like joining other models, define groupings, add aggregations etc.

最后,还有一个 findFirstBy<property-name>() 方法。这个方法扩展了前面提及的 “findFirst()” 方法。它允许您利用方法名中的属性名称,通过将要搜索的该字段的内容作为参数传给它,来快速从一个表执行检索操作。

Lastly, there is the findFirstBy<property-name>() method. This method expands on the “findFirst()” method mentioned earlier. It allows you to quickly perform a retrieval from a table by using the property name in the method itself and passing it a parameter that contains the data you want to search for in that column.

还是用上面用过的 Robots 模型来举例说明:

An example is in order, so taking our Robots model mentioned earlier :

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public $price;
  8. }

我们这里有3个属性:$id, $name 和 $price。因此,我们以想要查询第一个名称为 ‘Terminator’ 的记录为例,可以这样写:

We have three properties to work with here. $id, $name and $price. So, let’s say you want to retrieve the first record in the table with the name ‘Terminator’. This could be written like so :

  1. <?php
  2. $name = "Terminator";
  3. $robot = Robots::findFirstByName($name);
  4. if($robot){
  5. $this->flash->success("The first robot with the name " . $name . " cost " . $robot->price ".");
  6. }else{
  7. $this->flash->error("There were no robots found in our table with the name " . $name ".");
  8. }

请注意我们在方法调用中用的是 ‘Name’,并向它传递了变量 $name,$name 的值就是我们想要找的记录的名称。另外注意,当我们的查询找到了符合的记录后,这个记录的其他属性也都是可用的。

Notice that we used ‘Name’ in the method call and passed the variable $name to it, which contains the name we are looking for in our table. Notice also that when we find a match with our query, all the other properties are available to us as well.

模型结果集Model Resultsets

findFirst() 方法直接返回一个被调用对象的实例(如果有结果返回的话),而 find() 方法返回一个 Phalcon\Mvc\Model\Resultset\Simple 对象。这个对象也封装进了所有结果集的功能,比如遍历、查找特定的记录、统计等等。

While findFirst() returns directly an instance of the called class (when there is data to be returned), the find() method returns a Phalcon\Mvc\Model\Resultset\Simple. This is an object that encapsulates all the functionality a resultset has like traversing, seeking specific records, counting, etc.

这些对象比一般数组功能更强大。最大的特点是 Phalcon\Mvc\Model\Resultset 每时每刻只有一个结果在内存中。这对操作大数据量时的内存管理相当有帮助。

These objects are more powerful than standard arrays. One of the greatest features of the Phalcon\Mvc\Model\Resultset is that at any time there is only one record in memory. This greatly helps in memory management especially when working with large amounts of data.

  1. <?php
  2. // Get all robots
  3. $robots = Robots::find();
  4. // Traversing with a foreach
  5. foreach ($robots as $robot) {
  6. echo $robot->name, "\n";
  7. }
  8. // Traversing with a while
  9. $robots->rewind();
  10. while ($robots->valid()) {
  11. $robot = $robots->current();
  12. echo $robot->name, "\n";
  13. $robots->next();
  14. }
  15. // Count the resultset
  16. echo count($robots);
  17. // Alternative way to count the resultset
  18. echo $robots->count();
  19. // Move the internal cursor to the third robot
  20. $robots->seek(2);
  21. $robot = $robots->current();
  22. // Access a robot by its position in the resultset
  23. $robot = $robots[5];
  24. // Check if there is a record in certain position
  25. if (isset($robots[3])) {
  26. $robot = $robots[3];
  27. }
  28. // Get the first record in the resultset
  29. $robot = $robots->getFirst();
  30. // Get the last record
  31. $robot = $robots->getLast();

Phalcon 的结果集模拟了可滚动的游标,你可以通过位置,或者内部指针去访问任何一条特定的记录。注意有一些数据库系统不支持滚动游标,这就使得查询会被重复执行, 以便回放光标到最开始的位置,然后获得相应的记录。类似地,如果多次遍历结果集,那么必须执行相同的查询次数。

Phalcon’s resultsets emulate scrollable cursors, you can get any row just by accessing its position, or seeking the internal pointer to a specific position. Note that some database systems don’t support scrollable cursors, this forces to re-execute the query in order to rewind the cursor to the beginning and obtain the record at the requested position. Similarly, if a resultset is traversed several times, the query must be executed the same number of times.

将大数据量的查询结果存储在内存会消耗很多资源,正因为如此,分成每32行一块从数据库中获得结果集,以减少重复执行查询请求的次数,在一些情况下也节省内存。

Storing large query results in memory could consume many resources, because of this, resultsets are obtained from the database in chunks of 32 rows reducing the need for re-execute the request in several cases also saving memory.

注意结果集可以序列化后保存在一个后端缓存里面。 Phalcon\Cache 将从数据库检索到的所有数据以一个数组的方式保存,因此在这样执行的地方会消耗更多的内存。

Note that resultsets can be serialized and stored in a cache backend. Phalcon\Cache can help with that task. However, serializing data causes Phalcon\Mvc\Model to retrieve all the data from the database in an array, thus consuming more memory while this process takes place.

  1. <?php
  2. // Query all records from model parts
  3. $parts = Parts::find();
  4. // Store the resultset into a file
  5. file_put_contents("cache.txt", serialize($parts));
  6. // Get parts from file
  7. $parts = unserialize(file_get_contents("cache.txt"));
  8. // Traverse the parts
  9. foreach ($parts as $part) {
  10. echo $part->id;
  11. }

过滤结果集Filtering Resultsets

过滤数据最有效的方法是设置一些查询条件,数据库会利用表的索引快速返回数据。Phalcon 额外的允许你通过任何数据库不支持的方式过滤数据。

The most efficient way to filter data is setting some search criteria, databases will use indexes set on tables to return data faster. Phalcon additionally allows you to filter the data using PHP using any resource that is not available in the database:

  1. <?php
  2. $customers = Customers::find()->filter(function($customer) {
  3. //Return only customers with a valid e-mail
  4. if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
  5. return $customer;
  6. }
  7. });

绑定参数Binding Parameters

Phalcon\Mvc\Model 中也支持绑定参数。即使使用绑定参数对性能有一点很小的影响,还是强烈建议您使用这种方法,以消除代码受SQL注入攻击的可能性。 绑定参数支持字符串和整数占位符。实现方法如下:

Bound parameters are also supported in Phalcon\Mvc\Model. Although there is a minimal performance impact by using bound parameters, you are encouraged to use this methodology so as to eliminate the possibility of your code being subject to SQL injection attacks. Both string and integer placeholders are supported. Binding parameters can simply be achieved as follows:

  1. <?php
  2. // Query robots binding parameters with string placeholders
  3. $conditions = "name = :name: AND type = :type:";
  4. //Parameters whose keys are the same as placeholders
  5. $parameters = array(
  6. "name" => "Robotina",
  7. "type" => "maid"
  8. );
  9. //Perform the query
  10. $robots = Robots::find(array(
  11. $conditions,
  12. "bind" => $parameters
  13. ));
  14. // Query robots binding parameters with integer placeholders
  15. $conditions = "name = ?1 AND type = ?2";
  16. $parameters = array(1 => "Robotina", 2 => "maid");
  17. $robots = Robots::find(array(
  18. $conditions,
  19. "bind" => $parameters
  20. ));
  21. // Query robots binding parameters with both string and integer placeholders
  22. $conditions = "name = :name: AND type = ?1";
  23. //Parameters whose keys are the same as placeholders
  24. $parameters = array(
  25. "name" => "Robotina",
  26. 1 => "maid"
  27. );
  28. //Perform the query
  29. $robots = Robots::find(array(
  30. $conditions,
  31. "bind" => $parameters
  32. ));

当使用数字占位符,您需要定义它们为整数即1或2。使用“1”或“2”被认为是字符串而不是数字,所以占位符不能成功地替换了。

When using numeric placeholders, you will need to define them as integers i.e. 1 or 2. In this case “1” or “2” are considered strings and not numbers, so the placeholder could not be successfully replaced.

使用PDO_字符串会被自动转义。这个函数考虑了连接字符集,因此推荐在数据库中的连接参数或配置中定义正确的字符集,定义一个错误的字符集将会在存储或检索数据产生错误的结果。

Strings are automatically escaped using PDO. This function takes into account the connection charset, so its recommended to define the correct charset in the connection parameters or in the database configuration, as a wrong charset will produce undesired effects when storing or retrieving data.

另外你可以设置参数“bindTypes”,这个可以根据参数类型进行绑定:

Additionally you can set the parameter “bindTypes”, this allows defining how the parameters should be bound according to its data type:

  1. <?php
  2. use Phalcon\Db\Column;
  3. //Bind parameters
  4. $parameters = array(
  5. "name" => "Robotina",
  6. "year" => 2008
  7. );
  8. //Casting Types
  9. $types = array(
  10. "name" => Column::BIND_PARAM_STR,
  11. "year" => Column::BIND_PARAM_INT
  12. );
  13. // Query robots binding parameters with string placeholders
  14. $robots = Robots::find(array(
  15. "name = :name: AND year = :year:",
  16. "bind" => $parameters,
  17. "bindTypes" => $types
  18. ));

因为默认绑定类型为\Phalcon\Db\Column::BIND_PARAM_STR,所以如果所有列的类型都是字符就不需要再指定“bindTypes”参数。

Since the default bind-type is \Phalcon\Db\Column::BIND_PARAM_STR, there is no need to specify the “bindTypes” parameter if all of the columns are of that type.

绑定参数可用于所有查询方法,如find(),findFirst(),count(),sum(),average()等。

Bound parameters are available for all query methods such as find() and findFirst() but also the calculation methods like count(), sum(), average() etc.

初始化/准备获取记录Initializing/Preparing fetched records

普通的流程是从数据库获取数据并处理后供程序后续使用。但是我们也可以在数据模型中实现’afterFetch’这个方法,它将在实例执行后立刻被调用被分配相应的数据:

May be the case that after obtaining a record from the database is necessary to initialise the data before being used by the rest of the application. You can implement the method ‘afterFetch’ in a model, this event will be executed just after create the instance and assign the data to it:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public $status;
  8. public function beforeSave()
  9. {
  10. //Convert the array into a string
  11. $this->status = join(',', $this->status);
  12. }
  13. public function afterFetch()
  14. {
  15. //Convert the string to an array
  16. $this->status = explode(',', $this->status);
  17. }
  18. }

如果公共属性和getter/setter一块使用,当访问这些属性的时候就可以完成初始化:

If you use getters/setters instead of/or together with public properties, you can initialize the field once it is accessed:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public $status;
  8. public function getStatus()
  9. {
  10. return explode(',', $this->status);
  11. }
  12. }

模型关系Relationships between Models

有四种类型的关系:一对一,一对多,多对一和多对多。关系可能是单向或双向,每个可以是简单的(一个对一个模型)或更复杂的(模型)的组合。模型管理器管理这些关系的外键约束,这些定义保证了引用的完整性,让我们容易和快速访问一个模型的相关记录。通过关系的实现, 很容易以统一的方式从相互关联的模型中访问每一条记录。

There are four types of relationships: one-on-one, one-to-many, many-to-one and many-to-many. The relationship may be unidirectional or bidirectional, and each can be simple (a one to one model) or more complex (a combination of models). The model manager manages foreign key constraints for these relationships, the definition of these helps referential integrity as well as easy and fast access of related records to a model. Through the implementation of relations, it is easy to access data in related models from each record in a uniform way.

单项关系Unidirectional relationships

Unidirectional relations are those that are generated in relation to one another but not vice versa.

双向关系Bidirectional relations

The bidirectional relations build relationships in both models and each model defines the inverse relationship of the other.

定义关系Defining relationships

在Phalcon中关系必须在模型的initialize()中定义。belongsTo(),hasOne(),hasMany()和hasManyToMany()定义了从当前模型中一个或多个字段之间的关系到另一个模型中。上述每种方法都需要三个参数:本地字段,引用模型,引用字段。

In Phalcon, relationships must be defined in the initialize() method of a model. The methods belongsTo(), hasOne(), hasMany() and hasManyToMany() define the relationship between one or more fields from the current model to fields in another model. Each of these methods requires 3 parameters: local fields, referenced model, referenced fields.

MethodDescription
hasManyDefines a 1-n relationship
hasOneDefines a 1-1 relationship
belongsToDefines a n-1 relationship
hasManyToManyDefines a n-n relationship

下面是作为我们演示关系的例子的三个表:

The following schema shows 3 tables whose relations will serve us as an example regarding relationships:

  1. CREATE TABLE `robots` (
  2. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  3. `name` varchar(70) NOT NULL,
  4. `type` varchar(32) NOT NULL,
  5. `year` int(11) NOT NULL,
  6. PRIMARY KEY (`id`)
  7. );
  8. CREATE TABLE `robots_parts` (
  9. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  10. `robots_id` int(10) NOT NULL,
  11. `parts_id` int(10) NOT NULL,
  12. `created_at` DATE NOT NULL,
  13. PRIMARY KEY (`id`),
  14. KEY `robots_id` (`robots_id`),
  15. KEY `parts_id` (`parts_id`)
  16. );
  17. CREATE TABLE `parts` (
  18. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  19. `name` varchar(70) NOT NULL,
  20. PRIMARY KEY (`id`)
  21. );
  • The model “Robots” has many “RobotsParts”.
  • The model “Parts” has many “RobotsParts”.
  • The model “RobotsParts” belongs to both “Robots” and “Parts” models as a many-to-one relation.
  • The model “Robots” has a relation many-to-many to “Parts” through “RobotsParts”

看下面的EER图更加直观:

Check the EER diagram to understand better the relations:

../_images/eer-1.png

模型和之间的关系实现如下:

The models with their relations could be implemented as follows:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public function initialize()
  8. {
  9. $this->hasMany("id", "RobotsParts", "robots_id");
  10. }
  11. }
  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Parts extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public function initialize()
  8. {
  9. $this->hasMany("id", "RobotsParts", "parts_id");
  10. }
  11. }
  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class RobotsParts extends Model
  4. {
  5. public $id;
  6. public $robots_id;
  7. public $parts_id;
  8. public function initialize()
  9. {
  10. $this->belongsTo("robots_id", "Robots", "id");
  11. $this->belongsTo("parts_id", "Parts", "id");
  12. }
  13. }

第一个参数表示本地模型中字段关系;第二个参数表示引用模型的名称,第三个参数是引用模型的字段名。你也可以使用数组来定义多个字段的关系。

The first parameter indicates the field of the local model used in the relationship; the second indicates the name of the referenced model and the third the field name in the referenced model. You could also use arrays to define multiple fields in the relationship.

多对多的关系需要3模型并且定义关系中涉及到的属性:

Many to many relationships require 3 models and define the attributes involved in the relationship:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public function initialize()
  8. {
  9. $this->hasManyToMany(
  10. "id",
  11. "RobotsParts",
  12. "robots_id", "parts_id",
  13. "Parts",
  14. "id"
  15. );
  16. }
  17. }

使用关系Taking advantage of relationships

当显式地定义模型之间的关系,它很容易为一个特定的记录找到相关的记录。

When explicitly defining the relationships between models, it is easy to find related records for a particular record.

  1. <?php
  2. $robot = Robots::findFirst(2);
  3. foreach ($robot->robotsParts as $robotPart) {
  4. echo $robotPart->parts->name, "\n";
  5. }

Phalcon使用魔术方法__set/__get /__call存储或检索使用了关系的相关数据。

Phalcon uses the magic methods __set/__get/__call to store or retrieve related data using relationships.

通过访问一个名称相同的关系中的属性将检索所有相关记录。

By accessing an attribute with the same name as the relationship will retrieve all its related record(s).

  1. <?php
  2. $robot = Robots::findFirst();
  3. $robotsParts = $robot->robotsParts; // all the related records in RobotsParts

同样可以使用getter

Also, you can use a magic getter:

  1. <?php
  2. $robot = Robots::findFirst();
  3. $robotsParts = $robot->getRobotsParts(); // all the related records in RobotsParts
  4. $robotsParts = $robot->getRobotsParts(array('limit' => 5)); // passing parameters

如果调用一个有get前缀的方法:doc:Phalcon\Mvc\Model <../api/Phalcon_Mvc_Model> 会返回一个findFirst()/find() 结果,下面的示例对比了使用魔术方法和不使用去获取数据:

If the called method has a “get” prefix Phalcon\Mvc\Model will return a findFirst()/find() result. The following example compares retrieving related results with using magic methods and without:

  1. <?php
  2. $robot = Robots::findFirst(2);
  3. // Robots model has a 1-n (hasMany)
  4. // relationship to RobotsParts then
  5. $robotsParts = $robot->robotsParts;
  6. // Only parts that match conditions
  7. $robotsParts = $robot->getRobotsParts("created_at = '2012-03-15'");
  8. // Or using bound parameters
  9. $robotsParts = $robot->getRobotsParts(array(
  10. "created_at = :date:",
  11. "bind" => array("date" => "2012-03-15")
  12. ));
  13. $robotPart = RobotsParts::findFirst(1);
  14. // RobotsParts model has a n-1 (belongsTo)
  15. // relationship to RobotsParts then
  16. $robot = $robotPart->robots;

手动获取相关记录

Getting related records manually:

  1. <?php
  2. $robot = Robots::findFirst(2);
  3. // Robots model has a 1-n (hasMany)
  4. // relationship to RobotsParts, then
  5. $robotsParts = RobotsParts::find("robots_id = '" . $robot->id . "'");
  6. // Only parts that match conditions
  7. $robotsParts = RobotsParts::find(
  8. "robots_id = '" . $robot->id . "' AND created_at = '2012-03-15'"
  9. );
  10. $robotPart = RobotsParts::findFirst(1);
  11. // RobotsParts model has a n-1 (belongsTo)
  12. // relationship to RobotsParts then
  13. $robot = Robots::findFirst("id = '" . $robotPart->robots_id . "'");

前缀用于find()/findFirst()相关记录。根据关系类型决定使用find还是findFirst

The prefix “get” is used to find()/findFirst() related records. Depending on the type of relation it will use ‘find’ or ‘findFirst’:

TypeDescriptionImplicit Method
Belongs-ToReturns a model instance of the related record directlyfindFirst
Has-OneReturns a model instance of the related record directlyfindFirst
Has-ManyReturns a collection of model instances of the referenced modelfind
Has-Many-to-ManyReturns a collection of model instances of the referenced model, it implicitly does ‘inner joins’ with the involved models(complex query)

还可以使用“count”前缀返回一个表示相关记录的统计整数:

You can also use “count” prefix to return an integer denoting the count of the related records:

  1. <?php
  2. $robot = Robots::findFirst(2);
  3. echo "The robot has ", $robot->countRobotsParts(), " parts\n";

关系别名Aliasing Relationships

为了解释别名如何使用,让我们看下面例子:

To explain better how aliases work, let’s check the following example:

表“robots_similar”定义了类似机器人的信息:

Table “robots_similar” has the function to define what robots are similar to others:

  1. mysql> desc robots_similar;
  2. +-------------------+------------------+------+-----+---------+----------------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +-------------------+------------------+------+-----+---------+----------------+
  5. | id | int(10) unsigned | NO | PRI | NULL | auto_increment |
  6. | robots_id | int(10) unsigned | NO | MUL | NULL | |
  7. | similar_robots_id | int(10) unsigned | NO | | NULL | |
  8. +-------------------+------------------+------+-----+---------+----------------+
  9. 3 rows in set (0.00 sec)

robots_id表和similar_robots_id表都和Robots模型有关系:

Both “robots_id” and “similar_robots_id” have a relation to the model Robots:

../_images/eer-2.png

定义他们之前关系的数据模型如下所示:

A model that maps this table and its relationships is the following:

  1. <?php
  2. class RobotsSimilar extends Phalcon\Mvc\Model
  3. {
  4. public function initialize()
  5. {
  6. $this->belongsTo('robots_id', 'Robots', 'id');
  7. $this->belongsTo('similar_robots_id', 'Robots', 'id');
  8. }
  9. }

因为他们的关系都指向了同一个数据模型(Robots),获取关联数据就会不清楚:

Since both relations point to the same model (Robots), obtain the records related to the relationship could not be clear:

  1. <?php
  2. $robotsSimilar = RobotsSimilar::findFirst();
  3. //Returns the related record based on the column (robots_id)
  4. //Also as is a belongsTo it's only returning one record
  5. //but the name 'getRobots' seems to imply that return more than one
  6. $robot = $robotsSimilar->getRobots();
  7. //but, how to get the related record based on the column (similar_robots_id)
  8. //if both relationships have the same name?

可以使用别名重命名关系,这样就解决了这个问题:

The aliases allow us to rename both relationships to solve these problems:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class RobotsSimilar extends Model
  4. {
  5. public function initialize()
  6. {
  7. $this->belongsTo('robots_id', 'Robots', 'id', array(
  8. 'alias' => 'Robot'
  9. ));
  10. $this->belongsTo('similar_robots_id', 'Robots', 'id', array(
  11. 'alias' => 'SimilarRobot'
  12. ));
  13. }
  14. }

使用别名可以轻松的获取相关数据:

With the aliasing we can get the related records easily:

  1. <?php
  2. $robotsSimilar = RobotsSimilar::findFirst();
  3. //Returns the related record based on the column (robots_id)
  4. $robot = $robotsSimilar->getRobot();
  5. $robot = $robotsSimilar->robot;
  6. //Returns the related record based on the column (similar_robots_id)
  7. $similarRobot = $robotsSimilar->getSimilarRobot();
  8. $similarRobot = $robotsSimilar->similarRobot;

魔术方法 Getters 对比显示方法Magic Getters vs. Explicit methods

大多数IDE和编辑器有自动提示完成代码的功能,但是使用魔法getter方法时不能推断出正确的类型, 而不是使用魔法的getter方法我们可以显式地定义这些方法和相应的docblocks帮助IDE生成更好的自动提示完成代码:

Most IDEs and editors with auto-completion capabilities can not infer the correct types when using magic getters, instead of use the magic getters you can optionally define those methods explicitly with the corresponding docblocks helping the IDE to produce a better auto-completion:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public $id;
  6. public $name;
  7. public function initialize()
  8. {
  9. $this->hasMany("id", "RobotsParts", "robots_id");
  10. }
  11. /**
  12. * Return the related "robots parts"
  13. *
  14. * @return \RobotsParts[]
  15. */
  16. public function getRobotsParts($parameters=null)
  17. {
  18. return $this->getRelated('RobotsParts', $parameters);
  19. }
  20. }

虚拟外键Virtual Foreign Keys

默认情况下,不像数据库外键的关系那样,如果插入/更新一个无效值到相关模型中,Phalcon不会产生一个验证消息。在定义一个关系的时候可以通过添加第四个参数修改这个行为。

By default, relationships do not act like database foreign keys, that is, if you try to insert/update a value without having a valid value in the referenced model, Phalcon will not produce a validation message. You can modify this behavior by adding a fourth parameter when defining a relationship.

修改下RobotsPart模型来演示这个功能:

The RobotsPart model can be changed to demonstrate this feature:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class RobotsParts extends Model
  4. {
  5. public $id;
  6. public $robots_id;
  7. public $parts_id;
  8. public function initialize()
  9. {
  10. $this->belongsTo("robots_id", "Robots", "id", array(
  11. "foreignKey" => true
  12. ));
  13. $this->belongsTo("parts_id", "Parts", "id", array(
  14. "foreignKey" => array(
  15. "message" => "The part_id does not exist on the Parts model"
  16. )
  17. ));
  18. }
  19. }

如果改变一个belongsTo()作为外键的关系,在进行插入/更新相关模型操作时它将验证这些字段值是否有效。类似地,如果一个hasMany()/hasOne()被加了外键约束,它将验证记录不能被删除,如果记录还在存在一个相关模型中。

If you alter a belongsTo() relationship to act as foreign key, it will validate that the values inserted/updated on those fields have a valid value on the referenced model. Similarly, if a hasMany()/hasOne() is altered it will validate that the records cannot be deleted if that record is used on a referenced model.

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Parts extends Model
  4. {
  5. public function initialize()
  6. {
  7. $this->hasMany("id", "RobotsParts", "parts_id", array(
  8. "foreignKey" => array(
  9. "message" => "The part cannot be deleted because other robots are using it"
  10. )
  11. ));
  12. }
  13. }

级联与限制动作Cascade/Restrict actions

默认关系作为虚拟外键限制创建/更新/删除记录来保持数据的完整性:

Relationships that act as virtual foreign keys by default restrict the creation/update/deletion of records to maintain the integrity of data:

  1. <?php
  2. namespace Store\Models;
  3. use Phalcon\Mvc\Model;
  4. use Phalcon\Mvc\Model\Relation;
  5. class Robots extends Model
  6. {
  7. public $id;
  8. public $name;
  9. public function initialize()
  10. {
  11. $this->hasMany('id', 'Store\\Models\Parts', 'robots_id', array(
  12. 'foreignKey' => array(
  13. 'action' => Relation::ACTION_CASCADE
  14. )
  15. ));
  16. }
  17. }

上面的代码设置如果主记录(robot)被删除则删除所有被引用的记录(parts)。

The above code set up to delete all the referenced records (parts) if the master record (robot) is deleted.

生成运算Generating Calculations

Phalcon\Mvc\Model 计算或者统计函数像是 COUNT, SUM, MAX, MIN or AVG可以直接使用。

Calculations (or aggregations) are helpers for commonly used functions of database systems such as COUNT, SUM, MAX, MIN or AVG. Phalcon\Mvc\Model allows to use these functions directly from the exposed methods.

Count examples:

  1. <?php
  2. // How many employees are?
  3. $rowcount = Employees::count();
  4. // How many different areas are assigned to employees?
  5. $rowcount = Employees::count(array("distinct" => "area"));
  6. // How many employees are in the Testing area?
  7. $rowcount = Employees::count("area = 'Testing'");
  8. // Count employees grouping results by their area
  9. $group = Employees::count(array("group" => "area"));
  10. foreach ($group as $row) {
  11. echo "There are ", $row->rowcount, " in ", $row->area;
  12. }
  13. // Count employees grouping by their area and ordering the result by count
  14. $group = Employees::count(array(
  15. "group" => "area",
  16. "order" => "rowcount"
  17. ));
  18. // Avoid SQL injections using bound parameters
  19. $group = Employees::count(array(
  20. "type > ?0",
  21. "bind" => array($type)
  22. ));

求和例子:

Sum examples:

  1. <?php
  2. // How much are the salaries of all employees?
  3. $total = Employees::sum(array("column" => "salary"));
  4. // How much are the salaries of all employees in the Sales area?
  5. $total = Employees::sum(array(
  6. "column" => "salary",
  7. "conditions" => "area = 'Sales'"
  8. ));
  9. // Generate a grouping of the salaries of each area
  10. $group = Employees::sum(array(
  11. "column" => "salary",
  12. "group" => "area"
  13. ));
  14. foreach ($group as $row) {
  15. echo "The sum of salaries of the ", $row->area, " is ", $row->sumatory;
  16. }
  17. // Generate a grouping of the salaries of each area ordering
  18. // salaries from higher to lower
  19. $group = Employees::sum(array(
  20. "column" => "salary",
  21. "group" => "area",
  22. "order" => "sumatory DESC"
  23. ));
  24. // Avoid SQL injections using bound parameters
  25. $group = Employees::sum(array(
  26. "conditions" => "area > ?0",
  27. "bind" => array($area)
  28. ));

求平均例子:

Average examples:

  1. <?php
  2. // What is the average salary for all employees?
  3. $average = Employees::average(array("column" => "salary"));
  4. // What is the average salary for the Sales's area employees?
  5. $average = Employees::average(array(
  6. "column" => "salary",
  7. "conditions" => "area = 'Sales'"
  8. ));
  9. // Avoid SQL injections using bound parameters
  10. $average = Employees::average(array(
  11. "column" => "age",
  12. "conditions" => "area > ?0",
  13. "bind" => array($area)
  14. ));

最大/最小例子:

Max/Min examples:

  1. <?php
  2. // What is the oldest age of all employees?
  3. $age = Employees::maximum(array("column" => "age"));
  4. // What is the oldest of employees from the Sales area?
  5. $age = Employees::maximum(array(
  6. "column" => "age",
  7. "conditions" => "area = 'Sales'"
  8. ));
  9. // What is the lowest salary of all employees?
  10. $salary = Employees::minimum(array("column" => "salary"));

Hydration模式Hydration Modes

正如上面提到的,结果集是一个完整的集合对象,这意味着每一个返回的结果是一个对象,它代表数据库中的一行。这些对象可以被修改并再次持久保存在数据库中:

As mentioned above, resultsets are collections of complete objects, this means that every returned result is an object representing a row in the database. These objects can be modified and saved again to persistence:

  1. <?php
  2. // Manipulating a resultset of complete objects
  3. foreach (Robots::find() as $robot) {
  4. $robot->year = 2000;
  5. $robot->save();
  6. }

有时获取的数据只需要以只读模式呈现给用户,在这种情况下变更记录的呈现方式可以解决这个问题。这种变更对象在结果集中的呈现方式的方法被叫做“Hydration模式”:

Sometimes records are obtained only to be presented to a user in read-only mode, in these cases it may be useful to change the way in which records are represented to facilitate their handling. The strategy used to represent objects returned in a resultset is called ‘hydration mode’:

  1. <?php
  2. use Phalcon\Mvc\Model\Resultset;
  3. $robots = Robots::find();
  4. //Return every robot as an array
  5. $robots->setHydrateMode(Resultset::HYDRATE_ARRAYS);
  6. foreach ($robots as $robot) {
  7. echo $robot['year'], PHP_EOL;
  8. }
  9. //Return every robot as an stdClass
  10. $robots->setHydrateMode(Resultset::HYDRATE_OBJECTS);
  11. foreach ($robots as $robot) {
  12. echo $robot->year, PHP_EOL;
  13. }
  14. //Return every robot as a Robots instance
  15. $robots->setHydrateMode(Resultset::HYDRATE_RECORDS);
  16. foreach ($robots as $robot) {
  17. echo $robot->year, PHP_EOL;
  18. }

Hydration模式也可以被作为参数传递给“find”:

Hydration mode can also be passed as a parameter of ‘find’:

  1. <?php
  2. use Phalcon\Mvc\Model\Resultset;
  3. $robots = Robots::find(array(
  4. 'hydration' => Resultset::HYDRATE_ARRAYS
  5. ));
  6. foreach ($robots as $robot) {
  7. echo $robot['year'], PHP_EOL;
  8. }

创建与更新记录Creating Updating/Records

Phalcon\Mvc\Model::save()方法允许您创建/更新记录根据表中是否已经存在相关的记录。内部调用save实现创建和更新:doc:Phalcon\Mvc\Model <../api/Phalcon_Mvc_Model>。为实现这个功能必须正确地为表定义一个主键来确定一个记录应该被更新还是创建。

The method Phalcon\Mvc\Model::save() allows you to create/update records according to whether they already exist in the table associated with a model. The save method is called internally by the create and update methods of Phalcon\Mvc\Model. For this to work as expected it is necessary to have properly defined a primary key in the entity to determine whether a record should be updated or created.

如果虚拟外键和事件在模型中定义,save也验证器相关联的方法:

Also the method executes associated validators, virtual foreign keys 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. }

数组可以直接被传递给save方法,从而避免为每一列单独赋值。 Phalcon\Mvc\Model将检查是否有实现setter列,如果有则优先考虑setter,而不使用指定数组的值:

An array could be passed to “save” to avoid assign every column manually. Phalcon\Mvc\Model will check if there are setters implemented for the columns passed in the array giving priority to them instead of assign directly the values of the attributes:

  1. <?php
  2. $robot = new Robots();
  3. $robot->save(array(
  4. "type" => "mechanical",
  5. "name" => "Astro Boy",
  6. "year" => 1952
  7. ));

数组传递的值会根据属性类型自动转义。所以你可以传递一个非法的数组,而不用担心可能的SQL注入:

Values assigned directly or via the array of attributes are escaped/sanitized according to the related attribute data type. So you can pass an insecure array without worrying about possible SQL injections:

  1. <?php
  2. $robot = new Robots();
  3. $robot->save($_POST);

没有预防措施,攻击者可能会设置数据库中任何列的值。如果你想允许用户插入/更新模型的每一列中,即使这些字段不在提交表单中,可以使用这种方式。

Without precautions mass assignment could allow attackers to set any database column’s value. Only use this feature if you want to permit a user to insert/update every column in the model, even if those fields are not in the submitted form.

当有大量参数赋值的时候,可以为save设置一个白名单参数,只有在参数中的值才会被保存:

You can set an additional parameter in ‘save’ to set a whitelist of fields that only must taken into account when doing the mass assignment:

  1. <?php
  2. $robot = new Robots();
  3. $robot->save($_POST, array('name', 'type'));

创建与更新结果判断Create/Update with Confidence

如果应用有许多操作,我们希望去创建一条记录而程序实际上却执行了更新,这种情况可能会在使用Phalcon\Mvc\Model::save()保存记录的时候产生。如果想彻底避免这种情况发生,可以使用create() 或者 update()替代save()方法:

When an application has a lot of competition, we could be expecting create a record but it is actually updated. This could happen if we use Phalcon\Mvc\Model::save() to persist the records in the database. If we want to be absolutely sure that a record is created or updated, we can change the save() call with create() or update():

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

create和update方法也接受数组作为参数。

These methods “create” and “update” also accept an array of values as parameter.

自动生成标识列Auto-generated identity columns

有些模型有标示列。这些列通常是映射表的主键。Phalcon\Mvc\Model 在产生SQL INSERT的时候可以自动忽略这些列,数据库系统会自动为其生成一个值。在每次新的记录被创建后,这些标示列会被数据库自动产生的值赋值:

Some models may have identity columns. These columns usually are the primary key of the mapped table. Phalcon\Mvc\Model can recognize the identity column omitting it in the generated SQL INSERT, so the database system can generate an auto-generated value for it. Always after creating a record, the identity field will be registered with the value generated in the database system for it:

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

Phalcon\Mvc\Model 能够识别标识列。根据使用的数据库不同,这些列可能是序列列(PostgreSQL)或者是auto_increment列(MySQL)。

Phalcon\Mvc\Model is able to recognize the identity column. Depending on the database system, those columns may be serial columns like in PostgreSQL or auto_increment columns in the case of MySQL.

PostgreSQL使用序列生成auto-numeric值,默认情况下,Phalcon试图获得从“table_field_seq”序列生成的价值, 例如:robots_id_seq,如果序列具有不同的名称,“getSequenceName”方法需要实现:

PostgreSQL uses sequences to generate auto-numeric values, by default, Phalcon tries to obtain the generated value from the sequence “table_field_seq”, for example: robots_id_seq, if that sequence has a different name, the method “getSequenceName” needs to be implemented:

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

存储相关记录Storing related records

魔法属性可以用来存储记录及其相关属性:

Magic properties can be used to store a records and its related properties:

  1. <?php
  2. // Create an artist
  3. $artist = new Artists();
  4. $artist->name = 'Shinichi Osawa';
  5. $artist->country = 'Japan';
  6. // Create an album
  7. $album = new Albums();
  8. $album->name = 'The One';
  9. $album->artist = $artist; //Assign the artist
  10. $album->year = 2008;
  11. //Save both records
  12. $album->save();

保存记录及其相关has-many记录:

Saving a record and its related records in a has-many relation:

  1. <?php
  2. // Get an existing artist
  3. $artist = Artists::findFirst('name = "Shinichi Osawa"');
  4. // Create an album
  5. $album = new Albums();
  6. $album->name = 'The One';
  7. $album->artist = $artist;
  8. $songs = array();
  9. // Create a first song
  10. $songs[0] = new Songs();
  11. $songs[0]->name = 'Star Guitar';
  12. $songs[0]->duration = '5:54';
  13. // Create a second song
  14. $songs[1] = new Songs();
  15. $songs[1]->name = 'Last Days';
  16. $songs[1]->duration = '4:29';
  17. // Assign the songs array
  18. $album->songs = $songs;
  19. // Save the album + its songs
  20. $album->save();

同时保存album和artist会触发事务操作处理,如果保存相关记录出错,主记录信息也不会被报错。有关任何错误信息都会返回给用户。

Saving the album and the artist at the same time implicitly makes use of a transaction so if anything goes wrong with saving the related records, the parent will not be saved either. Messages are passed back to the user for information regarding any errors.

注意:添加相关实体通过重载下面的方法是不可能的(重载下面的方法执行添加相关记录不会成功):

Note: Adding related entities by overloading the following methods is not possible:

  • PhalconMvcModel::beforeSave()
  • PhalconMvcModel::beforeCreate()
  • PhalconMvcModel::beforeUpdate()

需要重载PhalconMvcModel::save() 才能保存相关记录。

You need to overload PhalconMvcModel::save() for this to work from within a model.

验证信息Validation Messages

Phalcon\Mvc\Model 有个消息子系统,可以灵活的显示或者储存在执行insert/update流程中的验证信息。

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

每个消息包含一个 Phalcon\Mvc\Model\Message 类的实例。产生的消息可以通过getMessages()获得。每个消息提供了扩展字段名,消息内容,消息类型:

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. }

Phalcon\Mvc\Model 会产生以下的验证信息:

Phalcon\Mvc\Model can generate the following types of validation messages:

TypeDescription
PresenceOfGenerated when a field with a non-null attribute on the database is trying to insert/update a null value
ConstraintViolationGenerated when a field part of a virtual foreign key is trying to insert/update a value that doesn’t exist in the referenced model
InvalidValueGenerated when a validator failed because of an invalid value
InvalidCreateAttemptProduced when a record is attempted to be created but it already exists
InvalidUpdateAttemptProduced when a record is attempted to be updated but it doesn’t exist

可以在模型中getMessages()中替换或者翻译ORM生成的默认信息。

The method getMessages() can be overridden in a model to replace/translate the default messages generated automatically by the ORM:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public function getMessages()
  6. {
  7. $messages = array();
  8. foreach (parent::getMessages() as $message) {
  9. switch ($message->getType()) {
  10. case 'InvalidCreateAttempt':
  11. $messages[] = 'The record cannot be created because it already exists';
  12. break;
  13. case 'InvalidUpdateAttempt':
  14. $messages[] = 'The record cannot be updated because it already exists';
  15. break;
  16. case 'PresenceOf':
  17. $messages[] = 'The field ' . $message->getField() . ' is mandatory';
  18. break;
  19. }
  20. }
  21. return $messages;
  22. }
  23. }

事件和事件管理器Events and Events Manager

数据模型允许在执行insert/update/delete触发事件。帮助我们更好的建立业务逻辑。下面是 Phalcon\Mvc\Model 支持的事件和执行顺序:

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

OperationNameCan stop operation?Explanation
Inserting/UpdatingbeforeValidationYESIs executed before the fields are validated for not nulls/empty strings or foreign keys
InsertingbeforeValidationOnCreateYESIs executed before the fields are validated for not nulls/empty strings or foreign keys when an insertion operation is being made
UpdatingbeforeValidationOnUpdateYESIs executed before the fields are validated for not nulls/empty strings or foreign keys when an updating operation is being made
Inserting/UpdatingonValidationFailsYES (already stopped)Is executed after an integrity validator fails
InsertingafterValidationOnCreateYESIs executed after the fields are validated for not nulls/empty strings or foreign keys when an insertion operation is being made
UpdatingafterValidationOnUpdateYESIs executed after the fields are validated for not nulls/empty strings or foreign keys when an updating operation is being made
Inserting/UpdatingafterValidationYESIs executed after the fields are validated for not nulls/empty strings or foreign keys
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

模型中自定义事件Implementing Events in the Model’s class

让一个模型对事件做出反应更简单的方法是在事件模型的类中实现一个具有相同名称的方法:

The easier way to make a model react to events is implement a method with the same name of the event in the model’s class:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  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\Model;
  3. class Products extends Model
  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. }

使用自定义事件管理器Using a custom Events Manager

事件管理器组件被整合到 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\Mvc\Model;
  3. use Phalcon\Events\Manager as EventsManager;
  4. class Robots extends Model
  5. {
  6. public function initialize()
  7. {
  8. $eventsManager = new EventsManager();
  9. //Attach an anonymous function as a listener for "model" events
  10. $eventsManager->attach('model', function($event, $robot) {
  11. if ($event->getType() == 'beforeSave') {
  12. if ($robot->name == 'Scooby Doo') {
  13. echo "Scooby Doo isn't a robot!";
  14. return false;
  15. }
  16. }
  17. return true;
  18. });
  19. //Attach the events manager to the event
  20. $this->setEventsManager($eventsManager);
  21. }
  22. }

在上面的例子中,事件管理器只是充当在一个对象和一个侦听器之间的桥梁(匿名函数)。在‘robots’保存的时候事件将被监听者收到:

In the example given above, EventsManager only acts as a bridge between an object and a listener (the anonymous function). Events will be fired to the listener when ‘robots’ are saved:

  1. <?php
  2. $robot = new Robots();
  3. $robot->name = 'Scooby Doo';
  4. $robot->year = 1969;
  5. $robot->save();

如果我们想要在我们的应用程序中使用相同的事件管理器,我们需要将它分配给模型管理器:

If we want all objects created in our application use the same EventsManager, then we need to assign it to the Models Manager:

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

如果一个侦听器返回false,将停止当前正在执行的操作。

If a listener returns false that will stop the operation that is executing currently.

实现业务逻辑Implementing a Business Rule

当执行插入、更新或删除时,该模型验证在表中是否有任何和事件相关的方法。

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.

我们建议验证方法声明为protected,防止业务逻辑实现被公开曝光。

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

下面的示例实现一个事件来验证年份小于0的不能被更新或插入:

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\Model;
  3. class Robots extends Model
  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作为停止当前操作的指示。如果一个事件不返回任何值,:doc:`Phalcon\Mvc\Model <../api/Phalcon_Mvc_Model>`假设返回了true。

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

验证数据完整性Validating Data Integrity

Phalcon\Mvc\Model 提供了一些事件来验证数据和实施业务规则。特殊的”验证”事件允许我们调用内置的验证器验证记录。Phalcon暴露一些内置的验证器,我们可以使用这些验证。

Phalcon\Mvc\Model 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\Model;
  3. use Phalcon\Mvc\Model\Validator\Uniqueness;
  4. use Phalcon\Mvc\Model\Validator\InclusionIn;
  5. class Robots extends Model
  6. {
  7. public function validation()
  8. {
  9. $this->validate(new InclusionIn(
  10. array(
  11. "field" => "type",
  12. "domain" => array("Mechanical", "Virtual")
  13. )
  14. ));
  15. $this->validate(new Uniqueness(
  16. array(
  17. "field" => "name",
  18. "message" => "The robot name must be unique"
  19. )
  20. ));
  21. return $this->validationHasFailed() != true;
  22. }
  23. }

上面的例子演示了使用内置“InclusionIn”(包含)验证器。它验证了字段“type”是否在domain列表中。如果验证失败就会返回false。内置的验证器如下所示:

The above example 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
PresenceOfValidates that a field’s value isn’t null or empty string. This validator is automatically added based on the attributes marked as not null on the mapped tableExample
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
UniquenessValidates that a field or a combination of a set of fields are not present more than once in the existing records of the related tableExample
StringLengthValidates the length of a stringExample
UrlValidates that a value has a valid URL formatExample

除了内置的验证器,我们还可以自定义验证器:

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

  1. <?php
  2. use Phalcon\Mvc\Model\Validator;
  3. use Phalcon\Mvc\Model\ValidatorInterface;
  4. class MaxMinValidator extends Validator implements ValidatorInterface
  5. {
  6. public function validate($model)
  7. {
  8. $field = $this->getOption('field');
  9. $min = $this->getOption('min');
  10. $max = $this->getOption('max');
  11. $value = $model->$field;
  12. if ($min <= $value && $value <= $max) {
  13. $this->appendMessage(
  14. "The field doesn't have the right range of values",
  15. $field,
  16. "MaxMinValidator"
  17. );
  18. return false;
  19. }
  20. return true;
  21. }
  22. }

将验证器加入模型:

Adding the validator to a model:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Customers extends Model
  4. {
  5. public function validation()
  6. {
  7. $this->validate(new MaxMinValidator(
  8. array(
  9. "field" => "price",
  10. "min" => 10,
  11. "max" => 100
  12. )
  13. ));
  14. if ($this->validationHasFailed() == true) {
  15. return false;
  16. }
  17. }
  18. }

创建验证器的想法是让他们可以在几个模型之间可重用。一个验证器也可以一样简单定义如下:

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

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. use Phalcon\Mvc\Model\Message;
  4. class Robots extends Model
  5. {
  6. public function validation()
  7. {
  8. if ($this->type == "Old") {
  9. $message = new Message(
  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. }

避免SQL注入Avoiding SQL injections

每个值分配给模型属性会根据数据类型被转义。程序员不必手动转义每个值保存到数据库。Phalcon使用内置的 bound parameters 由PDO提供的功能自动转义存入数据库的值。

Every value assigned to a model attribute is escaped depending of its data type. A developer doesn’t need to escape manually each value before storing it on the database. Phalcon uses internally the bound parameters capability provided by PDO to automatically escape every value to be stored in the database.

  1. mysql> desc products;
  2. +------------------+------------------+------+-----+---------+----------------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +------------------+------------------+------+-----+---------+----------------+
  5. | id | int(10) unsigned | NO | PRI | NULL | auto_increment |
  6. | product_types_id | int(10) unsigned | NO | MUL | NULL | |
  7. | name | varchar(70) | NO | | NULL | |
  8. | price | decimal(16,2) | NO | | NULL | |
  9. | active | char(1) | YES | | NULL | |
  10. +------------------+------------------+------+-----+---------+----------------+
  11. 5 rows in set (0.00 sec)

如果我们以一个安全的方式使用PDO存储记录,我们需要编写下面的代码:

If we use just PDO to store a record in a secure way, we need to write the following code:

  1. <?php
  2. $name = 'Artichoke';
  3. $price = 10.5;
  4. $active = 'Y';
  5. $productTypesId = 1;
  6. $sql = 'INSERT INTO products VALUES (null, :productTypesId, :name, :price, :active)';
  7. $sth = $dbh->prepare($sql);
  8. $sth->bindParam(':productTypesId', $productTypesId, PDO::PARAM_INT);
  9. $sth->bindParam(':name', $name, PDO::PARAM_STR, 70);
  10. $sth->bindParam(':price', doubleval($price));
  11. $sth->bindParam(':active', $active, PDO::PARAM_STR, 1);
  12. $sth->execute();

Phalcon已经帮我们自动处理了:

The good news is that Phalcon do this for you automatically:

  1. <?php
  2. $product = new Products();
  3. $product->product_types_id = 1;
  4. $product->name = 'Artichoke';
  5. $product->price = 10.5;
  6. $product->active = 'Y';
  7. $product->create();

忽略指定列的数据Skipping Columns

可以让Phalcon\Mvc\Model在创建或者更新记录的时候忽略一些字段,这样可以将数据库赋值委托给一个触发器。

To tell Phalcon\Mvc\Model that always omits some fields in the creation and/or update of records in order to delegate the database system the assignation of the values by a trigger or a default:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public function initialize()
  6. {
  7. //Skips fields/columns on both INSERT/UPDATE operations
  8. $this->skipAttributes(array('year', 'price'));
  9. //Skips only when inserting
  10. $this->skipAttributesOnCreate(array('created_at'));
  11. //Skips only when updating
  12. $this->skipAttributesOnUpdate(array('modified_in'));
  13. }
  14. }

上面的操作会在整个应用中INSERT/UPDATE操作时忽略上面的字段。如果想在不同的INSERT/UPDATE操作忽略不同的字段,可以定义第二个参数为true。

This will ignore globally these fields on each INSERT/UPDATE operation on the whole application. If you want to ignore different attributes on different INSERT/UPDATE operations, you can specify the second parameter (boolean) - true for replacement.

强制设置默认值可以用下面方法实现:

Forcing a default value can be done in the following way:

  1. <?php
  2. use Phalcon\Db\RawValue;
  3. $robot = new Robots();
  4. $robot->name = 'Bender';
  5. $robot->year = 1999;
  6. $robot->created_at = new RawValue('default');
  7. $robot->create();

回调同样可以被用于根据条件设置默认值。

A callback also can be used to create a conditional assignment of automatic default values:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. use Phalcon\Db\RawValue;
  4. class Robots extends Model
  5. {
  6. public function beforeCreate()
  7. {
  8. if ($this->price > 10000) {
  9. $this->type = new RawValue('default');
  10. }
  11. }
  12. }

永远不要用\Phalcon\Db\RawValue去给用户输入的数据或者变量赋值。这些字段的值在绑定参数到请求的时候会被忽略,可能让应用有SQL注入的风险。

Never use a \Phalcon\Db\RawValue to assign external data (such as user input) or variable data. The value of these fields is ignored when binding parameters to the query. So it could be used to attack the application injecting SQL.

动态更新Dynamic Update

默认SQL UPDATE语句更新模型中定义的每一列(完整的所有字段SQL更新)。可以改变特定的模型进行动态更新,在这种情况下只有变更的字段才用于创建最终的SQL语句。

SQL UPDATE statements are by default created with every column defined in the model (full all-field SQL update). You can change specific models to make dynamic updates, in this case, just the fields that had changed are used to create the final SQL statement.

在某些情况下这可能会提高性能,减少应用程序和数据库服务器之间的流量,特别是当表中有blob/文本字段:

In some cases this could improve the performance by reducing the traffic between the application and the database server, this specially helps when the table has blob/text fields:

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

删除记录Deleting Records

Phalcon\Mvc\Model::delete()方法被用于删除记录。使用方法如下:

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

  1. <?php
  2. $robot = Robots::findFirst(11);
  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. }

可以在foreach循环结果集批量删除记录:

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

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

当删除被执行的时候下面的事件可以被用于自定义业务逻辑:

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

使用上面的事件我们可以在模型中定义业务逻辑:

With the above events can also define business rules in the models:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public function beforeDelete()
  6. {
  7. if ($this->status == 'A') {
  8. echo "The robot is active, it can't be deleted";
  9. return false;
  10. }
  11. return true;
  12. }
  13. }

验证失败事件Validation Failed Events

在数据验证过程中发现失败的时候另外一些事件可以被使用。

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

OperationNameExplanation
Insert or UpdatenotSaveTriggered when the INSERT or UPDATE operation fails for any reason
Insert, Delete or UpdateonValidationFailsTriggered when any data manipulation operation fails

行为Behaviors

行为是为了重用代码而在几个模块中公用的部分,ORM提供一个API可以用来实现我们自己的模型行为。另外,可以使用事件和回调更自由的去实现行为。

Behaviors are shared conducts that several models may adopt in order to re-use code, the ORM provides an API to implement behaviors in your models. Also, you can use the events and callbacks as seen before as an alternative to implement Behaviors with more freedom.

行为必须被添加到模型的初始化行为中,一个模型可以用零个或者多个行为:

A behavior must be added in the model initializer, a model can have zero or more behaviors:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. use Phalcon\Mvc\Model\Behavior\Timestampable;
  4. class Users extends Model
  5. {
  6. public $id;
  7. public $name;
  8. public $created_at;
  9. public function initialize()
  10. {
  11. $this->addBehavior(new Timestampable(
  12. array(
  13. 'beforeCreate' => array(
  14. 'field' => 'created_at',
  15. 'format' => 'Y-m-d'
  16. )
  17. )
  18. ));
  19. }
  20. }

框架提供了下面内置的行为:

The following built-in behaviors are provided by the framework:

NameDescription
TimestampableAllows to automatically update a model’s attribute saving the datetime when a record is created or updated
SoftDeleteInstead of permanently delete a record it marks the record as deleted changing the value of a flag column

生成时间戳Timestampable

这种行为接收一个数组作为参数,第一级键必须是一个事件名称指明了在什么时候列必须被赋值:

This behavior receives an array of options, the first level key must be an event name indicating when the column must be assigned:

  1. <?php
  2. use Phalcon\Mvc\Model\Behavior\Timestampable;
  3. public function initialize()
  4. {
  5. $this->addBehavior(new Timestampable(
  6. array(
  7. 'beforeCreate' => array(
  8. 'field' => 'created_at',
  9. 'format' => 'Y-m-d'
  10. )
  11. )
  12. ));
  13. }

每个事件可以有自己的选项,‘field’是必须更新的列。如果’format’是字符,将会用PHP内置的函数date进行格式化。format还可以是匿名函数方便我们自己的定义时间戳格式。

Each event can have its own options, ‘field’ is the name of the column that must be updated, if ‘format’ is a string it will be used as format of the PHP’s function date, format can also be an anonymous function providing you the free to generate any kind timestamp:

  1. <?php
  2. use Phalcon\Mvc\Model\Behavior\Timestampable;
  3. public function initialize()
  4. {
  5. $this->addBehavior(new Timestampable(
  6. array(
  7. 'beforeCreate' => array(
  8. 'field' => 'created_at',
  9. 'format' => function() {
  10. $datetime = new Datetime(new DateTimeZone('Europe/Stockholm'));
  11. return $datetime->format('Y-m-d H:i:sP');
  12. }
  13. )
  14. )
  15. ));
  16. }

如果format选项被忽略了,将会使用PHP默认的time。

If the option ‘format’ is omitted a timestamp using the PHP’s function time, will be used.

软删除SoftDelete

行为可以被用于以下的方式:

This behavior can be used in the following way:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. use Phalcon\Mvc\Model\Behavior\SoftDelete;
  4. class Users extends Model
  5. {
  6. const DELETED = 'D';
  7. const NOT_DELETED = 'N';
  8. public $id;
  9. public $name;
  10. public $status;
  11. public function initialize()
  12. {
  13. $this->addBehavior(new SoftDelete(
  14. array(
  15. 'field' => 'status',
  16. 'value' => Users::DELETED
  17. )
  18. ));
  19. }
  20. }

行为接受两个参数“field”和“value”,”field”是要更新的字段,‘value’是要被删除的表和状态。

This behavior accepts two options: ‘field’ and ‘value’, ‘field’ determines what field must be updated and ‘value’ the value to be deleted. Let’s pretend the table ‘users’ has the following data:

  1. mysql> select * from users;
  2. +----+---------+--------+
  3. | id | name | status |
  4. +----+---------+--------+
  5. | 1 | Lana | N |
  6. | 2 | Brandon | N |
  7. +----+---------+--------+
  8. 2 rows in set (0.00 sec)

如果我们删除任意下面的一条记录,只是更新了他们的状态而不是真正的删除:

If we delete any of the two records the status will be updated instead of delete the record:

  1. <?php
  2. Users::findFirst(2)->delete();

操作会是以下的结果:

The operation will result in the following data in the table:

  1. mysql> select * from users;
  2. +----+---------+--------+
  3. | id | name | status |
  4. +----+---------+--------+
  5. | 1 | Lana | N |
  6. | 2 | Brandon | D |
  7. +----+---------+--------+
  8. 2 rows in set (0.01 sec)

注意,我们需在的查询条件中手动指定已经软删除的记录来忽略它们(防止已经软删除的被重复操作),行为并不支持这样的自动操作。

Note that you need to specify the deleted condition in your queries to effectively ignore them as deleted records, this behavior doesn’t support that.

创建行为Creating your own behaviors

ORM提供一个API来创建我们自己的行为。行为必须是:doc:`Phalcon\Mvc\Model\BehaviorInterface <../api/Phalcon_Mvc_Model_BehaviorInterface>`的类是实现。同时Phalcon\Mvc\Model\Behavior提供了实现行为大部分的方法。

The ORM provides an API to create your own behaviors. A behavior must be a class implementing the Phalcon\Mvc\Model\BehaviorInterface Also, Phalcon\Mvc\Model\Behavior provides most of the methods needed to ease the implementation of behaviors.

以下行为是一个例子,它监控用户对模型所做的行为操作:

The following behavior is an example, it implements the Blamable behavior which helps identify the user that is performed operations over a model:

  1. <?php
  2. use Phalcon\Mvc\Model\Behavior;
  3. use Phalcon\Mvc\Model\BehaviorInterface;
  4. class Blamable extends Behavior implements BehaviorInterface
  5. {
  6. public function notify($eventType, $model)
  7. {
  8. switch ($eventType) {
  9. case 'afterCreate':
  10. case 'afterDelete':
  11. case 'afterUpdate':
  12. $userName = // ... get the current user from session
  13. //Store in a log the username - event type and primary key
  14. file_put_contents(
  15. 'logs/blamable-log.txt',
  16. $userName . ' ' . $eventType . ' ' . $model->id
  17. );
  18. break;
  19. default:
  20. /* ignore the rest of events */
  21. }
  22. }
  23. }

上面是一个非常简单的行为,但是它演示了如何创建一个行为,现在让我们将这个行为添加到模型中:

The former is a very simple behavior, but it illustrates how to create a behavior, now let’s add this behavior to a model:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Profiles extends Model
  4. {
  5. public function initialize()
  6. {
  7. $this->addBehavior(new Blamable());
  8. }
  9. }

一个行为也能在模型中拦截丢失的方法:

A behavior is also capable of intercepting missing methods on your models:

  1. <?php
  2. use Phalcon\Tag;
  3. use Phalcon\Mvc\Model\Behavior;
  4. use Phalcon\Mvc\Model\BehaviorInterface;
  5. class Sluggable extends Behavior implements BehaviorInterface
  6. {
  7. public function missingMethod($model, $method, $arguments=array())
  8. {
  9. // if the method is 'getSlug' convert the title
  10. if ($method == 'getSlug') {
  11. return Tag::friendlyTitle($model->title);
  12. }
  13. }
  14. }

调用该模型的方法实现Sluggable并返回一个SEO友好的标题:

Call that method on a model that implements Sluggable returns a SEO friendly title:

  1. <?php
  2. $title = $post->getSlug();

使用 Traits 实现行为Using Traits as behaviors

从php5.4我们可以使用Traits_去更好的实现代码重用功能。这是另一个实现自定义行为的方法。下面的trait实现了另一个版本的Timestampable行为:

Starting from PHP 5.4 you can use Traits to re-use code in your classes, this is another way to implement custom behaviors. The following trait implements a simple version of the Timestampable behavior:

  1. <?php
  2. trait MyTimestampable
  3. {
  4. public function beforeCreate()
  5. {
  6. $this->created_at = date('r');
  7. }
  8. public function beforeUpdate()
  9. {
  10. $this->updated_at = date('r');
  11. }
  12. }

Then you can use it in your model as follows:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Products extends Model
  4. {
  5. use MyTimestampable;
  6. }

事务管理Transactions

当一个进程执行多个数据库操作,通常每一步都必须成功完成以保证数据的完整性。事务管理提供确保所有数据库操作都成功执行后再将数据提交到数据库的功能。

When a process performs multiple database operations, it is often that each step is completed successfully so that data integrity can be maintained. Transactions offer the ability to ensure that all database operations have been executed successfully before the data are committed to the database.

Phalcon中的事务管理允许您提交所有操作在他们被成功执行后,如果出现任何差错就执行回滚操作。

Transactions in Phalcon allow you to commit all operations if they have been executed successfully or rollback all operations if something went wrong.

手动事务管理Manual Transactions

如果一个应用程序只使用一个连接和事务不是很复杂,一个事务只是将当前连接创建为事务模式,根据操作是否成功做回滚或提交:

If an application only uses one connection and the transactions aren’t very complex, a transaction can be created by just moving the current connection to transaction mode, doing a rollback or commit if the operation is successfully or not:

  1. <?php
  2. use Phalcon\Mvc\Controller;
  3. class RobotsController extends Controller
  4. {
  5. public function saveAction()
  6. {
  7. $this->db->begin();
  8. $robot = new Robots();
  9. $robot->name = "WALL·E";
  10. $robot->created_at = date("Y-m-d");
  11. if ($robot->save() == false) {
  12. $this->db->rollback();
  13. return;
  14. }
  15. $robotPart = new RobotParts();
  16. $robotPart->robots_id = $robot->id;
  17. $robotPart->type = "head";
  18. if ($robotPart->save() == false) {
  19. $this->db->rollback();
  20. return;
  21. }
  22. $this->db->commit();
  23. }
  24. }

隐式事务管理Implicit Transactions

在数据库之前有引用关联操作存储记录的情况下,隐式地创建了一个事务处理,确保数据完整正确存储:

Existing relationships can be used to store records and their related instances, this kind of operation implicitly creates a transaction to ensure that data are correctly stored:

  1. <?php
  2. $robotPart = new RobotParts();
  3. $robotPart->type = "head";
  4. $robot = new Robots();
  5. $robot->name = "WALL·E";
  6. $robot->created_at = date("Y-m-d");
  7. $robot->robotPart = $robotPart;
  8. $robot->save(); //Creates an implicit transaction to store both records

单独事务管理Isolated Transactions

孤立的事务中执行一个新的连接确保所有生成的SQL,虚拟外键检查和业务规则相对于主连接是孤立的。这种全局性的事务需要一个事务管理器管理每个事务确保他们在请求执行结束前正确提交/回滚:

Isolated transactions are executed in a new connection ensuring that all the generated SQL, virtual foreign key checks and business rules are isolated from the main connection. This kind of transaction requires a transaction manager that globally manages each transaction created ensuring that they are correctly rolled back/committed before ending the request:

  1. <?php
  2. use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
  3. use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
  4. try {
  5. //Create a transaction manager
  6. $manager = new TxManager();
  7. // Request a transaction
  8. $transaction = $manager->get();
  9. $robot = new Robots();
  10. $robot->setTransaction($transaction);
  11. $robot->name = "WALL·E";
  12. $robot->created_at = date("Y-m-d");
  13. if ($robot->save() == false) {
  14. $transaction->rollback("Cannot save robot");
  15. }
  16. $robotPart = new RobotParts();
  17. $robotPart->setTransaction($transaction);
  18. $robotPart->robots_id = $robot->id;
  19. $robotPart->type = "head";
  20. if ($robotPart->save() == false) {
  21. $transaction->rollback("Cannot save robot part");
  22. }
  23. //Everything goes fine, let's commit the transaction
  24. $transaction->commit();
  25. } catch(TxFailed $e) {
  26. echo "Failed, reason: ", $e->getMessage();
  27. }

事务可以用来以一致的方式删除许多记录:

Transactions can be used to delete many records in a consistent way:

  1. <?php
  2. use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
  3. use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
  4. try {
  5. //Create a transaction manager
  6. $manager = new TxManager();
  7. //Request a transaction
  8. $transaction = $manager->get();
  9. //Get the robots will be deleted
  10. foreach (Robots::find("type = 'mechanical'") as $robot) {
  11. $robot->setTransaction($transaction);
  12. if ($robot->delete() == false) {
  13. //Something goes wrong, we should to rollback the transaction
  14. foreach ($robot->getMessages() as $message) {
  15. $transaction->rollback($message->getMessage());
  16. }
  17. }
  18. }
  19. //Everything goes fine, let's commit the transaction
  20. $transaction->commit();
  21. echo "Robots were deleted successfully!";
  22. } catch(TxFailed $e) {
  23. echo "Failed, reason: ", $e->getMessage();
  24. }

事务管理可以被重用无论事务管理对象在哪里被获取。只有当一个commit()和rollback()被执行后才会生成一个新的事务管理。您可以使用服务容器为整个应用程序创建全局事务管理器:

Transactions are reused no matter where the transaction object is retrieved. A new transaction is generated only when a commit() or rollback() is performed. You can use the service container to create the global transaction manager for the entire application:

  1. <?php
  2. use Phalcon\Mvc\Model\Transaction\Manager as TransactionManager
  3. $di->setShared('transactions', function(){
  4. return new TransactionManager();
  5. });

然后从一个控制器或视图访问它

Then access it from a controller or view:

  1. <?php
  2. use Phalcon\Mvc\Controller;
  3. class ProductsController extends Controller
  4. {
  5. public function saveAction()
  6. {
  7. //Obtain the TransactionsManager from the services container
  8. $manager = $this->di->getTransactions();
  9. //Or
  10. $manager = $this->transactions;
  11. //Request a transaction
  12. $transaction = $manager->get();
  13. //...
  14. }
  15. }

当一个事务管理被激活后,事务管理器将始终返回相同的事务管理在整个应用程序。

While a transaction is active, the transaction manager will always return the same transaction across the application.

独立的列映射Independent Column Mapping

ORM支持一个独立的列映射,它允许开发人员相对于数据中的实际定义在模型中使用不同的列名。Phalcon将识别到新的列名并相应地将重命名来匹配在数据库中相应的列。这是一个非常好的的特性,当需要在数据库重命名字段,而不必担心在代码中所有的查询。改变模型中的列映射会自动完整剩下的操作。例如:

The ORM supports an independent column map, which allows the developer to use different column names in the model to the ones in the table. Phalcon will recognize the new column names and will rename them accordingly to match the respective columns in the database. This is a great feature when one needs to rename fields in the database without having to worry about all the queries in the code. A change in the column map in the model will take care of the rest. For example:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public function columnMap()
  6. {
  7. //Keys are the real names in the table and键是数据库中实际值
  8. //the values their names in the application值是应用中映射的值
  9. return array(
  10. 'id' => 'code',
  11. 'the_name' => 'theName',
  12. 'the_type' => 'theType',
  13. 'the_year' => 'theYear'
  14. );
  15. }
  16. }

然后可以在代码使用新的名字:

Then you can use the new names naturally in your code:

  1. <?php
  2. //Find a robot by its name
  3. $robot = Robots::findFirst("theName = 'Voltron'");
  4. echo $robot->theName, "\n";
  5. //Get robots ordered by type
  6. $robot = Robots::find(array('order' => 'theType DESC'));
  7. foreach ($robots as $robot) {
  8. echo 'Code: ', $robot->code, "\n";
  9. }
  10. //Create a robot
  11. $robot = new Robots();
  12. $robot->code = '10101';
  13. $robot->theName = 'Bender';
  14. $robot->theType = 'Industrial';
  15. $robot->theYear = 2999;
  16. $robot->save();

如果进行列重命名需要考虑以下几点:

Take into consideration the following the next when renaming your columns:

  • 属性的引用关系/验证器必须使用新名称
  • 引用真实的列名将导致ORM异常
  • References to attributes in relationships/validators must use the new names
  • Refer the real column names will result in an exception by the ORM

独立的列映射允许我们:

The independent column map allow you to:

  • 使用自己的约定编写应用程序
  • 在代码中消除特定的前缀/后缀
  • 改变实际列名称不需要去改变应用程序代码
  • Write applications using your own conventions
  • Eliminate vendor prefixes/suffixes in your code
  • Change column names without change your application code

操作结果集Operations over Resultsets

如果结果集是完整的对象,通过简单的操作结果集就能操作数据记录:

If a resultset is composed of complete objects, the resultset is in the ability to perform operations on the records obtained in a simple manner:

更新关联表记录Updating related records

除了使用下面的操作:

Instead of doing this:

  1. <?php
  2. foreach ($robots->getParts() as $part) {
  3. $part->stock = 100;
  4. $part->updated_at = time();
  5. if ($part->update() == false) {
  6. foreach ($part->getMessages() as $message) {
  7. echo $message;
  8. }
  9. break;
  10. }
  11. }

我们还可以这样操作:

you can do this:

  1. <?php
  2. $robots->getParts()->update(array(
  3. 'stock' => 100,
  4. 'updated_at' => time()
  5. ));

‘update’接受匿名函数去过滤哪些记录应该被更新:

‘update’ also accepts an anonymous function to filter what records must be updated:

  1. <?php
  2. $data = array(
  3. 'stock' => 100,
  4. 'updated_at' => time()
  5. );
  6. //Update all the parts except these whose type is basic
  7. $robots->getParts()->update($data, function($part) {
  8. if ($part->type == Part::TYPE_BASIC) {
  9. return false;
  10. }
  11. return true;
  12. });

删除相关记录Deleting related records

除了使用下面的操作:

Instead of doing this:

  1. <?php
  2. foreach ($robots->getParts() as $part) {
  3. if ($part->delete() == false) {
  4. foreach ($part->getMessages() as $message) {
  5. echo $message;
  6. }
  7. break;
  8. }
  9. }

我们还可以:

you can do this:

  1. <?php
  2. $robots->getParts()->delete();

“delete”接受匿名函数去过滤哪些记录应该被删除:

‘delete’ also accepts an anonymous function to filter what records must be deleted:

  1. <?php
  2. //Delete only whose stock is greater or equal than zero
  3. $robots->getParts()->delete(function($part) {
  4. if ($part->stock < 0) {
  5. return false;
  6. }
  7. return true;
  8. });

记录快照Record Snapshots

特定的模型在查询时可以被设置为保持记录快照。您可以使用此功能来实现审计或者仅仅是想知道哪些字段在查询被修改了:

Specific models could be set to maintain a record snapshot when they’re queried. You can use this feature to implement auditing or just to know what fields are changed according to the data queried from the persistence:

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

当激活这个功能是应用程序将消耗更多的内存来跟踪获得的原始值。在激活了该功能的模型中可以检查哪些值发生了改变:

When activating this feature the application consumes a bit more of memory to keep track of the original values obtained from the persistence. In models that have this feature activated you can check what fields changed:

  1. <?php
  2. //Get a record from the database
  3. $robot = Robots::findFirst();
  4. //Change a column
  5. $robot->name = 'Other name';
  6. var_dump($robot->getChangedFields()); // ['name']
  7. var_dump($robot->hasChanged('name')); // true
  8. var_dump($robot->hasChanged('type')); // false

模型元数据Models Meta-Data

为了加速开发 Phalcon\Mvc\Model 可以帮助我们查询字段及表之前的相关约束信息。 Phalcon\Mvc\Model\MetaData 使用可以满足需求并且可以缓存表的元数据。

To speed up development Phalcon\Mvc\Model helps you to query fields and constraints from tables related to models. To achieve this, Phalcon\Mvc\Model\MetaData is available to manage and cache table meta-data.

有时获取模型的元数据是必须的,我们可以通过下面方法获取:

Sometimes it is necessary to get those attributes when working with models. You can get a meta-data instance as follows:

  1. <?php
  2. $robot = new Robots();
  3. // Get Phalcon\Mvc\Model\Metadata instance
  4. $metaData = $robot->getModelsMetaData();
  5. // Get robots fields names
  6. $attributes = $metaData->getAttributes($robot);
  7. print_r($attributes);
  8. // Get robots fields data types
  9. $dataTypes = $metaData->getDataTypes($robot);
  10. print_r($dataTypes);

缓存模型元数据Caching Meta-Data

一旦应用程序在生产阶段,没有必要从数据库表查询的模型元数据,可以使用以下的适配器缓存元数据:

Once the application is in a production stage, it is not necessary to query the meta-data of the table from the database system each time you use the table. This could be done caching the meta-data using any of the following adapters:

AdapterDescriptionAPI
MemoryThis adapter is the default. The meta-data is cached only during the request. When the request is completed, the meta-data are released as part of the normal memory of the request. This adapter is perfect when the application is in development so as to refresh the meta-data in each request containing the new and/or modified fields.Phalcon\Mvc\Model\MetaData\Memory
SessionThis adapter stores meta-data in the $_SESSION superglobal. This adapter is recommended only when the application is actually using a small number of models. The meta-data are refreshed every time a new session starts. This also requires the use of session_start() to start the session before using any models.Phalcon\Mvc\Model\MetaData\Session
ApcThis adapter uses the Alternative PHP Cache (APC) to store the table meta-data. You can specify the lifetime of the meta-data with options. This is the most recommended way to store meta-data when the application is in production stage.Phalcon\Mvc\Model\MetaData\Apc
XCacheThis adapter uses XCache to store the table meta-data. You can specify the lifetime of the meta-data with options. This is the most recommended way to store meta-data when the application is in production stage.Phalcon\Mvc\Model\MetaData\Xcache
FilesThis adapter uses plain files to store meta-data. By using this adapter the disk-reading is increased but the database access is reducedPhalcon\Mvc\Model\MetaData\Files

就像ORM的其他依赖项一样,从服务容器请求元数据管理器:

As other ORM’s dependencies, the metadata manager is requested from the services container:

  1. <?php
  2. use Phalcon\Mvc\Model\MetaData\Apc as ApcMetaData;
  3. $di['modelsMetadata'] = function() {
  4. // Create a meta-data manager with APC
  5. $metaData = new ApcMetaData(array(
  6. "lifetime" => 86400,
  7. "prefix" => "my-prefix"
  8. ));
  9. return $metaData;
  10. };

元数据策略Meta-Data Strategies

正如上面提到的默认策略是从数据库自身来获取模型的元数据。在下面的代码,信息架构被用来表明表中的字段,主键,可空的字段,数据类型,等等。

As mentioned above the default strategy to obtain the model’s meta-data is database introspection. In this strategy, the information schema is used to know the fields in a table, its primary key, nullable fields, data types, etc.

可以使用下面的代码变更默认的元数据获取方法:

You can change the default meta-data introspection in the following way:

  1. <?php
  2. use Phalcon\Mvc\Model\MetaData\Apc as ApcMetaData;
  3. $di['modelsMetadata'] = function() {
  4. // Instantiate a meta-data adapter
  5. $metaData = new ApcMetaData(array(
  6. "lifetime" => 86400,
  7. "prefix" => "my-prefix"
  8. ));
  9. //Set a custom meta-data introspection strategy
  10. $metaData->setStrategy(new MyInstrospectionStrategy());
  11. return $metaData;
  12. };

数据库内部策略Database Introspection Strategy

这种策略不需要任何定制,隐式地被使用元数据适配器使用。

This strategy doesn’t require any customization and is implicitly used by all the meta-data adapters.

注释策略Annotations Strategy

下面代码使用:doc:`annotations <annotations>`来描述模型中的列:

This strategy makes use of annotations to describe the columns in a model:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. /**
  6. * @Primary
  7. * @Identity
  8. * @Column(type="integer", nullable=false)
  9. */
  10. public $id;
  11. /**
  12. * @Column(type="string", length=70, nullable=false)
  13. */
  14. public $name;
  15. /**
  16. * @Column(type="string", length=32, nullable=false)
  17. */
  18. public $type;
  19. /**
  20. * @Column(type="integer", nullable=false)
  21. */
  22. public $year;
  23. }

注释必须放置需要映射的属性列的上方。没有@column属性注释按照简单的类属性处理。

Annotations must be placed in properties that are mapped to columns in the mapped source. Properties without the @Column annotation are handled as simple class attributes.

支持以下注释:

The following annotations are supported:

NameDescription
PrimaryMark the field as part of the table’s primary key
IdentityThe field is an auto_increment/serial column
ColumnThis marks an attribute as a mapped column

@Column支持如下参数:

The annotation @Column supports the following parameters:

NameDescription
typeThe column’s type (string, integer, decimal, boolean)
lengthThe column’s length if any
nullableSet whether the column accepts null values or not

注释策略可以设置如下:

The annotations strategy could be set up this way:

  1. <?php
  2. use Phalcon\Mvc\Model\MetaData\Apc as ApcMetaData;
  3. use Phalcon\Mvc\Model\MetaData\Strategy\Annotations as StrategyAnnotations;
  4. $di['modelsMetadata'] = function() {
  5. // Instantiate a meta-data adapter
  6. $metaData = new ApcMetaData(array(
  7. "lifetime" => 86400,
  8. "prefix" => "my-prefix"
  9. ));
  10. //Set a custom meta-data database introspection
  11. $metaData->setStrategy(new StrategyAnnotations());
  12. return $metaData;
  13. };

自定义元数据Manual Meta-Data

Phalcon可以自动获得每个模型的元数据即使开发人员没有按照上面提到的使用任何策略去手动设置它们。

Phalcon can obtain the metadata for each model automatically without the developer must set them manually using any of the introspection strategies presented above.

开发人员也可以手动定义元数据。这种策略覆盖任何的元数据管理器中的策略。从映射的表中添加新列/修改/删除/也必须对其他的进行添加/修改/删除来保证一切运转正常。

The developer also has the option of define the metadata manually. This strategy overrides any strategy set in the meta-data manager. New columns added/modified/removed to/from the mapped table must be added/modified/removed also for everything to work properly.

下面的例子显示了如何手动定义元数据:

The following example shows how to define the meta-data manually:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. use Phalcon\Db\Column;
  4. use Phalcon\Mvc\Model\MetaData;
  5. class Robots extends Model
  6. {
  7. public function metaData()
  8. {
  9. return array(
  10. //Every column in the mapped table
  11. MetaData::MODELS_ATTRIBUTES => array(
  12. 'id', 'name', 'type', 'year'
  13. ),
  14. //Every column part of the primary key
  15. MetaData::MODELS_PRIMARY_KEY => array(
  16. 'id'
  17. ),
  18. //Every column that isn't part of the primary key
  19. MetaData::MODELS_NON_PRIMARY_KEY => array(
  20. 'name', 'type', 'year'
  21. ),
  22. //Every column that doesn't allows null values
  23. MetaData::MODELS_NOT_NULL => array(
  24. 'id', 'name', 'type', 'year'
  25. ),
  26. //Every column and their data types
  27. MetaData::MODELS_DATA_TYPES => array(
  28. 'id' => Column::TYPE_INTEGER,
  29. 'name' => Column::TYPE_VARCHAR,
  30. 'type' => Column::TYPE_VARCHAR,
  31. 'year' => Column::TYPE_INTEGER
  32. ),
  33. //The columns that have numeric data types
  34. MetaData::MODELS_DATA_TYPES_NUMERIC => array(
  35. 'id' => true,
  36. 'year' => true,
  37. ),
  38. //The identity column, use boolean false if the model doesn't have
  39. //an identity column
  40. MetaData::MODELS_IDENTITY_COLUMN => 'id',
  41. //How every column must be bound/casted
  42. MetaData::MODELS_DATA_TYPES_BIND => array(
  43. 'id' => Column::BIND_PARAM_INT,
  44. 'name' => Column::BIND_PARAM_STR,
  45. 'type' => Column::BIND_PARAM_STR,
  46. 'year' => Column::BIND_PARAM_INT,
  47. ),
  48. //Fields that must be ignored from INSERT SQL statements
  49. MetaData::MODELS_AUTOMATIC_DEFAULT_INSERT => array(
  50. 'year' => true
  51. ),
  52. //Fields that must be ignored from UPDATE SQL statements
  53. MetaData::MODELS_AUTOMATIC_DEFAULT_UPDATE => array(
  54. 'year' => true
  55. )
  56. );
  57. }
  58. }

设置模式Pointing to a different schema

如果一个模型没有采用默认的设置而是映射到一个不同的模式或数据库中的表,可以使用getSchema方法定义:

If a model is mapped to a table that is in a different schemas/databases than the default. You can use the getSchema method to define that:

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

设置多个数据库Setting multiple databases

在Phalcon,所有模型可以属于同一个数据库连接或者是每个都独立。实际上,当:doc:Phalcon\Mvc\Model <../api/Phalcon_Mvc_Model> 需要连接到数据库时它在应用程序的服务容器请求“db”服务。我们可以在服务设置的初始化中覆盖这个方法:

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

  1. <?php
  2. use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
  3. use Phalcon\Db\Adapter\Pdo\PostgreSQL as PostgreSQLPdo;
  4. //This service returns a MySQL database
  5. $di->set('dbMysql', function() {
  6. return new MysqlPdo(array(
  7. "host" => "localhost",
  8. "username" => "root",
  9. "password" => "secret",
  10. "dbname" => "invo"
  11. ));
  12. });
  13. //This service returns a PostgreSQL database
  14. $di->set('dbPostgres', function() {
  15. return new PostgreSQLPdo(array(
  16. "host" => "localhost",
  17. "username" => "postgres",
  18. "password" => "",
  19. "dbname" => "invo"
  20. ));
  21. });

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

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

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

但Phalcon为您提供更大的灵活性,可以定义的连接必须使用“读”或“写”。这在我们用数据库做主从架构实现负载均衡的时候特别有用:

But Phalcon offers you more flexibility, you can define the connection that must be used to ‘read’ and for ‘write’. This is specially useful to balance the load to your databases implementing a master-slave architecture:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. public function initialize()
  6. {
  7. $this->setReadConnectionService('dbSlave');
  8. $this->setWriteConnectionService('dbMaster');
  9. }
  10. }

ORM还提供水平分片功能,允许我们根据当前查询条件实现一个“分片”选择:

The ORM also provides Horizontal Sharding facilities, by allowing you to implement a ‘shard’ selection according to the current query conditions:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. class Robots extends Model
  4. {
  5. /**
  6. * Dynamically selects a shard
  7. *
  8. * @param array $intermediate
  9. * @param array $bindParams
  10. * @param array $bindTypes
  11. */
  12. public function selectReadConnection($intermediate, $bindParams, $bindTypes)
  13. {
  14. //Check if there is a 'where' clause in the select
  15. if (isset($intermediate['where'])) {
  16. $conditions = $intermediate['where'];
  17. //Choose the possible shard according to the conditions
  18. if ($conditions['left']['name'] == 'id') {
  19. $id = $conditions['right']['value'];
  20. if ($id > 0 && $id < 10000) {
  21. return $this->getDI()->get('dbShard1');
  22. }
  23. if ($id > 10000) {
  24. return $this->getDI()->get('dbShard2');
  25. }
  26. }
  27. }
  28. //Use a default shard
  29. return $this->getDI()->get('dbShard0');
  30. }
  31. }

“selectReadConnection”方法来选择正确的连接,这个方法可以拦截任何新查询的执行:

The method ‘selectReadConnection’ is called to choose the right connection, this method intercepts any new query executed:

  1. <?php
  2. $robot = Robots::findFirst('id = 101');

记录底层SQL语句Logging Low-Level SQL Statements

当使用高级抽象组件(如 Phalcon\Mvc\Model 的访问数据库,我们很难理解哪些语句最终被发送到数据库系统执行。 Phalcon\Db 内置支持 Phalcon\Mvc\Model 。所以通过 Phalcon\LoggerPhalcon\Db 的交互为数据库抽象层提供了日志功能,能够让我们记录SQL语句。

When using high-level abstraction components such as Phalcon\Mvc\Model to access a database, it is difficult to understand which statements are finally sent to the database system. Phalcon\Mvc\Model is supported internally by Phalcon\Db. Phalcon\Logger interacts with Phalcon\Db, providing logging capabilities on the database abstraction layer, thus allowing us to log SQL statements as they happen.

  1. <?php
  2. use Phalcon\Logger;
  3. use Phalcon\Events\Manager;
  4. use Phalcon\Logger\Adapter\File as FileLogger;
  5. use Phalcon\Db\Adapter\Pdo\Mysql as Connection;
  6. $di->set('db', function() {
  7. $eventsManager = new EventsManager();
  8. $logger = new FileLogger("app/logs/debug.log");
  9. //Listen all the database events
  10. $eventsManager->attach('db', function($event, $connection) use ($logger) {
  11. if ($event->getType() == 'beforeQuery') {
  12. $logger->log($connection->getSQLStatement(), Logger::INFO);
  13. }
  14. });
  15. $connection = new Connection(array(
  16. "host" => "localhost",
  17. "username" => "root",
  18. "password" => "secret",
  19. "dbname" => "invo"
  20. ));
  21. //Assign the eventsManager to the db adapter instance
  22. $connection->setEventsManager($eventsManager);
  23. return $connection;
  24. });

模型访问的默认数据库连接,所有发送到数据库系统的SQL语句将被记录在文件中:

As models access the default database connection, all SQL statements that are sent to the database system will be logged in the file:

  1. <?php
  2. $robot = new Robots();
  3. $robot->name = "Robby the Robot";
  4. $robot->created_at = "1956-07-21";
  5. if ($robot->save() == false) {
  6. echo "Cannot save robot";
  7. }

上面的语言将会保存在*app/logs/db.log*中:

As above, the file app/logs/db.log will contain something like this:

  1. [Mon, 30 Apr 12 13:47:18 -0500][DEBUG][Resource Id #77] INSERT INTO robots
  2. (name, created_at) VALUES ('Robby the Robot', '1956-07-21')

分析SQL语句Profiling SQL Statements

使用 Phalcon\Db ,底层组件 Phalcon\Mvc\Model 可以通过分析由ORM产生的SQL语言来分析数据库操作的性能让我们可以诊断性能问题并发现瓶颈。

Thanks to Phalcon\Db, the underlying component of Phalcon\Mvc\Model, it’s possible to profile the SQL statements generated by the ORM in order to analyze the performance of database operations. With this you can diagnose performance problems and to discover bottlenecks.

  1. <?php
  2. use Phalcon\Db\Profiler as ProfilerDb;
  3. use Phalcon\Events\Manager as ManagerEvent;
  4. use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
  5. $di->set('profiler', function(){
  6. return new ProfilerDb();
  7. }, true);
  8. $di->set('db', function() use ($di) {
  9. $eventsManager = new ManagerEvent();
  10. //Get a shared instance of the DbProfiler
  11. $profiler = $di->getProfiler();
  12. //Listen all the database events
  13. $eventsManager->attach('db', function($event, $connection) use ($profiler) {
  14. if ($event->getType() == 'beforeQuery') {
  15. $profiler->startProfile($connection->getSQLStatement());
  16. }
  17. if ($event->getType() == 'afterQuery') {
  18. $profiler->stopProfile();
  19. }
  20. });
  21. $connection = new MysqlPdo(array(
  22. "host" => "localhost",
  23. "username" => "root",
  24. "password" => "secret",
  25. "dbname" => "invo"
  26. ));
  27. //Assign the eventsManager to the db adapter instance
  28. $connection->setEventsManager($eventsManager);
  29. return $connection;
  30. });

分析一些查询:

Profiling some queries:

  1. <?php
  2. // Send some SQL statements to the database
  3. Robots::find();
  4. Robots::find(array("order" => "name"));
  5. Robots::find(array("limit" => 30));
  6. //Get the generated profiles from the profiler
  7. $profiles = $di->get('profiler')->getProfiles();
  8. foreach ($profiles as $profile) {
  9. echo "SQL Statement: ", $profile->getSQLStatement(), "\n";
  10. echo "Start Time: ", $profile->getInitialTime(), "\n";
  11. echo "Final Time: ", $profile->getFinalTime(), "\n";
  12. echo "Total Elapsed Time: ", $profile->getTotalElapsedSeconds(), "\n";
  13. }

每个生成的分析文件包含每条指令以毫秒为单位的完成时间以及生成的SQL语句。

Each generated profile contains the duration in milliseconds that each instruction takes to complete as well as the generated SQL statement.

服务注入到模型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\Model;
  3. class Robots extends Model
  4. {
  5. public function notSave()
  6. {
  7. //Obtain the flash service from the DI container
  8. $flash = $this->getDI()->getFlash();
  9. //Show validation messages
  10. foreach ($this->getMessages() as $message) {
  11. $flash->error($message);
  12. }
  13. }
  14. }

每次“创造”或“更新”action执行失败后“notSave”事件被触发。所以我们提示验证消息通过DI容器“flash”服务的。通过这样做,我们不必每次保存后打印信息。

The “notSave” event is triggered every time that a “create” or “update” action fails. So 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 save.

禁用或启用特性Disabling/Enabling Features

在ORM中实现了一个机制允许快速的启用/禁用特定功能或全局选项。根据如何使用ORM可以永久或者是临时禁用不需要的功能。:

In the ORM we have implemented a mechanism that allow you to enable/disable specific features or options globally on the fly. According to how you use the ORM you can disable that you aren’t using. These options can also be temporarily disabled if required:

  1. <?php
  2. use Phalcon\Mvc\Model;
  3. Model::setup(array(
  4. 'events' => false,
  5. 'columnRenaming' => false
  6. ));

可用的选项如下:

The available options are:

OptionDescriptionDefault
eventsEnables/Disables callbacks, hooks and event notifications from all the modelstrue
columnRenamingEnables/Disables the column renamingtrue
notNullValidationsThe ORM automatically validate the not null columns present in the mapped tabletrue
virtualForeignKeysEnables/Disables the virtual foreign keystrue
phqlLiteralsEnables/Disables literals in the PHQL parsertrue

独立组件Stand-Alone component

:doc:`Phalcon\Mvc\Model <models>`可以如下的代码所示独立使用:

Using Phalcon\Mvc\Model in a stand-alone mode can be demonstrated below:

  1. <?php
  2. use Phalcon\DI;
  3. use Phalcon\Mvc\Model;
  4. use Phalcon\Mvc\Model\Manager as ModelsManager;
  5. use Phalcon\Db\Adapter\Pdo\Sqlite as Connection;
  6. use Phalcon\Mvc\Model\Metadata\Memory as MetaData;
  7. $di = new DI();
  8. //Setup a connection
  9. $di->set('db', new Connection(array(
  10. "dbname" => "sample.db"
  11. )));
  12. //Set a models manager
  13. $di->set('modelsManager', new ModelsManager());
  14. //Use the memory meta-data adapter or other
  15. $di->set('modelsMetadata', new MetaData());
  16. //Create a model
  17. class Robots extends Model
  18. {
  19. }
  20. //Use the model
  21. echo Robots::count();