Created
July 13, 2018 18:00
-
-
Save mcordingley/1e7291495866da9c113b6e30133976e6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Models\Traits; | |
use Carbon\Carbon; | |
use Cron\CronExpression; | |
use InvalidArgumentException; | |
/** | |
* @property CronExpression|null $cron_expression | |
* @property Carbon|null $ends_at | |
* @property int|null $remaining | |
* @property bool $recurring | |
* @property string|null $schedule_recurrence_pattern | |
* @property Carbon|null $schedule_runs_next_at | |
* @property Carbon|null $starts_at | |
*/ | |
trait Schedulable | |
{ | |
/** | |
* @return void | |
*/ | |
final public function runScheduleIfDue() | |
{ | |
if ($this->scheduleIsDue()) { | |
$this->run(); | |
if ($this->scheduleIsDeletable()) { | |
$this->delete(); | |
} | |
} | |
} | |
/** | |
* @return bool | |
*/ | |
final protected function scheduleIsDue(): bool | |
{ | |
return $this->scheduleHasStarted() | |
&& !$this->scheduleHasEnded() | |
&& $this->scheduleHasRemainingRuns() | |
&& $this->scheduleMatchesCron(); | |
} | |
/** | |
* @return bool | |
*/ | |
final protected function scheduleHasStarted(): bool | |
{ | |
return !$this->starts_at || $this->starts_at->lte(Carbon::now()); | |
} | |
/** | |
* @return bool | |
*/ | |
final protected function scheduleHasEnded(): bool | |
{ | |
return $this->ends_at && $this->ends_at->lte(Carbon::now()); | |
} | |
/** | |
* @return bool | |
*/ | |
final protected function scheduleHasRemainingRuns(): bool | |
{ | |
return $this->remaining === null || $this->remaining > 0; | |
} | |
/** | |
* @return bool | |
*/ | |
final protected function scheduleMatchesCron(): bool | |
{ | |
return !$this->getCronExpressionAttribute() || $this->getCronExpressionAttribute()->isDue(); | |
} | |
/** | |
* @return CronExpression|null | |
*/ | |
final public function getCronExpressionAttribute() | |
{ | |
if (!isset($this->attributes['cron_expression']) || !$this->attributes['cron_expression']) { | |
return null; | |
} | |
try { | |
return CronExpression::factory($this->attributes['cron_expression']); | |
} catch (InvalidArgumentException $exception) { | |
// Invalid CRON expression | |
return null; | |
} | |
} | |
/** | |
* @return bool | |
*/ | |
final public function getRecurringAttribute(): bool | |
{ | |
return isset($this->attributes['cron_expression']); | |
} | |
abstract protected function delete(); | |
/** | |
* @return string|null | |
*/ | |
final public function getScheduleRecurrencePatternAttribute() | |
{ | |
if (!isset($this->attributes['cron_expression'])) { | |
return null; | |
} | |
$cron = $this->attributes['cron_expression']; | |
if ($cron === '* * * * * *') { | |
return 'immediately'; | |
} | |
if (preg_match('/\S+ \S+ \* \* \* \*/', $cron)) { | |
return 'daily'; | |
} | |
if (preg_match('/\S+ \S+ \* \* \S+ \*/', $cron)) { | |
return 'weekly'; | |
} | |
if (preg_match('/\S+ \S+ \S+ \* \* \*/', $cron)) { | |
return 'monthly'; | |
} | |
return 'custom'; | |
} | |
/** | |
* @return Carbon|null | |
*/ | |
final public function getScheduleRunsNextAtAttribute() | |
{ | |
$now = Carbon::now(); | |
if ($this->remaining === 0 || $this->ends_at && $this->ends_at < $now) { | |
return null; | |
} | |
/** @var CronExpression $cron */ | |
$cron = $this->getCronExpressionAttribute(); | |
if (!$cron) { | |
return null; | |
} | |
/** @var Carbon $relativeDate */ | |
$relativeDate = $this->starts_at && $this->starts_at > $now ? $this->starts_at : $now; | |
return Carbon::instance($cron->getNextRunDate($relativeDate, 0, true)); | |
} | |
/** | |
* @param string $recurrencePattern One of 'daily', 'hourly', 'minutely', 'monthly', 'weekly', or 'yearly' | |
* @param Carbon|null $relativeTo Defaults to now. | |
* @param int|null $step Run only on every nth time of the selected interval. Note: Not available for 'weekly'. | |
* @throws InvalidArgumentException | |
*/ | |
final public function setCronExpressionRecurrence(string $recurrencePattern, Carbon $relativeTo = null, int $step = null) | |
{ | |
if (!$relativeTo) { | |
$relativeTo = $this->starts_at ?? Carbon::now(); | |
} | |
$cronParts = ['*', '*', '*', '*', '*', '*']; | |
$step = abs($step); | |
$suffix = $step && $step > 1 ? '/' . $step : ''; | |
switch ($recurrencePattern) { | |
case 'daily': | |
$cronParts[0] = $relativeTo->minute; | |
$cronParts[1] = $relativeTo->hour; | |
$cronParts[2] .= $suffix; | |
break; | |
case 'hourly': | |
$cronParts[0] = $relativeTo->minute; | |
$cronParts[1] .= $suffix; | |
break; | |
case 'minutely': | |
$cronParts[0] .= $suffix; | |
break; | |
case 'monthly': | |
$cronParts[0] = $relativeTo->minute; | |
$cronParts[1] = $relativeTo->hour; | |
$cronParts[2] = $relativeTo->day; | |
$cronParts[3] .= $suffix; | |
break; | |
case 'weekly': | |
$cronParts[0] = $relativeTo->minute; | |
$cronParts[1] = $relativeTo->hour; | |
$cronParts[4] = $relativeTo->dayOfWeek; | |
break; | |
case 'yearly': | |
$cronParts[0] = $relativeTo->minute; | |
$cronParts[1] = $relativeTo->hour; | |
$cronParts[2] = $relativeTo->day; | |
$cronParts[3] = $relativeTo->month; | |
$cronParts[5] .= $suffix; | |
break; | |
default: | |
throw new InvalidArgumentException('Invalid recurrence pattern specified.'); | |
} | |
$this->attributes['cron_expression'] = implode(' ', $cronParts); | |
} | |
abstract protected function run(); | |
/** | |
* @return bool | |
*/ | |
public function scheduleIsDeletable(): bool | |
{ | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment