模型关系(Relationships between Models)

有四种关系类型:1对1,1对多,多对1,多对多。关系可以是单向或者双向的,每个关系可以是简单的(一个1对1的模型)也可以是复杂的(一组多个模型)。

单向关系(Unidirectional relationships)

单向关系在两个模型中的一个模型中定义了彼此的关系。

双向关系(Bidirectional relations)

双向关系在两个模型中建立关系,每个模型定义了另一个模型的反向关系。

定义关系(Defining relationships)

在Phalcon中,关系必须在模型的代码:initialize())方法中定义。方法:code:`belongsTo(),:code:hasOne(),:code:hasMany()和:code:hasManyToMany()定义当前模型中一个或多个字段之间的关系,另一个模型。这些方法中的每一个都需要3个参数:局部字段,引用模型,引用字段。在Phalcon中,关系必须在模型的 :code:initialize() 方法中定义。可以使用方法 belongsTo()hasOne()hasMany() 以及 hasManyToMany() 定义当前模型和另外一个模型中一个或多个字段之间的关系。这些方法中的每一个都需要3个参数:local fields(当前模型字段), referenced model(引用模型), referenced fields(引用字段)。

Method Description
hasMany Defines a 1-n relationship
hasOne Defines a 1-1 relationship
belongsTo Defines a n-1 relationship
hasManyToMany Defines 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.  
  9. CREATE TABLE `robots_parts` (
  10. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  11. `robots_id` int(10) NOT NULL,
  12. `parts_id` int(10) NOT NULL,
  13. `created_at` DATE NOT NULL,
  14. PRIMARY KEY (`id`),
  15. KEY `robots_id` (`robots_id`),
  16. KEY `parts_id` (`parts_id`)
  17. );
  18.  
  19. CREATE TABLE `parts` (
  20. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  21. `name` varchar(70) NOT NULL,
  22. PRIMARY KEY (`id`)
  23. );
  • 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”.
    Check the EER diagram to understand better the relations:
    模型关系(Relationships between Models) - 图2
    The models with their relations could be implemented as follows:
  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Robots extends Model
  6. {
  7. public $id;
  8.  
  9. public $name;
  10.  
  11. public function initialize()
  12. {
  13. $this->hasMany("id", "RobotsParts", "robots_id");
  14. }
  15. }
  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class Parts extends Model
  6. {
  7. public $id;
  8.  
  9. public $name;
  10.  
  11. public function initialize()
  12. {
  13. $this->hasMany("id", "RobotsParts", "parts_id");
  14. }
  15. }
  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class RobotsParts extends Model
  6. {
  7. public $id;
  8.  
  9. public $robots_id;
  10.  
  11. public $parts_id;
  12.  
  13. public function initialize()
  14. {
  15. $this->belongsTo("robots_id", "Robots", "id");
  16. $this->belongsTo("parts_id", "Parts", "id");
  17. }
  18. }

The first parameter indicates the field of the local model used in the relationship; the second indicates the nameof 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.

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

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

使用关系(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.  
  3. $robot = Robots::findFirst(2);
  4. foreach ($robot->robotsParts as $robotPart) {
  5. echo $robotPart->parts->name, "\n";
  6. }

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.  
  3. $robot = Robots::findFirst();
  4. $robotsParts = $robot->robotsParts; // All the related records in RobotsParts

Also, you can use a magic getter:

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

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

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

Getting related records manually:

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

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

Type Description Implicit Method
Belongs-To Returns a model instance of the related record directly findFirst
Has-One Returns a model instance of the related record directly findFirst
Has-Many Returns a collection of model instances of the referenced model find
Has-Many-to-Many Returns a collection of model instances of the referenced model, it implicitly does ‘inner joins’ with the involved models (complex query)

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

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

定义关系(Aliasing Relationships)

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

The “robots_similar” table 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)

Both “robots_id” and “similar_robots_id” have a relation to the model Robots:
模型关系(Relationships between Models) - 图3
A model that maps this table and its relationships is the following:

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

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

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

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

  1. <?php
  2.  
  3. use Phalcon\Mvc\Model;
  4.  
  5. class RobotsSimilar extends Model
  6. {
  7. public function initialize()
  8. {
  9. $this->belongsTo(
  10. 'robots_id',
  11. 'Robots',
  12. 'id',
  13. array(
  14. 'alias' => 'Robot'
  15. )
  16. );
  17.  
  18. $this->belongsTo(
  19. 'similar_robots_id',
  20. 'Robots',
  21. 'id',
  22. array(
  23. 'alias' => 'SimilarRobot'
  24. )
  25. );
  26. }
  27. }

With the aliasing we can get the related records easily:

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

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

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 correspondingdocblocks helping the IDE to produce a better auto-completion:

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

虚拟外键(Virtual Foreign Keys)

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

The RobotsPart model can be changed to demonstrate this feature:

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

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

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

A virtual foreign key can be set up to allow null values as follows:

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

级联与限制动作(Cascade/Restrict actions)

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

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

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

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

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

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

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

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

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

- Phalcon\Mvc\Model::beforeSave()- Phalcon\Mvc\Model::beforeCreate()- Phalcon\Mvc\Model::beforeUpdate()

You need to overload Phalcon\Mvc\Model::save() for this to work from within a model.

在结果集中操作(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:

Instead of doing this:

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

you can do this:

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

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

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

Instead of doing this:

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

you can do this:

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

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

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

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