NamedScopeBehavior.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <?php
  2. App::uses('ModelBehavior', 'Model');
  3. /**
  4. * A behavior to keep scopes and conditions across multiple methods and models DRY.
  5. *
  6. * Basic idea taken and modified/fixed from https://github.com/netguru/namedscopebehavior
  7. * and https://github.com/josegonzalez/cakephp-simple-scope
  8. *
  9. * - it's now "scope" instead of "scopes" (singular and now analogous to "contain" etc)
  10. * - corrected syntax, indentation
  11. * - reads the model's 'scopes' attribute if applicable
  12. * - allows 'scopes' in scopedFind()
  13. *
  14. * If used across models, it is adviced to load this globally via $actAs in the AppModel
  15. * (just as with Containable).
  16. *
  17. * In case you need to dynamically set the Model->scopes attribute, use the constructor:
  18. *
  19. * public function __construct($id = false, $table = null, $ds = null) {
  20. * $this->scopes = ...
  21. * parent::__construct($id, $table, $ds);
  22. * }
  23. *
  24. * The order is important since behaviors are loaded in the parent constructor.
  25. *
  26. * Note that it can be vital to use the model prefixes in the conditions and in the scopes
  27. * to avoid SQL errors or naming conflicts.
  28. *
  29. * See the test cases for more complex examples.
  30. *
  31. * @license MIT
  32. * @author Mark Scherer
  33. */
  34. class NamedScopeBehavior extends ModelBehavior {
  35. protected $_defaults = array(
  36. 'scope' => array(), // Container to hold all scopes
  37. 'attribute' => 'scopes', // Model attribute to hold the custom scopes
  38. 'findAttribute' => 'scopedFinds' // Model attribute to hold the custom finds
  39. );
  40. /**
  41. * Sets up the behavior including settings (i.e. scope).
  42. *
  43. * @param Model $Model
  44. * @param array $settings
  45. * @return void
  46. */
  47. public function setup(Model $Model, $settings = array()) {
  48. $attribute = !empty($settings['attribute']) ? $settings['attribute'] : $this->_defaults['attribute'];
  49. if (!empty($Model->$attribute)) {
  50. $settings['scope'] = !empty($settings['scope']) ? array_merge($Model->$attribute, $settings['scope']) : $Model->$attribute;
  51. }
  52. $this->settings[$Model->alias] = $settings + $this->_defaults;
  53. }
  54. /**
  55. * Triggered before the actual find.
  56. *
  57. * @param Model $Model
  58. * @param array $queryData
  59. * @return mixed
  60. */
  61. public function beforeFind(Model $Model, $queryData) {
  62. $scopes = array();
  63. // passed as scopes
  64. if (!empty($queryData['scope'])) {
  65. $scope = !is_array($queryData['scope']) ? array($queryData['scope']) : $queryData['scope'];
  66. $scopes = array_merge($scopes, $scope);
  67. }
  68. // passed as conditions['scope']
  69. if (is_array($queryData['conditions']) && !empty($queryData['conditions']['scope'])) {
  70. $scope = !is_array($queryData['conditions']['scope']) ? array($queryData['conditions']['scope']) : $queryData['conditions']['scope'];
  71. unset($queryData['conditions']['scope']);
  72. $scopes = array_merge($scopes, $scope);
  73. }
  74. // if there are scopes defined, we need to get rid of possible condition set earlier by find() method if model->id was set
  75. if (!empty($scopes) && !empty($Model->id) && !empty($queryData['conditions']["`{$Model->alias}`.`{$Model->primaryKey}`"]) && $queryData['conditions']["`{$Model->alias}`.`{$Model->primaryKey}`"] ==
  76. $Model->id) {
  77. unset($queryData['conditions']["`{$Model->alias}`.`{$Model->primaryKey}`"]);
  78. }
  79. $queryData['conditions'][] = $this->_conditions($scopes, $Model->alias);
  80. return $queryData;
  81. }
  82. /**
  83. * Set/get scopes.
  84. *
  85. * @param Model $Model
  86. * @param string $name
  87. * @param mixed $value
  88. * @return mixed
  89. */
  90. public function scope(Model $Model, $name = null, $value = null) {
  91. if ($name === null) {
  92. return $this->settings[$Model->alias]['scope'];
  93. }
  94. if (in_array($name, $this->settings[$Model->alias]['scope'])) {
  95. continue;
  96. }
  97. $this->settings[$Model->alias]['scope'][$name] = $value;
  98. }
  99. /**
  100. * Scoped find() with a specific key.
  101. *
  102. * If you need to switch the type, use the customConfig:
  103. * array('type' => 'count')
  104. * All active find methods are supported.
  105. *
  106. * @param mixed $Model
  107. * @param mixed $key
  108. * @param array $customConfig
  109. * @return mixed
  110. * @throws RuntimeException On invalid configs.
  111. */
  112. public function scopedFind(Model $Model, $key, array $customConfig = array()) {
  113. $attribute = $this->settings[$Model->alias]['findAttribute'];
  114. if (empty($Model->$attribute)) {
  115. throw new RuntimeException('No scopedFinds configs in ' . $Model->alias);
  116. }
  117. $finds = $Model->$attribute;
  118. if (empty($finds[$key])) {
  119. throw new RuntimeException('No scopedFinds configs in ' . $Model->alias . ' for the key ' . $key);
  120. }
  121. $config = $finds[$key];
  122. $config['find'] = array_merge_recursive($config['find'], $customConfig);
  123. if (!isset($config['find']['type'])) {
  124. $config['find']['type'] = 'all';
  125. }
  126. if (!empty($config['find']['virtualFields'])) {
  127. $Model->virtualFields = $config['find']['virtualFields'] + $Model->virtualFields;
  128. }
  129. if (!empty($config['find']['options']['contain']) && !$Model->Behaviors->loaded('Containable')) {
  130. $Model->Behaviors->load('Containable');
  131. }
  132. return $Model->find($config['find']['type'], $config['find']['options']);
  133. }
  134. /**
  135. * List all scoped find groups available.
  136. *
  137. * @param Model $Model
  138. * @return array
  139. */
  140. public function scopedFinds(Model $Model) {
  141. $attribute = $this->settings[$Model->alias]['findAttribute'];
  142. if (empty($Model->$attribute)) {
  143. return array();
  144. }
  145. $data = array();
  146. foreach ($Model->$attribute as $group => $config) {
  147. $data[$group] = $config['name'];
  148. }
  149. return $data;
  150. }
  151. /**
  152. * Resolves the scope names into their conditions.
  153. *
  154. * @param array $scopes
  155. * @param string $modelName
  156. * @return array
  157. */
  158. protected function _conditions(array $scopes, $modelName) {
  159. $conditions = array();
  160. foreach ($scopes as $scope) {
  161. if (strpos($scope, '.')) {
  162. list($scopeModel, $scope) = explode('.', $scope);
  163. } else {
  164. $scopeModel = $modelName;
  165. }
  166. if (!empty($this->settings[$scopeModel]['scope'][$scope])) {
  167. $conditions[] = array($this->settings[$scopeModel]['scope'][$scope]);
  168. }
  169. }
  170. return $conditions;
  171. }
  172. }