diff --git a/composer.lock b/composer.lock index f89f68f..a7e09ff 100644 --- a/composer.lock +++ b/composer.lock @@ -2398,16 +2398,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.4.34", + "version": "v6.4.35", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "91e49958b8a6092e48e4711894a1aeb1b151c62a" + "reference": "d95712d0e9446b9f244b64811ffb6af7b7434213" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/91e49958b8a6092e48e4711894a1aeb1b151c62a", - "reference": "91e49958b8a6092e48e4711894a1aeb1b151c62a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d95712d0e9446b9f244b64811ffb6af7b7434213", + "reference": "d95712d0e9446b9f244b64811ffb6af7b7434213", "shasum": "" }, "require": { @@ -2459,7 +2459,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.34" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.35" }, "funding": [ { @@ -2479,7 +2479,7 @@ "type": "tidelift" } ], - "time": "2026-02-24T15:33:38+00:00" + "time": "2026-02-26T12:16:01+00:00" }, { "name": "symfony/deprecation-contracts", diff --git a/src/Cli/RunCommand.php b/src/Cli/RunCommand.php index 535422f..b8734fa 100644 --- a/src/Cli/RunCommand.php +++ b/src/Cli/RunCommand.php @@ -70,9 +70,10 @@ public function run(?ArgumentValueList $arguments = null):void { public function cronRunStep( int $jobsRan, ?DateTime $wait, - bool $continue + bool $continue, + array $runCommandList = [], + ?string $nextCommand = null ):void { - $message = ""; $now = new DateTime(); if(is_null($wait)) { @@ -80,17 +81,25 @@ public function cronRunStep( exit(0); } - $jobPlural = "job"; - if($jobsRan !== 1) { - $jobPlural .= "s"; - } + $jobPlural = $jobsRan === 1 ? "job" : "jobs"; + $displayedRunCommands = array_map( + function(string $command):string { + return $this->displayCommandName($command); + }, + $runCommandList + ); - if($jobsRan > 0) { - $message = "Just ran $jobsRan $jobPlural, "; + $message = "Just ran $jobsRan $jobPlural"; + if($displayedRunCommands) { + $message .= " (" . implode(", ", $displayedRunCommands) . ")"; } + $this->stream->writeLine($message); - $message .= "next job at: " . $wait->format("H:i:s"); + $message = "Next job at: " . $wait->format("H:i:s"); + if($nextCommand) { + $message .= " (" . $this->displayCommandName($nextCommand) . ")"; + } if($now->diff($wait)->format("%a") > 0) { $message .= " on " . $wait->format("dS M"); @@ -98,17 +107,26 @@ public function cronRunStep( if($now->diff($wait)->format("%y") > 0) { $message .= " " . $wait->format("Y"); } + $this->stream->writeLine($message); if($continue) { - $message .= ". Waiting..."; + $this->stream->writeLine("Waiting..."); } - else { - $message .= ". Stopping now."; + } + + protected function displayCommandName(string $command):string { + $command = trim($command); + if(strpos($command, "::") !== false) { + $functionExpression = trim( + preg_replace("/\\s*\\(.+$/", "", $command) + ); + [$class, $method] = explode("::", $functionExpression, 2); + $class = basename(str_replace("\\", "/", $class)); + return "$class::$method"; } - $this->stream->writeLine( - ucfirst($message) - ); + $script = preg_split("/\\s+/", $command, 2)[0]; + return basename(str_replace("\\", "/", $script)); } public function getName():string { diff --git a/src/Queue.php b/src/Queue.php index b38a4f2..2d51268 100644 --- a/src/Queue.php +++ b/src/Queue.php @@ -50,6 +50,21 @@ public function runDueJobs():int { return $jobsRan; } + /** @return string[] */ + public function runDueJobsAndGetCommands():array { + $commandList = []; + + foreach($this->jobList as $job) { + if(!$job->hasRun() + && $job->isDue($this->now())) { + $job->run(); + $commandList []= $job->getCommand(); + } + } + + return $commandList; + } + public function runAllJobs():int { $jobsRan = 0; @@ -83,6 +98,15 @@ public function getNextJob():?Job { return $nextJob; } + public function commandOfNextJob():?string { + $nextJob = $this->getNextJob(); + if(!$nextJob) { + return null; + } + + return $nextJob->getCommand(); + } + public function now(?DateTime $newNow = null):DateTime { if(!is_null($newNow)) { $this->now = $newNow; diff --git a/src/Runner.php b/src/Runner.php index 7509aec..b22b710 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -78,10 +78,9 @@ public function run(bool $continue = false):int { $this->continue = $continue; do { - $jobsRan = 0; $this->queue->reset(); - - $jobsRan += $this->queue->runDueJobs(); + $runCommandList = $this->queue->runDueJobsAndGetCommands(); + $jobsRan = count($runCommandList); if(is_callable($this->runCallback)) { $this->queue->now(new DateTime()); @@ -90,7 +89,9 @@ public function run(bool $continue = false):int { $this->runCallback, $jobsRan, $this->queue->timeOfNextJob(), - $continue + $continue, + $runCommandList, + $this->queue->commandOfNextJob() ); } diff --git a/test/phpunit/Command/RunCommandTest.php b/test/phpunit/Command/RunCommandTest.php index 237bd99..4a1b324 100644 --- a/test/phpunit/Command/RunCommandTest.php +++ b/test/phpunit/Command/RunCommandTest.php @@ -2,6 +2,7 @@ namespace Gt\Cron\Test\Command; use Gt\Cli\Argument\ArgumentValueList; +use Gt\Cli\Stream; use Gt\Cron\Cli\RunCommand; use Gt\Cron\Test\Command\CommandTestCase; use Gt\Cron\Test\Helper\ExampleClass; @@ -49,8 +50,19 @@ public function testRunNowFunction() { ExampleClass::$calls ); - self::assertStreamOutput("Just ran 1 job", $stream); - self::assertStreamOutput("Stopping now", $stream); + $output = $this->getFullOutput($stream); + self::assertStringContainsString( + "Just ran 1 job (ExampleClass::doSomething)", + $output + ); + self::assertStringContainsString( + "Next job at:", + $output + ); + self::assertStringContainsString( + "(ExampleClass::doSomething)", + $output + ); } public function testRunNowFunctionWithArguments() { @@ -273,6 +285,16 @@ function($command)use(&$calledCommand) { 1, \Gt\Cron\Test\Helper\ExampleClass::$calls ); + + $output = $this->getFullOutput($stream); + self::assertStringContainsString( + "Just ran 2 jobs (doSomething, ExampleClass::doSomething)", + $output + ); + self::assertStringContainsString( + "Next job at:", + $output + ); } public function testRunNowScriptNotExists() { @@ -320,4 +342,10 @@ public function testRunNowFunctionNotExists() { $stream ); } + + protected function getFullOutput(Stream $stream):string { + $out = $stream->getOutStream(); + $out->rewind(); + return $out->fread(10000); + } } diff --git a/test/phpunit/RunnerTest.php b/test/phpunit/RunnerTest.php index b3dab73..86c0f9e 100644 --- a/test/phpunit/RunnerTest.php +++ b/test/phpunit/RunnerTest.php @@ -248,6 +248,41 @@ public function testRunCallbackIsExecuted() { self::assertEquals(2, $count); } + public function testRunCallbackIncludesCommands():void { + $cronContents = <<mockJobRepository(0), + $this->mockQueueRepository(0), + $cronContents + ); + + $jobsRan = 0; + $runCommandList = []; + $nextCommand = null; + $runner->setRunCallback( + function( + int $jobsRanArg, + ?DateTime $wait, + bool $continue, + array $runCommandListArg, + ?string $nextCommandArg + ) use(&$jobsRan, &$runCommandList, &$nextCommand) { + $jobsRan = $jobsRanArg; + $runCommandList = $runCommandListArg; + $nextCommand = $nextCommandArg; + } + ); + + $runner->run(); + + self::assertEquals(1, $jobsRan); + self::assertEquals(["ExampleClass::example"], $runCommandList); + self::assertEquals("ExampleClass::example", $nextCommand); + } + public function testComments() { $cronContents = <<method("runDueJobs") ->willReturn($numberDueJobs); + $queue->method("runDueJobsAndGetCommands") + ->willReturn( + array_fill(0, $numberDueJobs, "ExampleClass::example") + ); $queue->method("secondsUntilNextJob") ->willReturn($secondsUntilNextJob); + $queue->method("commandOfNextJob") + ->willReturn("ExampleClass::example"); $repository = self::createMock(QueueRepository::class); $repository->method("createAtTime")