TimestampBehavior.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\ORM\Behavior;
  16. use Cake\Datasource\EntityInterface;
  17. use Cake\Event\Event;
  18. use Cake\I18n\Time;
  19. use Cake\ORM\Behavior;
  20. use DateTime;
  21. use UnexpectedValueException;
  22. class TimestampBehavior extends Behavior
  23. {
  24. /**
  25. * Default config
  26. *
  27. * These are merged with user-provided config when the behavior is used.
  28. *
  29. * events - an event-name keyed array of which fields to update, and when, for a given event
  30. * possible values for when a field will be updated are "always", "new" or "existing", to set
  31. * the field value always, only when a new record or only when an existing record.
  32. *
  33. * refreshTimestamp - if true (the default) the timestamp used will be the current time when
  34. * the code is executed, to set to an explicit date time value - set refreshTimetamp to false
  35. * and call setTimestamp() on the behavior class before use.
  36. *
  37. * @var array
  38. */
  39. protected $_defaultConfig = [
  40. 'implementedFinders' => [],
  41. 'implementedMethods' => [
  42. 'timestamp' => 'timestamp',
  43. 'touch' => 'touch'
  44. ],
  45. 'events' => [
  46. 'Model.beforeSave' => [
  47. 'created' => 'new',
  48. 'modified' => 'always'
  49. ]
  50. ],
  51. 'refreshTimestamp' => true
  52. ];
  53. /**
  54. * Current timestamp
  55. *
  56. * @var \DateTime
  57. */
  58. protected $_ts;
  59. /**
  60. * Initialize hook
  61. *
  62. * If events are specified - do *not* merge them with existing events,
  63. * overwrite the events to listen on
  64. *
  65. * @param array $config The config for this behavior.
  66. * @return void
  67. */
  68. public function initialize(array $config)
  69. {
  70. if (isset($config['events'])) {
  71. $this->setConfig('events', $config['events'], false);
  72. }
  73. }
  74. /**
  75. * There is only one event handler, it can be configured to be called for any event
  76. *
  77. * @param \Cake\Event\Event $event Event instance.
  78. * @param \Cake\Datasource\EntityInterface $entity Entity instance.
  79. * @throws \UnexpectedValueException if a field's when value is misdefined
  80. * @return bool Returns true irrespective of the behavior logic, the save will not be prevented.
  81. * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing'
  82. */
  83. public function handleEvent(Event $event, EntityInterface $entity)
  84. {
  85. $eventName = $event->getName();
  86. $events = $this->_config['events'];
  87. $new = $entity->isNew() !== false;
  88. $refresh = $this->_config['refreshTimestamp'];
  89. foreach ($events[$eventName] as $field => $when) {
  90. if (!in_array($when, ['always', 'new', 'existing'])) {
  91. throw new UnexpectedValueException(
  92. sprintf('When should be one of "always", "new" or "existing". The passed value "%s" is invalid', $when)
  93. );
  94. }
  95. if ($when === 'always' ||
  96. ($when === 'new' && $new) ||
  97. ($when === 'existing' && !$new)
  98. ) {
  99. $this->_updateField($entity, $field, $refresh);
  100. }
  101. }
  102. return true;
  103. }
  104. /**
  105. * implementedEvents
  106. *
  107. * The implemented events of this behavior depend on configuration
  108. *
  109. * @return array
  110. */
  111. public function implementedEvents()
  112. {
  113. return array_fill_keys(array_keys($this->_config['events']), 'handleEvent');
  114. }
  115. /**
  116. * Get or set the timestamp to be used
  117. *
  118. * Set the timestamp to the given DateTime object, or if not passed a new DateTime object
  119. * If an explicit date time is passed, the config option `refreshTimestamp` is
  120. * automatically set to false.
  121. *
  122. * @param \DateTime|null $ts Timestamp
  123. * @param bool $refreshTimestamp If true timestamp is refreshed.
  124. * @return \DateTime
  125. */
  126. public function timestamp(DateTime $ts = null, $refreshTimestamp = false)
  127. {
  128. if ($ts) {
  129. if ($this->_config['refreshTimestamp']) {
  130. $this->_config['refreshTimestamp'] = false;
  131. }
  132. $this->_ts = new Time($ts);
  133. } elseif ($this->_ts === null || $refreshTimestamp) {
  134. $this->_ts = new Time();
  135. }
  136. return $this->_ts;
  137. }
  138. /**
  139. * Touch an entity
  140. *
  141. * Bumps timestamp fields for an entity. For any fields configured to be updated
  142. * "always" or "existing", update the timestamp value. This method will overwrite
  143. * any pre-existing value.
  144. *
  145. * @param \Cake\Datasource\EntityInterface $entity Entity instance.
  146. * @param string $eventName Event name.
  147. * @return bool true if a field is updated, false if no action performed
  148. */
  149. public function touch(EntityInterface $entity, $eventName = 'Model.beforeSave')
  150. {
  151. $events = $this->_config['events'];
  152. if (empty($events[$eventName])) {
  153. return false;
  154. }
  155. $return = false;
  156. $refresh = $this->_config['refreshTimestamp'];
  157. foreach ($events[$eventName] as $field => $when) {
  158. if (in_array($when, ['always', 'existing'])) {
  159. $return = true;
  160. $entity->setDirty($field, false);
  161. $this->_updateField($entity, $field, $refresh);
  162. }
  163. }
  164. return $return;
  165. }
  166. /**
  167. * Update a field, if it hasn't been updated already
  168. *
  169. * @param \Cake\Datasource\EntityInterface $entity Entity instance.
  170. * @param string $field Field name
  171. * @param bool $refreshTimestamp Whether to refresh timestamp.
  172. * @return void
  173. */
  174. protected function _updateField($entity, $field, $refreshTimestamp)
  175. {
  176. if ($entity->isDirty($field)) {
  177. return;
  178. }
  179. $entity->set($field, $this->timestamp(null, $refreshTimestamp));
  180. }
  181. }