模型的高级用法(Working with Models (Advanced))

结果集结合模式(Hydration Modes)

模型的结果集是模型对象的集合,这意味着每个返回的结果是一个对象对应数据库中的一行数据。这些对象可以被修改并保存:

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

当我们想将结果只用于只读时,我们可以通过设置结合模式为数组或者 stdClass,我们将这种策略叫做 ‘hydration mode’:

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

我们可以在方法 find 中传递结合模式:

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

创建与更新记录(Creating Updating/Records)

Phalcon\Mvc\Model::save() 方法允许你创建/更新记录。save方法自动调用 Phalcon\Mvc\Model 内部的create和update方法,如果想达到预期般的工作效果,正确定义实体主键是非常必须的,以确保创建和更新记录成功。

Also the method executes associated validators, virtual foreign keys and events that are defined in the model:

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

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

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

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

  1. <?php
  2.  
  3. $robot = new Robots();
  4. $robot->save($_POST);
Without precautions mass assignment could allow attackers to set any database column’s value. Only use this featureif you want to permit a user to insert/update every column in the model, even if those fields are not in the submittedform.

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

  1. <?php
  2.  
  3. $robot = new Robots();
  4.  
  5. $robot->save(
  6. $_POST,
  7. array(
  8. 'name',
  9. 'type'
  10. )
  11. );

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

Some models may have identity columns. These columns usually are the primary key of the mapped table. Phalcon\Mvc\Modelcan 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.  
  3. $robot->save();
  4.  
  5. echo "The generated id is: ", $robot->id;

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

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.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. public function getSequenceName()
  8. {
  9. return "robots_sequence_name";
  10. }
  11. }

忽略指定列的数据(Skipping Columns)

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

  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. public function initialize()
  8. {
  9. // Skips fields/columns on both INSERT/UPDATE operations
  10. $this->skipAttributes(
  11. array(
  12. 'year',
  13. 'price'
  14. )
  15. );
  16.  
  17. // Skips only when inserting
  18. $this->skipAttributesOnCreate(
  19. array(
  20. 'created_at'
  21. )
  22. );
  23.  
  24. // Skips only when updating
  25. $this->skipAttributesOnUpdate(
  26. array(
  27. 'modified_in'
  28. )
  29. );
  30. }
  31. }

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) - truefor replacement. Forcing a default value can be done in the following way:

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

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

  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4. use Phalcon\Db\RawValue;
  5.  
  6. class Robots extends Model
  7. {
  8. public function beforeCreate()
  9. {
  10. if ($this->price > 10000) {
  11. $this->type = new RawValue('default');
  12. }
  13. }
  14. }
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 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 changedare used to create the final SQL statement.

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.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. public function initialize()
  8. {
  9. $this->useDynamicUpdate(true);
  10. }
  11. }

独立的列映射(Independent Column Mapping)

The ORM supports an independent column map, which allows the developer to use different column names in the model to the ones inthe 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 queriesin the code. A change in the column map in the model will take care of the rest. For example:

  1. <?php
  2.  
  3. namespace Store\Toys;
  4.  
  5. use Phalcon\Mvc\Model;
  6.  
  7. class Robots extends Model
  8. {
  9. public $code;
  10.  
  11. public $theName;
  12.  
  13. public $theType;
  14.  
  15. public $theYear;
  16.  
  17. public function columnMap()
  18. {
  19. // Keys are the real names in the table and
  20. // the values their names in the application
  21. return [
  22. "id" => "code",
  23. "the_name" => "theName",
  24. "the_type" => "theType",
  25. "the_year" => "theYear",
  26. ];
  27. }
  28. }

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

  1. <?php
  2.  
  3. use Store\Toys\Robots;
  4.  
  5. // Find a robot by its name
  6. $robot = Robots::findFirst(
  7. "theName = 'Voltron'"
  8. );
  9.  
  10. echo $robot->theName, "\n";
  11.  
  12. // Get robots ordered by type
  13. $robot = Robots::find(
  14. [
  15. "order" => "theType DESC",
  16. ]
  17. );
  18.  
  19. foreach ($robots as $robot) {
  20. echo "Code: ", $robot->code, "\n";
  21. }
  22.  
  23. // Create a robot
  24. $robot = new Robots();
  25.  
  26. $robot->code = "10101";
  27. $robot->theName = "Bender";
  28. $robot->theType = "Industrial";
  29. $robot->theYear = 2999;
  30.  
  31. $robot->save();

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

  • 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

记录快照(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 whatfields are changed according to the data queried from the persistence:

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

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.  
  3. use Store\Toys\Robots;
  4.  
  5. // Get a record from the database
  6. $robot = Robots::findFirst();
  7.  
  8. // Change a column
  9. $robot->name = "Other name";
  10.  
  11. var_dump($robot->getChangedFields()); // ["name"]
  12.  
  13. var_dump($robot->hasChanged("name")); // true
  14.  
  15. var_dump($robot->hasChanged("type")); // false

设置模式(Pointing to a different schema)

如果一个模型映射到一个在非默认的schemas/数据库中的表,你可以通过 setSchema() 方法去定义它:

  1. <?php
  2.  
  3. namespace Store\Toys;
  4.  
  5. use Phalcon\Mvc\Model;
  6.  
  7. class Robots extends Model
  8. {
  9. public function initialize()
  10. {
  11. $this->setSchema("toys");
  12. }
  13. }

设置多个数据库(Setting multiple databases)

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

在Phalcon中,所有模型可以属于同一个数据库连接,也可以分属独立的数据库连接。实际上,当 Phalcon\Mvc\Model需要连接数据库的时候,它在应用服务容器内请求”db”这个服务。 可以通过在 initialize 方法内重写这个服务的设置。

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

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

然后,在 Initialize 方法内,我们为这个模型定义数据库连接。

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

实现读写分离(Reading and Writing Separation)

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

另外Phalcon还提供了更多的灵活性,你可分别定义用来读取和写入的数据库连接。这对实现主从架构的数据库负载均衡非常有用。

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

分库(Split Database)

  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. /**
  8. * 动态选择读数据库连接
  9. *
  10. * @param Phalcon\Mvc\Model\Query $query
  11. * @param array $intermediate
  12. * @param array $bindParams
  13. * @param array $bindTypes
  14. */
  15. public function selectReadConnection($query, $intermediate, $bindParams, $bindTypes)
  16. {
  17. return $this->getDI()->get('readDB');
  18. }
  19.  
  20. /**
  21. * 动态选择写数据库连接
  22. *
  23. * @param Phalcon\Mvc\Model\Query $query
  24. * @param array $intermediate
  25. * @param array $bindParams
  26. * @param array $bindTypes
  27. */
  28. public function selectWriteConnection($query, $intermediate, $bindParams, $bindTypes)
  29. {
  30. return $this->getDI()->get('writeDB');
  31. }
  32. }

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

另外ORM还可以通过根据当前查询条件来实现一个 ‘shared’ 选择器,来实现水平切分的功能。

  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. /**
  8. * 动态选择读数据库连接
  9. *
  10. * @param Phalcon\Mvc\Model\Query $query
  11. * @param array $intermediate
  12. * @param array $bindParams
  13. * @param array $bindTypes
  14. */
  15. public function selectReadConnection($query, $intermediate, $bindParams, $bindTypes)
  16. {
  17. // Check if there is a 'where' clause in the select
  18. if (isset($intermediate['where'])) {
  19.  
  20. $conditions = $intermediate['where'];
  21.  
  22. // Choose the possible shard according to the conditions
  23. if ($conditions['left']['name'] == 'id') {
  24. $id = $conditions['right']['value'];
  25.  
  26. if ($id > 0 && $id < 10000) {
  27. return $this->getDI()->get('dbShard1');
  28. }
  29.  
  30. if ($id > 10000) {
  31. return $this->getDI()->get('dbShard2');
  32. }
  33. }
  34. }
  35.  
  36. // Use a default shard
  37. return $this->getDI()->get('dbShard0');
  38. }
  39.  
  40. /**
  41. * 动态选择写数据库连接
  42. *
  43. * @param Phalcon\Mvc\Model\Query $query
  44. * @param array $intermediate
  45. * @param array $bindParams
  46. * @param array $bindTypes
  47. */
  48. public function selectWriteConnection($query, $intermediate, $bindParams, $bindTypes)
  49. {
  50. // Check if there is a 'where' clause in the select
  51.  
  52. // Use a default shard
  53. return $this->getDI()->get('dbShard0');
  54. }
  55. }

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

‘selectReadConnection’ 方法用来选择正确的数据库连接,这个方法拦截任何新的查询操作:

  1. <?php
  2.  
  3. $robot = Robots::findFirst('id = 101');
Model查询器 中可实现同样的功能。

实现分表

你可根据 Query 数据来选择不同的表进行操作。

也可以用如下方法实现:

  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. /**
  8. * 动态选择表
  9. *
  10. * @param Phalcon\Mvc\Model\Query $query
  11. */
  12. public function selectSource($query)
  13. {
  14. return 'robots_1';
  15. }
  16. }

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

The “notSaved” event is triggered every time that a “create” or “update” action fails. So we’re flashing the validation messagesobtaining the “flash” service from the DI container. By doing this, we don’t have to print messages after each save.

每当 “create” 或者 “update” 操作失败时会触发 “notSave” 事件。所以我们从DI中获取 “flash” 服务并推送确认消息。这样的话,我们不需要每次在save之后去打印信息。

禁用或启用特性(Disabling/Enabling Features)

在 ORM 中,我们实现了一种机制,允许您在全局上启用或者禁用特定的特性或选项。我们可以使用 setup 方法暂时启用或者禁用它:

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

The available options are:

Option Description Default
events Enables/Disables callbacks, hooks and event notifications from all the models true
columnRenaming Enables/Disables the column renaming true
notNullValidations The ORM automatically validate the not null columns present in the mapped table true
virtualForeignKeys Enables/Disables the virtual foreign keys true
phqlLiterals Enables/Disables literals in the PHQL parser true
lateStateBinding Enables/Disables late state binding of the method Mvc\Model::cloneResultMap() false
mustColumn 开启或者禁用模型序列化或执行 toArray 方法时不包括字段之外的数据 true
strict 开启或者禁用严格模式,严格模式下将会根据影响行数返回操作成功还是失败 false

独立的组件(Stand-Alone component)

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

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

原文: http://www.myleftstudio.com/reference/models-advanced.html