command = $command; $this->output = $this->getDefaultOutput(); } /** * Get the default output depending on the OS. * * @return string */ protected 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 (count($this->afterCallbacks) > 0 || count($this->beforeCallbacks) > 0) { $this->runCommandInForeground($container); } else { $this->runCommandInBackground(); } } /** * Run the command in the background using exec. * * @return void */ protected function runCommandInBackground() { chdir(base_path()); exec($this->buildCommand()); } /** * Run the command in the foreground. * * @param \Illuminate\Contracts\Container\Container $container * @return void */ protected function runCommandInForeground(Container $container) { $this->callBeforeCallbacks($container); (new Process( trim($this->buildCommand(), '& '), base_path(), null, null, null ))->run(); $this->callAfterCallbacks($container); } /** * Call all of the "before" callbacks for the event. * * @param \Illuminate\Contracts\Container\Container $container * @return void */ protected 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 */ protected function callAfterCallbacks(Container $container) { foreach ($this->afterCallbacks as $callback) { $container->call($callback); } } /** * Build the command string. * * @return string */ public function buildCommand() { $output = ProcessUtils::escapeArgument($this->output); $redirect = $this->shouldAppendOutput ? ' >> ' : ' > '; if ($this->withoutOverlapping) { if (windows_os()) { $command = '(echo \'\' > "'.$this->mutexPath().'" & '.$this->command.' & del "'.$this->mutexPath().'")'.$redirect.$output.' 2>&1 &'; } else { $command = '(touch '.$this->mutexPath().'; '.$this->command.'; rm '.$this->mutexPath().')'.$redirect.$output.' 2>&1 &'; } } else { $command = $this->command.$redirect.$output.' 2>&1 &'; } return $this->user && ! windows_os() ? 'sudo -u '.$this->user.' -- sh -c \''.$command.'\'' : $command; } /** * Get the mutex path for the scheduled command. * * @return string */ protected function mutexPath() { return storage_path('framework/schedule-'.md5($this->expression.$this->command)); } /** * Determine if the given event should run based on the Cron expression. * * @param \Illuminate\Contracts\Foundation\Application $app * @return bool */ public function isDue(Application $app) { if (! $this->runsInMaintenanceMode() && $app->isDownForMaintenance()) { return false; } return $this->expressionPasses() && $this->filtersPass($app) && $this->runsInEnvironment($app->environment()); } /** * 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 filters pass for the event. * * @param \Illuminate\Contracts\Foundation\Application $app * @return bool */ protected function filtersPass(Application $app) { if (($this->filter && ! $app->call($this->filter)) || $this->reject && $app->call($this->reject)) { return false; } return true; } /** * 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 event runs in maintenance mode. * * @return bool */ public function runsInMaintenanceMode() { return $this->evenInMaintenanceMode; } /** * The Cron expression representing the event's frequency. * * @param string $expression * @return $this */ public function cron($expression) { $this->expression = $expression; return $this; } /** * Schedule the event to run hourly. * * @return $this */ public function hourly() { return $this->cron('0 * * * * *'); } /** * Schedule the event to run daily. * * @return $this */ public function daily() { return $this->cron('0 0 * * * *'); } /** * Schedule the command at a given time. * * @param string $time * @return $this */ public function at($time) { return $this->dailyAt($time); } /** * Schedule the event to run daily at a given time (10:00, 19:30, etc). * * @param string $time * @return $this */ public function dailyAt($time) { $segments = explode(':', $time); return $this->spliceIntoPosition(2, (int) $segments[0]) ->spliceIntoPosition(1, count($segments) == 2 ? (int) $segments[1] : '0'); } /** * Schedule the event to run twice daily. * * @param int $first * @param int $second * @return $this */ public function twiceDaily($first = 1, $second = 13) { $hours = $first.','.$second; return $this->spliceIntoPosition(1, 0) ->spliceIntoPosition(2, $hours); } /** * Schedule the event to run only on weekdays. * * @return $this */ public function weekdays() { return $this->spliceIntoPosition(5, '1-5'); } /** * Schedule the event to run only on Mondays. * * @return $this */ public function mondays() { return $this->days(1); } /** * Schedule the event to run only on Tuesdays. * * @return $this */ public function tuesdays() { return $this->days(2); } /** * Schedule the event to run only on Wednesdays. * * @return $this */ public function wednesdays() { return $this->days(3); } /** * Schedule the event to run only on Thursdays. * * @return $this */ public function thursdays() { return $this->days(4); } /** * Schedule the event to run only on Fridays. * * @return $this */ public function fridays() { return $this->days(5); } /** * Schedule the event to run only on Saturdays. * * @return $this */ public function saturdays() { return $this->days(6); } /** * Schedule the event to run only on Sundays. * * @return $this */ public function sundays() { return $this->days(0); } /** * Schedule the event to run weekly. * * @return $this */ public function weekly() { return $this->cron('0 0 * * 0 *'); } /** * Schedule the event to run weekly on a given day and time. * * @param int $day * @param string $time * @return $this */ public function weeklyOn($day, $time = '0:0') { $this->dailyAt($time); return $this->spliceIntoPosition(5, $day); } /** * Schedule the event to run monthly. * * @return $this */ public function monthly() { return $this->cron('0 0 1 * * *'); } /** * Schedule the event to run yearly. * * @return $this */ public function yearly() { return $this->cron('0 0 1 1 * *'); } /** * Schedule the event to run every minute. * * @return $this */ public function everyMinute() { return $this->cron('* * * * * *'); } /** * Schedule the event to run every five minutes. * * @return $this */ public function everyFiveMinutes() { return $this->cron('*/5 * * * * *'); } /** * Schedule the event to run every ten minutes. * * @return $this */ public function everyTenMinutes() { return $this->cron('*/10 * * * * *'); } /** * Schedule the event to run every thirty minutes. * * @return $this */ public function everyThirtyMinutes() { return $this->cron('0,30 * * * * *'); } /** * Set the days of the week the command should run on. * * @param array|mixed $days * @return $this */ public function days($days) { $days = is_array($days) ? $days : func_get_args(); return $this->spliceIntoPosition(5, implode(',', $days)); } /** * Set the timezone the date should be evaluated on. * * @param \DateTimeZone|string $timezone * @return $this */ public function timezone($timezone) { $this->timezone = $timezone; 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. * * @return $this */ public function withoutOverlapping() { $this->withoutOverlapping = true; return $this->skip(function () { return file_exists($this->mutexPath()); }); } /** * Register a callback to further filter the schedule. * * @param \Closure $callback * @return $this */ public function when(Closure $callback) { $this->filter = $callback; return $this; } /** * Register a callback to further filter the schedule. * * @param \Closure $callback * @return $this */ public function skip(Closure $callback) { $this->reject = $callback; return $this; } /** * 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 * @return $this * * @throws \LogicException */ public function emailOutputTo($addresses) { if (is_null($this->output) || $this->output == $this->getDefaultOutput()) { throw new LogicException('Must direct output to a file in order to e-mail results.'); } $addresses = is_array($addresses) ? $addresses : func_get_args(); return $this->then(function (Mailer $mailer) use ($addresses) { $this->emailOutput($mailer, $addresses); }); } /** * E-mail the output of the event to the recipients. * * @param \Illuminate\Contracts\Mail\Mailer $mailer * @param array $addresses * @return void */ protected function emailOutput(Mailer $mailer, $addresses) { $mailer->raw(file_get_contents($this->output), function ($m) use ($addresses) { $m->subject($this->getEmailSubject()); foreach ($addresses as $address) { $m->to($address); } }); } /** * Get the e-mail subject line for output results. * * @return string */ protected function getEmailSubject() { if ($this->description) { return 'Scheduled Job Output ('.$this->description.')'; } return 'Scheduled Job Output'; } /** * 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 be called before the operation. * * @param \Closure $callback * @return $this */ public function before(Closure $callback) { $this->beforeCallbacks[] = $callback; return $this; } /** * 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); }); } /** * 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; } /** * Splice the given value into the given position of the expression. * * @param int $position * @param string $value * @return $this */ protected function spliceIntoPosition($position, $value) { $segments = explode(' ', $this->expression); $segments[$position - 1] = $value; return $this->cron(implode(' ', $segments)); } /** * Get the summary of the event for display. * * @return string */ public function getSummaryForDisplay() { if (is_string($this->description)) { return $this->description; } return $this->buildCommand(); } /** * Get the Cron expression for the event. * * @return string */ public function getExpression() { return $this->expression; } }