Skip to content

Instantly share code, notes, and snippets.

@mcordingley
Created July 13, 2018 18:00
Show Gist options
  • Save mcordingley/1e7291495866da9c113b6e30133976e6 to your computer and use it in GitHub Desktop.
Save mcordingley/1e7291495866da9c113b6e30133976e6 to your computer and use it in GitHub Desktop.
<?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