Relationship.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\data\model;
  9. use lithium\core\Libraries;
  10. use lithium\util\Inflector;
  11. use lithium\core\ConfigException;
  12. use lithium\core\ClassNotFoundException;
  13. /**
  14. * The `Relationship` class encapsulates the data and functionality necessary to link two model
  15. * classes together.
  16. */
  17. class Relationship extends \lithium\core\Object {
  18. /**
  19. * A relationship linking type defined by one document or record (or multiple) being embedded
  20. * within another.
  21. */
  22. const LINK_EMBEDDED = 'embedded';
  23. /**
  24. * The reciprocal of `LINK_EMBEDDED`, this defines a linking type wherein an embedded document
  25. * references the document that contains it.
  26. */
  27. const LINK_CONTAINED = 'contained';
  28. /**
  29. * A one-to-one or many-to-one relationship in which a key contains an ID value linking to
  30. * another document or record.
  31. */
  32. const LINK_KEY = 'key';
  33. /**
  34. * A many-to-many relationship in which a key contains an embedded array of IDs linking to other
  35. * records or documents.
  36. */
  37. const LINK_KEY_LIST = 'keylist';
  38. /**
  39. * A relationship defined by a database-native reference mechanism, linking a key to an
  40. * arbitrary record or document in another data collection or entirely separate database.
  41. */
  42. const LINK_REF = 'ref';
  43. /**
  44. * Constructs an object that represents a relationship between two model classes.
  45. *
  46. * @param array $config The relationship's configuration, which defines how the two models in
  47. * question are bound. The available options are:
  48. *
  49. * - `'name'` _string_: The name of the relationship in the context of the
  50. * originating model. For example, a `Posts` model might define a relationship to
  51. * a `Users` model like so:
  52. * {{{ public $hasMany = array('Author' => array('to' => 'Users')); }}}
  53. * In this case, the relationship is bound to the `Users` model, but `'Author'` would be the
  54. * relationship name. This is the name with which the relationship is referenced in the
  55. * originating model.
  56. * - `'key'` _mixed_: An array of fields that define the relationship, where the
  57. * keys are fields in the originating model, and the values are fields in the
  58. * target model. If the relationship is not deined by keys, this array should be
  59. * empty.
  60. * - `'type'` _string_: The type of relationship. Should be one of `'belongsTo'`,
  61. * `'hasOne'` or `'hasMany'`.
  62. * - `'from'` _string_: The fully namespaced class name of the model where this
  63. * relationship originates.
  64. * - `'to'` _string_: The fully namespaced class name of the model that this
  65. * relationship targets.
  66. * - `'link'` _string_: A constant specifying how the object bound to the
  67. * originating model is linked to the object bound to the target model. For
  68. * relational databases, the only valid value is `LINK_KEY`, which means a foreign
  69. * key in one object matches another key (usually the primary key) in the other.
  70. * For document-oriented and other non-relational databases, different types of
  71. * linking, including key lists, database reference objects (such as MongoDB's
  72. * `MongoDBRef`), or even embedding.
  73. * - `'fields'` _mixed_: An array of the subset of fields that should be selected
  74. * from the related object(s) by default. If set to `true` (the default), all
  75. * fields are selected.
  76. * - `'fieldName'` _string_: The name of the field used when accessing the related
  77. * data in a result set. For example, in the case of `Posts hasMany Comments`, the
  78. * field name defaults to `'comments'`, so comment data is accessed (assuming
  79. * `$post = Posts::first()`) as `$post->comments`.
  80. * - `'constraints'` _mixed_: A string or array containing additional constraints
  81. * on the relationship query. If a string, can contain a literal SQL fragment or
  82. * other database-native value. If an array, maps fields from the related object
  83. * either to fields elsewhere, or to arbitrary expressions. In either case, _the
  84. * values specified here will be literally interpreted by the database_.
  85. */
  86. public function __construct(array $config = array()) {
  87. $defaults = array(
  88. 'name' => null,
  89. 'key' => array(),
  90. 'type' => null,
  91. 'to' => null,
  92. 'from' => null,
  93. 'link' => static::LINK_KEY,
  94. 'fields' => true,
  95. 'fieldName' => null,
  96. 'constraints' => array()
  97. );
  98. parent::__construct($config + $defaults);
  99. }
  100. protected function _init() {
  101. parent::_init();
  102. $config =& $this->_config;
  103. $type = $config['type'];
  104. $name = ($type === 'hasOne') ? Inflector::pluralize($config['name']) : $config['name'];
  105. $config['fieldName'] = $config['fieldName'] ?: lcfirst($name);
  106. if (!$config['to']) {
  107. $assoc = preg_replace("/\\w+$/", "", $config['from']) . $name;
  108. $config['to'] = Libraries::locate('models', $assoc);
  109. }
  110. if (!$config['key'] || !is_array($config['key'])) {
  111. $config['key'] = $this->_keys($config['key']);
  112. }
  113. }
  114. public function data($key = null) {
  115. if (!$key) {
  116. return $this->_config;
  117. }
  118. return isset($this->_config[$key]) ? $this->_config[$key] : null;
  119. }
  120. public function __call($name, $args = array()) {
  121. return $this->data($name);
  122. }
  123. /**
  124. * Custom check to determine if our given magic methods can be responded to.
  125. *
  126. * @param string $method Method name.
  127. * @param bool $internal Interal call or not.
  128. * @return bool
  129. */
  130. public function respondsTo($method, $internal = false) {
  131. return is_callable(array($this, $method), true);
  132. }
  133. protected function _keys($keys) {
  134. $config = $this->_config;
  135. $hasRel = ($related = ($config['type'] === 'belongsTo') ? $config['to'] : $config['from']);
  136. if (!$hasRel || !$keys) {
  137. return array();
  138. }
  139. if (!class_exists($related)) {
  140. throw new ClassNotFoundException("Related model class '{$related}' not found.");
  141. }
  142. if (!$related::key()) {
  143. throw new ConfigException("No key defined for related model `{$related}`.");
  144. }
  145. $keys = (array) $keys;
  146. $related = (array) $related::key();
  147. if (count($keys) !== count($related)) {
  148. $msg = "Unmatched keys in relationship `{$config['name']}` between models ";
  149. $msg .= "`{$config['from']}` and `{$config['to']}`.";
  150. throw new ConfigException($msg);
  151. }
  152. return array_combine($keys, $related);
  153. }
  154. }
  155. ?>