ToggleBehavior.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. namespace Tools\Model\Behavior;
  3. use ArrayObject;
  4. use Cake\Datasource\EntityInterface;
  5. use Cake\Event\Event;
  6. use Cake\ORM\Behavior;
  7. use LogicException;
  8. /**
  9. * ToggleBehavior
  10. *
  11. * An implementation of a unique field toggle per table or scope.
  12. * This will ensure that on a set of records only one can be a "primary" one, setting the others to false then.
  13. * On delete it will give the primary status to another record if applicable.
  14. *
  15. * @author Mark Scherer
  16. * @license MIT
  17. */
  18. class ToggleBehavior extends Behavior {
  19. /**
  20. * Default config
  21. *
  22. * @var array
  23. */
  24. protected $_defaultConfig = [
  25. 'field' => 'primary',
  26. 'on' => 'afterSave', // afterSave (without transactions) or beforeSave (with transactions)
  27. 'scopeFields' => [],
  28. 'scope' => [],
  29. 'findOrder' => null // null = autodetect modified/created
  30. ];
  31. /**
  32. * @param \Cake\Event\Event $event
  33. * @param \Cake\Datasource\EntityInterface $entity
  34. * @param \ArrayObject $options
  35. *
  36. * @return void
  37. */
  38. public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options) {
  39. $field = $this->getConfig('field');
  40. $value = $entity->get($field);
  41. if (!$value) {
  42. return;
  43. }
  44. $conditions = $this->buildConditions($entity);
  45. $order = $this->getConfig('findOrder');
  46. if ($order === null) {
  47. $order = [];
  48. if ($this->_table->getSchema()->getColumn('modified')) {
  49. $order['modified'] = 'DESC';
  50. }
  51. }
  52. $entity = $this->_table->find()->where($conditions)->order($order)->first();
  53. if (!$entity) {
  54. // This should be caught with a validation rule if at least one "primary" must exist
  55. return;
  56. }
  57. $entity->set($field, true);
  58. $this->_table->saveOrFail($entity);
  59. }
  60. /**
  61. * @param \Cake\Event\Event $event
  62. * @param \Cake\Datasource\EntityInterface $entity
  63. * @param \ArrayObject $options
  64. * @return void
  65. * @throws \LogicException
  66. */
  67. public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) {
  68. $field = $this->getConfig('field');
  69. if ($entity->isNew() && !$entity->get($field)) {
  70. if (!$this->getCurrent($entity)) {
  71. $entity->set($field, true);
  72. }
  73. }
  74. if ($this->_config['on'] !== 'beforeSave') {
  75. return;
  76. }
  77. $value = $entity->get($this->getConfig('field'));
  78. if (!$value && !$this->getCurrent($entity)) {
  79. // This should be caught with a validation rule as this is not normal behavior
  80. throw new LogicException();
  81. }
  82. $this->removeFromOthers($entity);
  83. }
  84. /**
  85. * @param \Cake\Event\Event $event
  86. * @param \Cake\Datasource\EntityInterface $entity
  87. * @param \ArrayObject $options
  88. *
  89. * @return void
  90. *
  91. * @throws \LogicException
  92. */
  93. public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) {
  94. if ($this->_config['on'] !== 'afterSave') {
  95. return;
  96. }
  97. if (!$entity->isDirty($this->getConfig('field'))) {
  98. return;
  99. }
  100. $value = $entity->get($this->getConfig('field'));
  101. if (!$value && !$this->getCurrent($entity)) {
  102. // This should be caught with a validation rule as this is not normal behavior
  103. throw new LogicException();
  104. }
  105. $this->removeFromOthers($entity);
  106. }
  107. /**
  108. * @param \Cake\Datasource\EntityInterface $entity
  109. *
  110. * @return \Cake\Datasource\EntityInterface|null
  111. */
  112. protected function getCurrent(EntityInterface $entity) {
  113. $conditions = $this->buildConditions($entity);
  114. return $this->_table->find()
  115. ->where($conditions)
  116. ->first();
  117. }
  118. /**
  119. * @param \Cake\Datasource\EntityInterface $entity
  120. *
  121. * @return void
  122. */
  123. protected function removeFromOthers(EntityInterface $entity) {
  124. $field = $this->getConfig('field');
  125. $id = $entity->get('id');
  126. $conditions = $this->buildConditions($entity);
  127. $this->_table->updateAll([$field => false], ['id !=' => $id] + $conditions);
  128. }
  129. /**
  130. * @param \Cake\Datasource\EntityInterface $entity
  131. *
  132. * @return array
  133. */
  134. protected function buildConditions(EntityInterface $entity) {
  135. $conditions = $this->getConfig('scope');
  136. $scopeFields = (array)$this->getConfig('scopeFields');
  137. foreach ($scopeFields as $scopeField) {
  138. $conditions[$scopeField] = $entity->get($scopeField);
  139. }
  140. return $conditions;
  141. }
  142. /**
  143. * @param \Cake\Datasource\EntityInterface $entity
  144. *
  145. * @return bool
  146. */
  147. public function toggleField(EntityInterface $entity) {
  148. $field = $this->getConfig('field');
  149. $id = $entity->get('id');
  150. $conditions = $this->buildConditions($entity);
  151. $primary = $this->_table->updateAll([$field => true], ['id' => $id] + $conditions);
  152. $others = $this->_table->updateAll([$field => false], ['id !=' => $id] + $conditions);
  153. return $primary + $others > 0;
  154. }
  155. }