678 lines
15 KiB
PHP
678 lines
15 KiB
PHP
<?php
|
|
|
|
namespace Illuminate\Console\Scheduling;
|
|
|
|
use Closure;
|
|
use Cron\CronExpression;
|
|
use Illuminate\Support\Carbon;
|
|
use GuzzleHttp\Client as HttpClient;
|
|
use Illuminate\Contracts\Mail\Mailer;
|
|
use Symfony\Component\Process\Process;
|
|
use Illuminate\Support\Traits\Macroable;
|
|
use Illuminate\Contracts\Container\Container;
|
|
|
|
class Event
|
|
{
|
|
use Macroable, ManagesFrequencies;
|
|
|
|
/**
|
|
* The command string.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $command;
|
|
|
|
/**
|
|
* The cron expression representing the event's frequency.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $expression = '* * * * * *';
|
|
|
|
/**
|
|
* The timezone the date should be evaluated on.
|
|
*
|
|
* @var \DateTimeZone|string
|
|
*/
|
|
public $timezone;
|
|
|
|
/**
|
|
* The user the command should run as.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $user;
|
|
|
|
/**
|
|
* The list of environments the command should run under.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $environments = [];
|
|
|
|
/**
|
|
* Indicates if the command should run in maintenance mode.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $evenInMaintenanceMode = false;
|
|
|
|
/**
|
|
* Indicates if the command should not overlap itself.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $withoutOverlapping = false;
|
|
|
|
/**
|
|
* The amount of time the mutex should be valid.
|
|
*
|
|
* @var int
|
|
*/
|
|
public $expiresAt = 1440;
|
|
|
|
/**
|
|
* Indicates if the command should run in background.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $runInBackground = false;
|
|
|
|
/**
|
|
* The array of filter callbacks.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $filters = [];
|
|
|
|
/**
|
|
* The array of reject callbacks.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $rejects = [];
|
|
|
|
/**
|
|
* The location that output should be sent to.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $output = '/dev/null';
|
|
|
|
/**
|
|
* Indicates whether output should be appended.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $shouldAppendOutput = false;
|
|
|
|
/**
|
|
* The array of callbacks to be run before the event is started.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $beforeCallbacks = [];
|
|
|
|
/**
|
|
* The array of callbacks to be run after the event is finished.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $afterCallbacks = [];
|
|
|
|
/**
|
|
* The human readable description of the event.
|
|
*
|
|
* @var string
|
|
*/
|
|
public $description;
|
|
|
|
/**
|
|
* The mutex implementation.
|
|
*
|
|
* @var \Illuminate\Console\Scheduling\Mutex
|
|
*/
|
|
public $mutex;
|
|
|
|
/**
|
|
* Create a new event instance.
|
|
*
|
|
* @param \Illuminate\Console\Scheduling\Mutex $mutex
|
|
* @param string $command
|
|
* @return void
|
|
*/
|
|
public function __construct(Mutex $mutex, $command)
|
|
{
|
|
$this->mutex = $mutex;
|
|
$this->command = $command;
|
|
$this->output = $this->getDefaultOutput();
|
|
}
|
|
|
|
/**
|
|
* Get the default output depending on the OS.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDefaultOutput()
|
|
{
|
|
return (DIRECTORY_SEPARATOR == '\\') ? 'NUL' : '/dev/null';
|
|
}
|
|
|
|
/**
|
|
* Run the given event.
|
|
*
|
|
* @param \Illuminate\Contracts\Container\Container $container
|
|
* @return void
|
|
*/
|
|
public function run(Container $container)
|
|
{
|
|
if ($this->withoutOverlapping &&
|
|
! $this->mutex->create($this)) {
|
|
return;
|
|
}
|
|
|
|
$this->runInBackground
|
|
? $this->runCommandInBackground($container)
|
|
: $this->runCommandInForeground($container);
|
|
}
|
|
|
|
/**
|
|
* Get the mutex name for the scheduled command.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function mutexName()
|
|
{
|
|
return 'framework'.DIRECTORY_SEPARATOR.'schedule-'.sha1($this->expression.$this->command);
|
|
}
|
|
|
|
/**
|
|
* Run the command in the foreground.
|
|
*
|
|
* @param \Illuminate\Contracts\Container\Container $container
|
|
* @return void
|
|
*/
|
|
protected function runCommandInForeground(Container $container)
|
|
{
|
|
$this->callBeforeCallbacks($container);
|
|
|
|
(new Process(
|
|
$this->buildCommand(), base_path(), null, null, null
|
|
))->run();
|
|
|
|
$this->callAfterCallbacks($container);
|
|
}
|
|
|
|
/**
|
|
* Run the command in the background.
|
|
*
|
|
* @param \Illuminate\Contracts\Container\Container $container
|
|
* @return void
|
|
*/
|
|
protected function runCommandInBackground(Container $container)
|
|
{
|
|
$this->callBeforeCallbacks($container);
|
|
|
|
(new Process(
|
|
$this->buildCommand(), base_path(), null, null, null
|
|
))->run();
|
|
}
|
|
|
|
/**
|
|
* Call all of the "before" callbacks for the event.
|
|
*
|
|
* @param \Illuminate\Contracts\Container\Container $container
|
|
* @return void
|
|
*/
|
|
public function callBeforeCallbacks(Container $container)
|
|
{
|
|
foreach ($this->beforeCallbacks as $callback) {
|
|
$container->call($callback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call all of the "after" callbacks for the event.
|
|
*
|
|
* @param \Illuminate\Contracts\Container\Container $container
|
|
* @return void
|
|
*/
|
|
public function callAfterCallbacks(Container $container)
|
|
{
|
|
foreach ($this->afterCallbacks as $callback) {
|
|
$container->call($callback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the command string.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function buildCommand()
|
|
{
|
|
return (new CommandBuilder)->buildCommand($this);
|
|
}
|
|
|
|
/**
|
|
* Determine if the given event should run based on the Cron expression.
|
|
*
|
|
* @param \Illuminate\Contracts\Foundation\Application $app
|
|
* @return bool
|
|
*/
|
|
public function isDue($app)
|
|
{
|
|
if (! $this->runsInMaintenanceMode() && $app->isDownForMaintenance()) {
|
|
return false;
|
|
}
|
|
|
|
return $this->expressionPasses() &&
|
|
$this->runsInEnvironment($app->environment());
|
|
}
|
|
|
|
/**
|
|
* Determine if the event runs in maintenance mode.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function runsInMaintenanceMode()
|
|
{
|
|
return $this->evenInMaintenanceMode;
|
|
}
|
|
|
|
/**
|
|
* Determine if the Cron expression passes.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function expressionPasses()
|
|
{
|
|
$date = Carbon::now();
|
|
|
|
if ($this->timezone) {
|
|
$date->setTimezone($this->timezone);
|
|
}
|
|
|
|
return CronExpression::factory($this->expression)->isDue($date->toDateTimeString());
|
|
}
|
|
|
|
/**
|
|
* Determine if the event runs in the given environment.
|
|
*
|
|
* @param string $environment
|
|
* @return bool
|
|
*/
|
|
public function runsInEnvironment($environment)
|
|
{
|
|
return empty($this->environments) || in_array($environment, $this->environments);
|
|
}
|
|
|
|
/**
|
|
* Determine if the filters pass for the event.
|
|
*
|
|
* @param \Illuminate\Contracts\Foundation\Application $app
|
|
* @return bool
|
|
*/
|
|
public function filtersPass($app)
|
|
{
|
|
foreach ($this->filters as $callback) {
|
|
if (! $app->call($callback)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
foreach ($this->rejects as $callback) {
|
|
if ($app->call($callback)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Send the output of the command to a given location.
|
|
*
|
|
* @param string $location
|
|
* @param bool $append
|
|
* @return $this
|
|
*/
|
|
public function sendOutputTo($location, $append = false)
|
|
{
|
|
$this->output = $location;
|
|
|
|
$this->shouldAppendOutput = $append;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Append the output of the command to a given location.
|
|
*
|
|
* @param string $location
|
|
* @return $this
|
|
*/
|
|
public function appendOutputTo($location)
|
|
{
|
|
return $this->sendOutputTo($location, true);
|
|
}
|
|
|
|
/**
|
|
* E-mail the results of the scheduled operation.
|
|
*
|
|
* @param array|mixed $addresses
|
|
* @param bool $onlyIfOutputExists
|
|
* @return $this
|
|
*
|
|
* @throws \LogicException
|
|
*/
|
|
public function emailOutputTo($addresses, $onlyIfOutputExists = false)
|
|
{
|
|
$this->ensureOutputIsBeingCapturedForEmail();
|
|
|
|
$addresses = is_array($addresses) ? $addresses : [$addresses];
|
|
|
|
return $this->then(function (Mailer $mailer) use ($addresses, $onlyIfOutputExists) {
|
|
$this->emailOutput($mailer, $addresses, $onlyIfOutputExists);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* E-mail the results of the scheduled operation if it produces output.
|
|
*
|
|
* @param array|mixed $addresses
|
|
* @return $this
|
|
*
|
|
* @throws \LogicException
|
|
*/
|
|
public function emailWrittenOutputTo($addresses)
|
|
{
|
|
return $this->emailOutputTo($addresses, true);
|
|
}
|
|
|
|
/**
|
|
* Ensure that output is being captured for email.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function ensureOutputIsBeingCapturedForEmail()
|
|
{
|
|
if (is_null($this->output) || $this->output == $this->getDefaultOutput()) {
|
|
$this->sendOutputTo(storage_path('logs/schedule-'.sha1($this->mutexName()).'.log'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* E-mail the output of the event to the recipients.
|
|
*
|
|
* @param \Illuminate\Contracts\Mail\Mailer $mailer
|
|
* @param array $addresses
|
|
* @param bool $onlyIfOutputExists
|
|
* @return void
|
|
*/
|
|
protected function emailOutput(Mailer $mailer, $addresses, $onlyIfOutputExists = false)
|
|
{
|
|
$text = file_exists($this->output) ? file_get_contents($this->output) : '';
|
|
|
|
if ($onlyIfOutputExists && empty($text)) {
|
|
return;
|
|
}
|
|
|
|
$mailer->raw($text, function ($m) use ($addresses) {
|
|
$m->to($addresses)->subject($this->getEmailSubject());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the e-mail subject line for output results.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getEmailSubject()
|
|
{
|
|
if ($this->description) {
|
|
return $this->description;
|
|
}
|
|
|
|
return "Scheduled Job Output For [{$this->command}]";
|
|
}
|
|
|
|
/**
|
|
* Register a callback to ping a given URL before the job runs.
|
|
*
|
|
* @param string $url
|
|
* @return $this
|
|
*/
|
|
public function pingBefore($url)
|
|
{
|
|
return $this->before(function () use ($url) {
|
|
(new HttpClient)->get($url);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register a callback to ping a given URL after the job runs.
|
|
*
|
|
* @param string $url
|
|
* @return $this
|
|
*/
|
|
public function thenPing($url)
|
|
{
|
|
return $this->then(function () use ($url) {
|
|
(new HttpClient)->get($url);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* State that the command should run in background.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function runInBackground()
|
|
{
|
|
$this->runInBackground = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set which user the command should run as.
|
|
*
|
|
* @param string $user
|
|
* @return $this
|
|
*/
|
|
public function user($user)
|
|
{
|
|
$this->user = $user;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Limit the environments the command should run in.
|
|
*
|
|
* @param array|mixed $environments
|
|
* @return $this
|
|
*/
|
|
public function environments($environments)
|
|
{
|
|
$this->environments = is_array($environments) ? $environments : func_get_args();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* State that the command should run even in maintenance mode.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function evenInMaintenanceMode()
|
|
{
|
|
$this->evenInMaintenanceMode = true;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Do not allow the event to overlap each other.
|
|
*
|
|
* @param int $expiresAt
|
|
* @return $this
|
|
*/
|
|
public function withoutOverlapping($expiresAt = 1440)
|
|
{
|
|
$this->withoutOverlapping = true;
|
|
|
|
$this->expiresAt = $expiresAt;
|
|
|
|
return $this->then(function () {
|
|
$this->mutex->forget($this);
|
|
})->skip(function () {
|
|
return $this->mutex->exists($this);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register a callback to further filter the schedule.
|
|
*
|
|
* @param \Closure|bool $callback
|
|
* @return $this
|
|
*/
|
|
public function when($callback)
|
|
{
|
|
$this->filters[] = is_callable($callback) ? $callback : function () use ($callback) {
|
|
return $callback;
|
|
};
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Register a callback to further filter the schedule.
|
|
*
|
|
* @param \Closure|bool $callback
|
|
* @return $this
|
|
*/
|
|
public function skip($callback)
|
|
{
|
|
$this->rejects[] = is_callable($callback) ? $callback : function () use ($callback) {
|
|
return $callback;
|
|
};
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Register a callback to be called before the operation.
|
|
*
|
|
* @param \Closure $callback
|
|
* @return $this
|
|
*/
|
|
public function before(Closure $callback)
|
|
{
|
|
$this->beforeCallbacks[] = $callback;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Register a callback to be called after the operation.
|
|
*
|
|
* @param \Closure $callback
|
|
* @return $this
|
|
*/
|
|
public function after(Closure $callback)
|
|
{
|
|
return $this->then($callback);
|
|
}
|
|
|
|
/**
|
|
* Register a callback to be called after the operation.
|
|
*
|
|
* @param \Closure $callback
|
|
* @return $this
|
|
*/
|
|
public function then(Closure $callback)
|
|
{
|
|
$this->afterCallbacks[] = $callback;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the human-friendly description of the event.
|
|
*
|
|
* @param string $description
|
|
* @return $this
|
|
*/
|
|
public function name($description)
|
|
{
|
|
return $this->description($description);
|
|
}
|
|
|
|
/**
|
|
* Set the human-friendly description of the event.
|
|
*
|
|
* @param string $description
|
|
* @return $this
|
|
*/
|
|
public function description($description)
|
|
{
|
|
$this->description = $description;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the summary of the event for display.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getSummaryForDisplay()
|
|
{
|
|
if (is_string($this->description)) {
|
|
return $this->description;
|
|
}
|
|
|
|
return $this->buildCommand();
|
|
}
|
|
|
|
/**
|
|
* Determine the next due date for an event.
|
|
*
|
|
* @param \DateTime|string $currentTime
|
|
* @param int $nth
|
|
* @param bool $allowCurrentDate
|
|
* @return \Illuminate\Support\Carbon
|
|
*/
|
|
public function nextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
|
|
{
|
|
return Carbon::instance(CronExpression::factory(
|
|
$this->getExpression()
|
|
)->getNextRunDate($currentTime, $nth, $allowCurrentDate));
|
|
}
|
|
|
|
/**
|
|
* Get the Cron expression for the event.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getExpression()
|
|
{
|
|
return $this->expression;
|
|
}
|
|
|
|
/**
|
|
* Set the mutex implementation to be used.
|
|
*
|
|
* @param \Illuminate\Console\Scheduling\Mutex $mutex
|
|
* @return $this
|
|
*/
|
|
public function preventOverlapsUsing(Mutex $mutex)
|
|
{
|
|
$this->mutex = $mutex;
|
|
|
|
return $this;
|
|
}
|
|
}
|