diff --git a/src/Illuminate/Console/Command.php b/src/Illuminate/Console/Command.php index df45e97785b6..562c0c82bcfe 100755 --- a/src/Illuminate/Console/Command.php +++ b/src/Illuminate/Console/Command.php @@ -57,6 +57,13 @@ class Command extends SymfonyCommand */ protected $description; + /** + * Indicates whether the command should be shown in the Artisan command list. + * + * @var bool + */ + protected $hidden = false; + /** * The default verbosity of output commands. * @@ -95,6 +102,8 @@ public function __construct() $this->setDescription($this->description); + $this->setHidden($this->hidden); + if (! isset($this->signature)) { $this->specifyParameters(); } diff --git a/src/Illuminate/Console/ScheduleServiceProvider.php b/src/Illuminate/Console/ScheduleServiceProvider.php index a32eb43f61bd..c0ef75fd7505 100644 --- a/src/Illuminate/Console/ScheduleServiceProvider.php +++ b/src/Illuminate/Console/ScheduleServiceProvider.php @@ -20,7 +20,10 @@ class ScheduleServiceProvider extends ServiceProvider */ public function register() { - $this->commands('Illuminate\Console\Scheduling\ScheduleRunCommand'); + $this->commands([ + 'Illuminate\Console\Scheduling\ScheduleRunCommand', + 'Illuminate\Console\Scheduling\ScheduleFinishCommand', + ]); } /** @@ -32,6 +35,7 @@ public function provides() { return [ 'Illuminate\Console\Scheduling\ScheduleRunCommand', + 'Illuminate\Console\Scheduling\ScheduleFinishCommand', ]; } } diff --git a/src/Illuminate/Console/Scheduling/CommandBuilder.php b/src/Illuminate/Console/Scheduling/CommandBuilder.php new file mode 100644 index 000000000000..08537b4ac4d8 --- /dev/null +++ b/src/Illuminate/Console/Scheduling/CommandBuilder.php @@ -0,0 +1,69 @@ +runInBackground) { + return $this->buildBackgroundCommand($event); + } else { + return $this->buildForegroundCommand($event); + } + } + + /** + * Build the command for running the event in the foreground. + * + * @param \Illuminate\Console\Scheduling\Event $event + * @return string + */ + protected function buildForegroundCommand(Event $event) + { + $output = ProcessUtils::escapeArgument($event->output); + + return $this->finalize($event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1'); + } + + /** + * Build the command for running the event in the foreground. + * + * @param \Illuminate\Console\Scheduling\Event $event + * @return string + */ + protected function buildBackgroundCommand(Event $event) + { + $output = ProcessUtils::escapeArgument($event->output); + + $redirect = $event->shouldAppendOutput ? ' >> ' : ' > '; + + $finished = Application::formatCommandString('schedule:finish').' "'.$event->mutexName().'"'; + + return $this->finalize($event, + '('.$event->command.$redirect.$output.' 2>&1 '.(windows_os() ? '&' : ';').' '.$finished.') > ' + .ProcessUtils::escapeArgument($event->getDefaultOutput()).' 2>&1 &' + ); + } + + /** + * Finalize the event's command syntax. + * + * @param \Illuminate\Console\Scheduling\Event $event + * @param string $command + * @return string + */ + protected function finalize(Event $event, $command) + { + return $event->user && ! windows_os() ? 'sudo -u '.$event->user.' -- sh -c \''.$command.'\'' : $command; + } +} diff --git a/src/Illuminate/Console/Scheduling/Event.php b/src/Illuminate/Console/Scheduling/Event.php index 724e507b34dc..a7d69a577aa6 100644 --- a/src/Illuminate/Console/Scheduling/Event.php +++ b/src/Illuminate/Console/Scheduling/Event.php @@ -6,11 +6,9 @@ use Carbon\Carbon; use LogicException; use Cron\CronExpression; -use Illuminate\Console\Application; use GuzzleHttp\Client as HttpClient; use Illuminate\Contracts\Mail\Mailer; use Symfony\Component\Process\Process; -use Symfony\Component\Process\ProcessUtils; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Cache\Repository as Cache; @@ -107,7 +105,7 @@ class Event * * @var bool */ - protected $shouldAppendOutput = false; + public $shouldAppendOutput = false; /** * The array of callbacks to be run before the event is started. @@ -149,7 +147,7 @@ public function __construct(Cache $cache, $command) * * @return string */ - protected function getDefaultOutput() + public function getDefaultOutput() { return (DIRECTORY_SEPARATOR == '\\') ? 'NUL' : '/dev/null'; } @@ -167,7 +165,7 @@ public function run(Container $container) } if ($this->runInBackground) { - $this->runCommandInBackground(); + $this->runCommandInBackground($container); } else { $this->runCommandInForeground($container); } @@ -193,10 +191,13 @@ protected function runCommandInForeground(Container $container) /** * Run the command in the background. * + * @param \Illuminate\Contracts\Container\Container $container * @return void */ - protected function runCommandInBackground() + protected function runCommandInBackground(Container $container) { + $this->callBeforeCallbacks($container); + (new Process( $this->buildCommand(), base_path(), null, null, null ))->run(); @@ -208,7 +209,7 @@ protected function runCommandInBackground() * @param \Illuminate\Contracts\Container\Container $container * @return void */ - protected function callBeforeCallbacks(Container $container) + public function callBeforeCallbacks(Container $container) { foreach ($this->beforeCallbacks as $callback) { $container->call($callback); @@ -221,7 +222,7 @@ protected function callBeforeCallbacks(Container $container) * @param \Illuminate\Contracts\Container\Container $container * @return void */ - protected function callAfterCallbacks(Container $container) + public function callAfterCallbacks(Container $container) { foreach ($this->afterCallbacks as $callback) { $container->call($callback); @@ -235,23 +236,7 @@ protected function callAfterCallbacks(Container $container) */ public function buildCommand() { - $output = ProcessUtils::escapeArgument($this->output); - - $redirect = $this->shouldAppendOutput ? ' >> ' : ' > '; - - if ($this->withoutOverlapping) { - $forget = Application::formatCommandString('cache:forget'); - - if (windows_os()) { - $command = '('.$this->command.' & '.$forget.' "'.$this->mutexName().'")'.$redirect.$output.' 2>&1 &'; - } else { - $command = '('.$this->command.'; '.$forget.' '.$this->mutexName().')'.$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; + return (new CommandBuilder)->buildCommand($this); } /** @@ -259,7 +244,7 @@ public function buildCommand() * * @return string */ - protected function mutexName() + public function mutexName() { return 'framework'.DIRECTORY_SEPARATOR.'schedule-'.sha1($this->expression.$this->command); } @@ -399,7 +384,9 @@ public function withoutOverlapping() { $this->withoutOverlapping = true; - return $this->skip(function () { + return $this->then(function () { + $this->cache->forget($this->mutexName()); + })->skip(function () { return $this->cache->has($this->mutexName()); }); } diff --git a/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php b/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php new file mode 100644 index 000000000000..f22a0c5bfad6 --- /dev/null +++ b/src/Illuminate/Console/Scheduling/ScheduleFinishCommand.php @@ -0,0 +1,61 @@ +schedule = $schedule; + + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + collect($this->schedule->events())->filter(function ($value) { + return $value->mutexName() == $this->argument('id'); + })->each->callAfterCallbacks($this->laravel); + } +} diff --git a/tests/Console/Scheduling/EventTest.php b/tests/Console/Scheduling/EventTest.php index 69be7fd8c3fe..aab613ef349f 100644 --- a/tests/Console/Scheduling/EventTest.php +++ b/tests/Console/Scheduling/EventTest.php @@ -17,7 +17,15 @@ public function testBuildCommand() $event = new Event(m::mock('Illuminate\Contracts\Cache\Repository'), 'php -i'); $defaultOutput = (DIRECTORY_SEPARATOR == '\\') ? 'NUL' : '/dev/null'; - $this->assertSame("php -i > {$quote}{$defaultOutput}{$quote} 2>&1 &", $event->buildCommand()); + $this->assertSame("php -i > {$quote}{$defaultOutput}{$quote} 2>&1", $event->buildCommand()); + + $quote = (DIRECTORY_SEPARATOR == '\\') ? '"' : "'"; + + $event = new Event(m::mock('Illuminate\Contracts\Cache\Repository'), 'php -i'); + $event->runInBackground(); + + $defaultOutput = (DIRECTORY_SEPARATOR == '\\') ? 'NUL' : '/dev/null'; + $this->assertSame("(php -i > {$quote}{$defaultOutput}{$quote} 2>&1 ; '".PHP_BINARY."' artisan schedule:finish \"framework/schedule-c65b1c374c37056e0c57fccb0c08d724ce6f5043\") > {$quote}{$defaultOutput}{$quote} 2>&1 &", $event->buildCommand()); } public function testBuildCommandSendOutputTo() @@ -27,12 +35,12 @@ public function testBuildCommandSendOutputTo() $event = new Event(m::mock('Illuminate\Contracts\Cache\Repository'), 'php -i'); $event->sendOutputTo('/dev/null'); - $this->assertSame("php -i > {$quote}/dev/null{$quote} 2>&1 &", $event->buildCommand()); + $this->assertSame("php -i > {$quote}/dev/null{$quote} 2>&1", $event->buildCommand()); $event = new Event(m::mock('Illuminate\Contracts\Cache\Repository'), 'php -i'); $event->sendOutputTo('/my folder/foo.log'); - $this->assertSame("php -i > {$quote}/my folder/foo.log{$quote} 2>&1 &", $event->buildCommand()); + $this->assertSame("php -i > {$quote}/my folder/foo.log{$quote} 2>&1", $event->buildCommand()); } public function testBuildCommandAppendOutput() @@ -42,7 +50,7 @@ public function testBuildCommandAppendOutput() $event = new Event(m::mock('Illuminate\Contracts\Cache\Repository'), 'php -i'); $event->appendOutputTo('/dev/null'); - $this->assertSame("php -i >> {$quote}/dev/null{$quote} 2>&1 &", $event->buildCommand()); + $this->assertSame("php -i >> {$quote}/dev/null{$quote} 2>&1", $event->buildCommand()); } /**