Event.php 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. <?php
  2. namespace Illuminate\Console\Scheduling;
  3. use Closure;
  4. use Cron\CronExpression;
  5. use Illuminate\Support\Arr;
  6. use Illuminate\Support\Carbon;
  7. use GuzzleHttp\Client as HttpClient;
  8. use Illuminate\Contracts\Mail\Mailer;
  9. use Symfony\Component\Process\Process;
  10. use Illuminate\Support\Traits\Macroable;
  11. use Illuminate\Contracts\Container\Container;
  12. class Event
  13. {
  14. use Macroable, ManagesFrequencies;
  15. /**
  16. * The command string.
  17. *
  18. * @var string
  19. */
  20. public $command;
  21. /**
  22. * The cron expression representing the event's frequency.
  23. *
  24. * @var string
  25. */
  26. public $expression = '* * * * *';
  27. /**
  28. * The timezone the date should be evaluated on.
  29. *
  30. * @var \DateTimeZone|string
  31. */
  32. public $timezone;
  33. /**
  34. * The user the command should run as.
  35. *
  36. * @var string
  37. */
  38. public $user;
  39. /**
  40. * The list of environments the command should run under.
  41. *
  42. * @var array
  43. */
  44. public $environments = [];
  45. /**
  46. * Indicates if the command should run in maintenance mode.
  47. *
  48. * @var bool
  49. */
  50. public $evenInMaintenanceMode = false;
  51. /**
  52. * Indicates if the command should not overlap itself.
  53. *
  54. * @var bool
  55. */
  56. public $withoutOverlapping = false;
  57. /**
  58. * Indicates if the command should only be allowed to run on one server for each cron expression.
  59. *
  60. * @var bool
  61. */
  62. public $onOneServer = false;
  63. /**
  64. * The amount of time the mutex should be valid.
  65. *
  66. * @var int
  67. */
  68. public $expiresAt = 1440;
  69. /**
  70. * Indicates if the command should run in background.
  71. *
  72. * @var bool
  73. */
  74. public $runInBackground = false;
  75. /**
  76. * The array of filter callbacks.
  77. *
  78. * @var array
  79. */
  80. protected $filters = [];
  81. /**
  82. * The array of reject callbacks.
  83. *
  84. * @var array
  85. */
  86. protected $rejects = [];
  87. /**
  88. * The location that output should be sent to.
  89. *
  90. * @var string
  91. */
  92. public $output = '/dev/null';
  93. /**
  94. * Indicates whether output should be appended.
  95. *
  96. * @var bool
  97. */
  98. public $shouldAppendOutput = false;
  99. /**
  100. * The array of callbacks to be run before the event is started.
  101. *
  102. * @var array
  103. */
  104. protected $beforeCallbacks = [];
  105. /**
  106. * The array of callbacks to be run after the event is finished.
  107. *
  108. * @var array
  109. */
  110. protected $afterCallbacks = [];
  111. /**
  112. * The human readable description of the event.
  113. *
  114. * @var string
  115. */
  116. public $description;
  117. /**
  118. * The event mutex implementation.
  119. *
  120. * @var \Illuminate\Console\Scheduling\EventMutex
  121. */
  122. public $mutex;
  123. /**
  124. * Create a new event instance.
  125. *
  126. * @param \Illuminate\Console\Scheduling\Mutex $mutex
  127. * @param string $command
  128. * @return void
  129. */
  130. public function __construct(EventMutex $mutex, $command)
  131. {
  132. $this->mutex = $mutex;
  133. $this->command = $command;
  134. $this->output = $this->getDefaultOutput();
  135. }
  136. /**
  137. * Get the default output depending on the OS.
  138. *
  139. * @return string
  140. */
  141. public function getDefaultOutput()
  142. {
  143. return (DIRECTORY_SEPARATOR == '\\') ? 'NUL' : '/dev/null';
  144. }
  145. /**
  146. * Run the given event.
  147. *
  148. * @param \Illuminate\Contracts\Container\Container $container
  149. * @return void
  150. */
  151. public function run(Container $container)
  152. {
  153. if ($this->withoutOverlapping &&
  154. ! $this->mutex->create($this)) {
  155. return;
  156. }
  157. $this->runInBackground
  158. ? $this->runCommandInBackground($container)
  159. : $this->runCommandInForeground($container);
  160. }
  161. /**
  162. * Get the mutex name for the scheduled command.
  163. *
  164. * @return string
  165. */
  166. public function mutexName()
  167. {
  168. return 'framework'.DIRECTORY_SEPARATOR.'schedule-'.sha1($this->expression.$this->command);
  169. }
  170. /**
  171. * Run the command in the foreground.
  172. *
  173. * @param \Illuminate\Contracts\Container\Container $container
  174. * @return void
  175. */
  176. protected function runCommandInForeground(Container $container)
  177. {
  178. $this->callBeforeCallbacks($container);
  179. (new Process(
  180. $this->buildCommand(), base_path(), null, null, null
  181. ))->run();
  182. $this->callAfterCallbacks($container);
  183. }
  184. /**
  185. * Run the command in the background.
  186. *
  187. * @param \Illuminate\Contracts\Container\Container $container
  188. * @return void
  189. */
  190. protected function runCommandInBackground(Container $container)
  191. {
  192. $this->callBeforeCallbacks($container);
  193. (new Process(
  194. $this->buildCommand(), base_path(), null, null, null
  195. ))->run();
  196. }
  197. /**
  198. * Call all of the "before" callbacks for the event.
  199. *
  200. * @param \Illuminate\Contracts\Container\Container $container
  201. * @return void
  202. */
  203. public function callBeforeCallbacks(Container $container)
  204. {
  205. foreach ($this->beforeCallbacks as $callback) {
  206. $container->call($callback);
  207. }
  208. }
  209. /**
  210. * Call all of the "after" callbacks for the event.
  211. *
  212. * @param \Illuminate\Contracts\Container\Container $container
  213. * @return void
  214. */
  215. public function callAfterCallbacks(Container $container)
  216. {
  217. foreach ($this->afterCallbacks as $callback) {
  218. $container->call($callback);
  219. }
  220. }
  221. /**
  222. * Build the command string.
  223. *
  224. * @return string
  225. */
  226. public function buildCommand()
  227. {
  228. return (new CommandBuilder)->buildCommand($this);
  229. }
  230. /**
  231. * Determine if the given event should run based on the Cron expression.
  232. *
  233. * @param \Illuminate\Contracts\Foundation\Application $app
  234. * @return bool
  235. */
  236. public function isDue($app)
  237. {
  238. if (! $this->runsInMaintenanceMode() && $app->isDownForMaintenance()) {
  239. return false;
  240. }
  241. return $this->expressionPasses() &&
  242. $this->runsInEnvironment($app->environment());
  243. }
  244. /**
  245. * Determine if the event runs in maintenance mode.
  246. *
  247. * @return bool
  248. */
  249. public function runsInMaintenanceMode()
  250. {
  251. return $this->evenInMaintenanceMode;
  252. }
  253. /**
  254. * Determine if the Cron expression passes.
  255. *
  256. * @return bool
  257. */
  258. protected function expressionPasses()
  259. {
  260. $date = Carbon::now();
  261. if ($this->timezone) {
  262. $date->setTimezone($this->timezone);
  263. }
  264. return CronExpression::factory($this->expression)->isDue($date->toDateTimeString());
  265. }
  266. /**
  267. * Determine if the event runs in the given environment.
  268. *
  269. * @param string $environment
  270. * @return bool
  271. */
  272. public function runsInEnvironment($environment)
  273. {
  274. return empty($this->environments) || in_array($environment, $this->environments);
  275. }
  276. /**
  277. * Determine if the filters pass for the event.
  278. *
  279. * @param \Illuminate\Contracts\Foundation\Application $app
  280. * @return bool
  281. */
  282. public function filtersPass($app)
  283. {
  284. foreach ($this->filters as $callback) {
  285. if (! $app->call($callback)) {
  286. return false;
  287. }
  288. }
  289. foreach ($this->rejects as $callback) {
  290. if ($app->call($callback)) {
  291. return false;
  292. }
  293. }
  294. return true;
  295. }
  296. /**
  297. * Send the output of the command to a given location.
  298. *
  299. * @param string $location
  300. * @param bool $append
  301. * @return $this
  302. */
  303. public function sendOutputTo($location, $append = false)
  304. {
  305. $this->output = $location;
  306. $this->shouldAppendOutput = $append;
  307. return $this;
  308. }
  309. /**
  310. * Append the output of the command to a given location.
  311. *
  312. * @param string $location
  313. * @return $this
  314. */
  315. public function appendOutputTo($location)
  316. {
  317. return $this->sendOutputTo($location, true);
  318. }
  319. /**
  320. * E-mail the results of the scheduled operation.
  321. *
  322. * @param array|mixed $addresses
  323. * @param bool $onlyIfOutputExists
  324. * @return $this
  325. *
  326. * @throws \LogicException
  327. */
  328. public function emailOutputTo($addresses, $onlyIfOutputExists = false)
  329. {
  330. $this->ensureOutputIsBeingCapturedForEmail();
  331. $addresses = Arr::wrap($addresses);
  332. return $this->then(function (Mailer $mailer) use ($addresses, $onlyIfOutputExists) {
  333. $this->emailOutput($mailer, $addresses, $onlyIfOutputExists);
  334. });
  335. }
  336. /**
  337. * E-mail the results of the scheduled operation if it produces output.
  338. *
  339. * @param array|mixed $addresses
  340. * @return $this
  341. *
  342. * @throws \LogicException
  343. */
  344. public function emailWrittenOutputTo($addresses)
  345. {
  346. return $this->emailOutputTo($addresses, true);
  347. }
  348. /**
  349. * Ensure that output is being captured for email.
  350. *
  351. * @return void
  352. */
  353. protected function ensureOutputIsBeingCapturedForEmail()
  354. {
  355. if (is_null($this->output) || $this->output == $this->getDefaultOutput()) {
  356. $this->sendOutputTo(storage_path('logs/schedule-'.sha1($this->mutexName()).'.log'));
  357. }
  358. }
  359. /**
  360. * E-mail the output of the event to the recipients.
  361. *
  362. * @param \Illuminate\Contracts\Mail\Mailer $mailer
  363. * @param array $addresses
  364. * @param bool $onlyIfOutputExists
  365. * @return void
  366. */
  367. protected function emailOutput(Mailer $mailer, $addresses, $onlyIfOutputExists = false)
  368. {
  369. $text = file_exists($this->output) ? file_get_contents($this->output) : '';
  370. if ($onlyIfOutputExists && empty($text)) {
  371. return;
  372. }
  373. $mailer->raw($text, function ($m) use ($addresses) {
  374. $m->to($addresses)->subject($this->getEmailSubject());
  375. });
  376. }
  377. /**
  378. * Get the e-mail subject line for output results.
  379. *
  380. * @return string
  381. */
  382. protected function getEmailSubject()
  383. {
  384. if ($this->description) {
  385. return $this->description;
  386. }
  387. return "Scheduled Job Output For [{$this->command}]";
  388. }
  389. /**
  390. * Register a callback to ping a given URL before the job runs.
  391. *
  392. * @param string $url
  393. * @return $this
  394. */
  395. public function pingBefore($url)
  396. {
  397. return $this->before(function () use ($url) {
  398. (new HttpClient)->get($url);
  399. });
  400. }
  401. /**
  402. * Register a callback to ping a given URL before the job runs if the given condition is true.
  403. *
  404. * @param bool $value
  405. * @param string $url
  406. * @return $this
  407. */
  408. public function pingBeforeIf($value, $url)
  409. {
  410. return $value ? $this->pingBefore($url) : $this;
  411. }
  412. /**
  413. * Register a callback to ping a given URL after the job runs.
  414. *
  415. * @param string $url
  416. * @return $this
  417. */
  418. public function thenPing($url)
  419. {
  420. return $this->then(function () use ($url) {
  421. (new HttpClient)->get($url);
  422. });
  423. }
  424. /**
  425. * Register a callback to ping a given URL after the job runs if the given condition is true.
  426. *
  427. * @param bool $value
  428. * @param string $url
  429. * @return $this
  430. */
  431. public function thenPingIf($value, $url)
  432. {
  433. return $value ? $this->thenPing($url) : $this;
  434. }
  435. /**
  436. * State that the command should run in background.
  437. *
  438. * @return $this
  439. */
  440. public function runInBackground()
  441. {
  442. $this->runInBackground = true;
  443. return $this;
  444. }
  445. /**
  446. * Set which user the command should run as.
  447. *
  448. * @param string $user
  449. * @return $this
  450. */
  451. public function user($user)
  452. {
  453. $this->user = $user;
  454. return $this;
  455. }
  456. /**
  457. * Limit the environments the command should run in.
  458. *
  459. * @param array|mixed $environments
  460. * @return $this
  461. */
  462. public function environments($environments)
  463. {
  464. $this->environments = is_array($environments) ? $environments : func_get_args();
  465. return $this;
  466. }
  467. /**
  468. * State that the command should run even in maintenance mode.
  469. *
  470. * @return $this
  471. */
  472. public function evenInMaintenanceMode()
  473. {
  474. $this->evenInMaintenanceMode = true;
  475. return $this;
  476. }
  477. /**
  478. * Do not allow the event to overlap each other.
  479. *
  480. * @param int $expiresAt
  481. * @return $this
  482. */
  483. public function withoutOverlapping($expiresAt = 1440)
  484. {
  485. $this->withoutOverlapping = true;
  486. $this->expiresAt = $expiresAt;
  487. return $this->then(function () {
  488. $this->mutex->forget($this);
  489. })->skip(function () {
  490. return $this->mutex->exists($this);
  491. });
  492. }
  493. /**
  494. * Allow the event to only run on one server for each cron expression.
  495. *
  496. * @return $this
  497. */
  498. public function onOneServer()
  499. {
  500. $this->onOneServer = true;
  501. return $this;
  502. }
  503. /**
  504. * Register a callback to further filter the schedule.
  505. *
  506. * @param \Closure|bool $callback
  507. * @return $this
  508. */
  509. public function when($callback)
  510. {
  511. $this->filters[] = is_callable($callback) ? $callback : function () use ($callback) {
  512. return $callback;
  513. };
  514. return $this;
  515. }
  516. /**
  517. * Register a callback to further filter the schedule.
  518. *
  519. * @param \Closure|bool $callback
  520. * @return $this
  521. */
  522. public function skip($callback)
  523. {
  524. $this->rejects[] = is_callable($callback) ? $callback : function () use ($callback) {
  525. return $callback;
  526. };
  527. return $this;
  528. }
  529. /**
  530. * Register a callback to be called before the operation.
  531. *
  532. * @param \Closure $callback
  533. * @return $this
  534. */
  535. public function before(Closure $callback)
  536. {
  537. $this->beforeCallbacks[] = $callback;
  538. return $this;
  539. }
  540. /**
  541. * Register a callback to be called after the operation.
  542. *
  543. * @param \Closure $callback
  544. * @return $this
  545. */
  546. public function after(Closure $callback)
  547. {
  548. return $this->then($callback);
  549. }
  550. /**
  551. * Register a callback to be called after the operation.
  552. *
  553. * @param \Closure $callback
  554. * @return $this
  555. */
  556. public function then(Closure $callback)
  557. {
  558. $this->afterCallbacks[] = $callback;
  559. return $this;
  560. }
  561. /**
  562. * Set the human-friendly description of the event.
  563. *
  564. * @param string $description
  565. * @return $this
  566. */
  567. public function name($description)
  568. {
  569. return $this->description($description);
  570. }
  571. /**
  572. * Set the human-friendly description of the event.
  573. *
  574. * @param string $description
  575. * @return $this
  576. */
  577. public function description($description)
  578. {
  579. $this->description = $description;
  580. return $this;
  581. }
  582. /**
  583. * Get the summary of the event for display.
  584. *
  585. * @return string
  586. */
  587. public function getSummaryForDisplay()
  588. {
  589. if (is_string($this->description)) {
  590. return $this->description;
  591. }
  592. return $this->buildCommand();
  593. }
  594. /**
  595. * Determine the next due date for an event.
  596. *
  597. * @param \DateTime|string $currentTime
  598. * @param int $nth
  599. * @param bool $allowCurrentDate
  600. * @return \Illuminate\Support\Carbon
  601. */
  602. public function nextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
  603. {
  604. return Carbon::instance(CronExpression::factory(
  605. $this->getExpression()
  606. )->getNextRunDate($currentTime, $nth, $allowCurrentDate));
  607. }
  608. /**
  609. * Get the Cron expression for the event.
  610. *
  611. * @return string
  612. */
  613. public function getExpression()
  614. {
  615. return $this->expression;
  616. }
  617. /**
  618. * Set the event mutex implementation to be used.
  619. *
  620. * @param \Illuminate\Console\Scheduling\EventMutex $mutex
  621. * @return $this
  622. */
  623. public function preventOverlapsUsing(EventMutex $mutex)
  624. {
  625. $this->mutex = $mutex;
  626. return $this;
  627. }
  628. }