ToggleBehavior.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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 Symfony\Component\Console\Exception\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->schema()->column('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. */
  66. public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) {
  67. $field = $this->getConfig('field');
  68. if ($entity->isNew() && !$entity->get($field)) {
  69. if (!$this->getCurrent($entity)) {
  70. $entity->set($field, true);
  71. }
  72. }
  73. if ($this->_config['on'] !== 'beforeSave') {
  74. return;
  75. }
  76. $value = $entity->get($this->getConfig('field'));
  77. if (!$value && !$this->getCurrent($entity)) {
  78. // This should be caught with a validation rule as this is not normal behavior
  79. throw new LogicException();
  80. }
  81. $this->removeFromOthers($entity);
  82. }
  83. /**
  84. * @param \Cake\Event\Event $event
  85. * @param \Cake\Datasource\EntityInterface $entity
  86. * @param \ArrayObject $options
  87. *
  88. * @return void
  89. */
  90. public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) {
  91. if ($this->_config['on'] !== 'afterSave') {
  92. return;
  93. }
  94. if (!$entity->dirty($this->getConfig('field'))) {
  95. return;
  96. }
  97. $value = $entity->get($this->getConfig('field'));
  98. if (!$value && !$this->getCurrent($entity)) {
  99. // This should be caught with a validation rule as this is not normal behavior
  100. throw new LogicException();
  101. }
  102. $this->removeFromOthers($entity);
  103. }
  104. /**
  105. * @param \Cake\Datasource\EntityInterface $entity
  106. *
  107. * @return \Cake\Datasource\EntityInterface|null
  108. */
  109. protected function getCurrent(EntityInterface $entity) {
  110. $conditions = $this->buildConditions($entity);
  111. return $this->_table->find()
  112. ->where($conditions)
  113. ->first();
  114. }
  115. /**
  116. * @param \Cake\Datasource\EntityInterface $entity
  117. *
  118. * @return void
  119. */
  120. protected function removeFromOthers(EntityInterface $entity) {
  121. $field = $this->getConfig('field');
  122. $id = $entity->get('id');
  123. $conditions = $this->buildConditions($entity);
  124. $this->_table->updateAll([$field => false], ['id !=' => $id] + $conditions);
  125. }
  126. /**
  127. * @param \Cake\Datasource\EntityInterface $entity
  128. *
  129. * @return array
  130. */
  131. protected function buildConditions(EntityInterface $entity) {
  132. $conditions = $this->getConfig('scope');
  133. $scopeFields = (array)$this->getConfig('scopeFields');
  134. foreach ($scopeFields as $scopeField) {
  135. $conditions[$scopeField] = $entity->get($scopeField);
  136. }
  137. return $conditions;
  138. }
  139. /**
  140. * @param \Cake\Datasource\EntityInterface $entity
  141. *
  142. * @return bool
  143. */
  144. public function toggleField(EntityInterface $entity) {
  145. $field = $this->getConfig('field');
  146. $id = $entity->get('id');
  147. $conditions = $this->buildConditions($entity);
  148. $primary = $this->_table->updateAll([$field => true], ['id' => $id] + $conditions);
  149. $others = $this->_table->updateAll([$field => false], ['id !=' => $id] + $conditions);
  150. return $primary + $others > 0;
  151. }
  152. }