Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
32 / 32 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| CronExpressionEvaluator | |
100.00% |
31 / 31 |
|
100.00% |
2 / 2 |
15 | |
100.00% |
1 / 1 |
| isDue | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
| matchPart | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
9 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Core\Cron; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | /** |
| 12 | * Standard 5-Part Cron Expression Evaluator. |
| 13 | * |
| 14 | * Evaluates cron schedule expressions (* * * * *) against a given Unix timestamp. |
| 15 | * |
| 16 | * @package App\Core\Cron |
| 17 | */ |
| 18 | final readonly class CronExpressionEvaluator |
| 19 | { |
| 20 | /** |
| 21 | * Evaluates whether cron expression matches current timestamp. |
| 22 | * |
| 23 | * @param string $expression 5-part cron expression string. |
| 24 | * @param int|null $timestamp Optional Unix timestamp to test against (defaults to current time). |
| 25 | * @return bool True if cron expression matches timestamp. |
| 26 | */ |
| 27 | public function isDue(string $expression, ?int $timestamp = null): bool |
| 28 | { |
| 29 | $time = $timestamp ?? time(); |
| 30 | $parts = array_values(array_filter(explode(' ', trim($expression)), fn($p) => $p !== '')); |
| 31 | |
| 32 | if (count($parts) !== 5) { |
| 33 | return false; |
| 34 | } |
| 35 | |
| 36 | $minute = (int)date('i', $time); |
| 37 | $hour = (int)date('G', $time); |
| 38 | $dayOfMonth = (int)date('j', $time); |
| 39 | $month = (int)date('n', $time); |
| 40 | $dayOfWeek = (int)date('w', $time); |
| 41 | |
| 42 | return $this->matchPart($parts[0], $minute, 0, 59) |
| 43 | && $this->matchPart($parts[1], $hour, 0, 23) |
| 44 | && $this->matchPart($parts[2], $dayOfMonth, 1, 31) |
| 45 | && $this->matchPart($parts[3], $month, 1, 12) |
| 46 | && $this->matchPart($parts[4], $dayOfWeek, 0, 6); |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Matches single cron field part. |
| 51 | * |
| 52 | * @param string $part Cron field value string. |
| 53 | * @param int $current Current time unit value. |
| 54 | * @param int $min Minimum valid value. |
| 55 | * @param int $max Maximum valid value. |
| 56 | * @return bool True if field matches current value. |
| 57 | */ |
| 58 | private function matchPart(string $part, int $current, int $min, int $max): bool |
| 59 | { |
| 60 | if ($part === '*') { |
| 61 | return true; |
| 62 | } |
| 63 | |
| 64 | $matched = false; |
| 65 | if (str_contains($part, ',')) { |
| 66 | foreach (explode(',', $part) as $subPart) { |
| 67 | if ($this->matchPart($subPart, $current, $min, $max)) { |
| 68 | $matched = true; |
| 69 | break; |
| 70 | } |
| 71 | } |
| 72 | } elseif (str_starts_with($part, '*/')) { |
| 73 | $step = (int)substr($part, 2); |
| 74 | $matched = $step > 0 && ($current % $step === 0); |
| 75 | } elseif (str_contains($part, '-')) { |
| 76 | $range = explode('-', $part, 2); |
| 77 | $start = (int)$range[0]; |
| 78 | $end = (int)$range[1]; |
| 79 | $matched = $current >= $start && $current <= $end; |
| 80 | } else { |
| 81 | $matched = (int)$part === $current; |
| 82 | } |
| 83 | |
| 84 | return $matched; |
| 85 | } |
| 86 | } |