Schedule.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Cron\Model;
  7. use Magento\Framework\Exception\CronException;
  8. use Magento\Framework\App\ObjectManager;
  9. use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
  10. /**
  11. * Crontab schedule model
  12. *
  13. * @method string getJobCode()
  14. * @method \Magento\Cron\Model\Schedule setJobCode(string $value)
  15. * @method string getStatus()
  16. * @method \Magento\Cron\Model\Schedule setStatus(string $value)
  17. * @method string getMessages()
  18. * @method \Magento\Cron\Model\Schedule setMessages(string $value)
  19. * @method string getCreatedAt()
  20. * @method \Magento\Cron\Model\Schedule setCreatedAt(string $value)
  21. * @method string getScheduledAt()
  22. * @method \Magento\Cron\Model\Schedule setScheduledAt(string $value)
  23. * @method string getExecutedAt()
  24. * @method \Magento\Cron\Model\Schedule setExecutedAt(string $value)
  25. * @method string getFinishedAt()
  26. * @method \Magento\Cron\Model\Schedule setFinishedAt(string $value)
  27. * @method array getCronExprArr()
  28. * @method \Magento\Cron\Model\Schedule setCronExprArr(array $value)
  29. *
  30. * @api
  31. * @since 100.0.2
  32. */
  33. class Schedule extends \Magento\Framework\Model\AbstractModel
  34. {
  35. const STATUS_PENDING = 'pending';
  36. const STATUS_RUNNING = 'running';
  37. const STATUS_SUCCESS = 'success';
  38. const STATUS_MISSED = 'missed';
  39. const STATUS_ERROR = 'error';
  40. /**
  41. * @var TimezoneInterface
  42. */
  43. private $timezoneConverter;
  44. /**
  45. * @param \Magento\Framework\Model\Context $context
  46. * @param \Magento\Framework\Registry $registry
  47. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  48. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  49. * @param array $data
  50. * @param TimezoneInterface $timezoneConverter
  51. */
  52. public function __construct(
  53. \Magento\Framework\Model\Context $context,
  54. \Magento\Framework\Registry $registry,
  55. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  56. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  57. array $data = [],
  58. TimezoneInterface $timezoneConverter = null
  59. ) {
  60. parent::__construct($context, $registry, $resource, $resourceCollection, $data);
  61. $this->timezoneConverter = $timezoneConverter ?: ObjectManager::getInstance()->get(TimezoneInterface::class);
  62. }
  63. /**
  64. * @inheritdoc
  65. */
  66. public function _construct()
  67. {
  68. $this->_init(\Magento\Cron\Model\ResourceModel\Schedule::class);
  69. }
  70. /**
  71. * Set cron expression.
  72. *
  73. * @param string $expr
  74. * @return $this
  75. * @throws \Magento\Framework\Exception\CronException
  76. */
  77. public function setCronExpr($expr)
  78. {
  79. $e = preg_split('#\s+#', $expr, null, PREG_SPLIT_NO_EMPTY);
  80. if (sizeof($e) < 5 || sizeof($e) > 6) {
  81. throw new CronException(__('Invalid cron expression: %1', $expr));
  82. }
  83. $this->setCronExprArr($e);
  84. return $this;
  85. }
  86. /**
  87. * Checks the observer's cron expression against time.
  88. *
  89. * Supports $this->setCronExpr('* 0-5,10-59/5 2-10,15-25 january-june/2 mon-fri')
  90. *
  91. * @return bool
  92. */
  93. public function trySchedule()
  94. {
  95. $time = $this->getScheduledAt();
  96. $e = $this->getCronExprArr();
  97. if (!$e || !$time) {
  98. return false;
  99. }
  100. if (!is_numeric($time)) {
  101. //convert time from UTC to admin store timezone
  102. //we assume that all schedules in configuration (crontab.xml and DB tables) are in admin store timezone
  103. $time = $this->timezoneConverter->date($time)->format('Y-m-d H:i');
  104. $time = strtotime($time);
  105. }
  106. $match = $this->matchCronExpression($e[0], strftime('%M', $time))
  107. && $this->matchCronExpression($e[1], strftime('%H', $time))
  108. && $this->matchCronExpression($e[2], strftime('%d', $time))
  109. && $this->matchCronExpression($e[3], strftime('%m', $time))
  110. && $this->matchCronExpression($e[4], strftime('%w', $time));
  111. return $match;
  112. }
  113. /**
  114. * Match cron expression.
  115. *
  116. * @param string $expr
  117. * @param int $num
  118. * @return bool
  119. * @throws \Magento\Framework\Exception\CronException
  120. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  121. * @SuppressWarnings(PHPMD.NPathComplexity)
  122. */
  123. public function matchCronExpression($expr, $num)
  124. {
  125. // handle ALL match
  126. if ($expr === '*') {
  127. return true;
  128. }
  129. // handle multiple options
  130. if (strpos($expr, ',') !== false) {
  131. foreach (explode(',', $expr) as $e) {
  132. if ($this->matchCronExpression($e, $num)) {
  133. return true;
  134. }
  135. }
  136. return false;
  137. }
  138. // handle modulus
  139. if (strpos($expr, '/') !== false) {
  140. $e = explode('/', $expr);
  141. if (sizeof($e) !== 2) {
  142. throw new CronException(__('Invalid cron expression, expecting \'match/modulus\': %1', $expr));
  143. }
  144. if (!is_numeric($e[1])) {
  145. throw new CronException(__('Invalid cron expression, expecting numeric modulus: %1', $expr));
  146. }
  147. $expr = $e[0];
  148. $mod = $e[1];
  149. } else {
  150. $mod = 1;
  151. }
  152. // handle all match by modulus
  153. if ($expr === '*') {
  154. $from = 0;
  155. $to = 60;
  156. } elseif (strpos($expr, '-') !== false) {
  157. // handle range
  158. $e = explode('-', $expr);
  159. if (sizeof($e) !== 2) {
  160. throw new CronException(__('Invalid cron expression, expecting \'from-to\' structure: %1', $expr));
  161. }
  162. $from = $this->getNumeric($e[0]);
  163. $to = $this->getNumeric($e[1]);
  164. } else {
  165. // handle regular token
  166. $from = $this->getNumeric($expr);
  167. $to = $from;
  168. }
  169. if ($from === false || $to === false) {
  170. throw new CronException(__('Invalid cron expression: %1', $expr));
  171. }
  172. return $num >= $from && $num <= $to && $num % $mod === 0;
  173. }
  174. /**
  175. * Get number of a month.
  176. *
  177. * @param int|string $value
  178. * @return bool|int|string
  179. */
  180. public function getNumeric($value)
  181. {
  182. static $data = [
  183. 'jan' => 1,
  184. 'feb' => 2,
  185. 'mar' => 3,
  186. 'apr' => 4,
  187. 'may' => 5,
  188. 'jun' => 6,
  189. 'jul' => 7,
  190. 'aug' => 8,
  191. 'sep' => 9,
  192. 'oct' => 10,
  193. 'nov' => 11,
  194. 'dec' => 12,
  195. 'sun' => 0,
  196. 'mon' => 1,
  197. 'tue' => 2,
  198. 'wed' => 3,
  199. 'thu' => 4,
  200. 'fri' => 5,
  201. 'sat' => 6,
  202. ];
  203. if (is_numeric($value)) {
  204. return $value;
  205. }
  206. if (is_string($value)) {
  207. $value = strtolower(substr($value, 0, 3));
  208. if (isset($data[$value])) {
  209. return $data[$value];
  210. }
  211. }
  212. return false;
  213. }
  214. /**
  215. * Lock the cron job so no other scheduled instances run simultaneously.
  216. *
  217. * Sets a job to STATUS_RUNNING only if it is currently in STATUS_PENDING
  218. * and no other jobs of the same code are currently in STATUS_RUNNING.
  219. * Returns true if status was changed and false otherwise.
  220. *
  221. * @return boolean
  222. */
  223. public function tryLockJob()
  224. {
  225. if ($this->_getResource()->trySetJobUniqueStatusAtomic(
  226. $this->getId(),
  227. self::STATUS_RUNNING,
  228. self::STATUS_PENDING
  229. )) {
  230. $this->setStatus(self::STATUS_RUNNING);
  231. return true;
  232. }
  233. return false;
  234. }
  235. }