Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 76316a7

Browse files
committed
[Scheduler] add "hashed" cron expression support
1 parent aadd302 commit 76316a7

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

src/Symfony/Component/Scheduler/RecurringMessage.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Scheduler\Exception\InvalidArgumentException;
1515
use Symfony\Component\Scheduler\Trigger\CronExpressionTrigger;
1616
use Symfony\Component\Scheduler\Trigger\DateIntervalTrigger;
17+
use Symfony\Component\Scheduler\Trigger\HashedCronExpressionTrigger;
1718
use Symfony\Component\Scheduler\Trigger\TriggerInterface;
1819

1920
/**
@@ -46,6 +47,11 @@ public static function cron(string $expression, object $message): self
4647
return new self(CronExpressionTrigger::fromSpec($expression), $message);
4748
}
4849

50+
public static function hashedCron(string $expression, string $context, object $message): self
51+
{
52+
return new self(new HashedCronExpressionTrigger($expression, $context), $message);
53+
}
54+
4955
public static function trigger(TriggerInterface $trigger, object $message): self
5056
{
5157
return new self($trigger, $message);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Scheduler\Tests\Trigger;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Scheduler\Trigger\HashedCronExpressionTrigger;
16+
17+
final class HashedCronExpressionTriggerTest extends TestCase
18+
{
19+
/**
20+
* @dataProvider hashedExpressionProvider
21+
*/
22+
public function testExpressionParsing(string $input, string $expected)
23+
{
24+
$expressionA = new HashedCronExpressionTrigger($input, 'my task');
25+
$expressionB = new HashedCronExpressionTrigger($input, 'my task');
26+
$expressionC = new HashedCronExpressionTrigger($input, 'another task');
27+
28+
$this->assertSame($expected, $expressionA->getExpression());
29+
$this->assertSame($expressionB->getExpression(), $expressionA->getExpression());
30+
$this->assertNotSame($expressionC->getExpression(), $expressionA->getExpression());
31+
}
32+
33+
public static function hashedExpressionProvider(): array
34+
{
35+
return [
36+
['# * * * *', '56 * * * *'],
37+
['# # * * *', '56 20 * * *'],
38+
['# # # * *', '56 20 1 * *'],
39+
['# # # # *', '56 20 1 9 *'],
40+
['# # # # #', '56 20 1 9 0'],
41+
['# # 1,15 1-11 *', '56 20 1,15 1-11 *'],
42+
['# # 1,15 * *', '56 20 1,15 * *'],
43+
['#hourly', '56 * * * *'],
44+
['#daily', '56 20 * * *'],
45+
['#weekly', '56 20 * * 0'],
46+
['#weekly@midnight', '56 2 * * 0'],
47+
['#monthly', '56 20 1 * *'],
48+
['#monthly@midnight', '56 2 1 * *'],
49+
['#yearly', '56 20 1 9 *'],
50+
['#yearly@midnight', '56 2 1 9 *'],
51+
['#annually', '56 20 1 9 *'],
52+
['#annually@midnight', '56 2 1 9 *'],
53+
['#midnight', '56 2 * * *'],
54+
['#(1-15) * * * *', '12 * * * *'],
55+
['#(1-15) * * * #(3-5)', '12 * * * 5'],
56+
['#(1-15) * # * #(3-5)', '12 * 1 * 5'],
57+
];
58+
}
59+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Scheduler\Trigger;
13+
14+
use Cron\CronExpression;
15+
use Symfony\Component\Scheduler\Exception\InvalidArgumentException;
16+
17+
/**
18+
* Use "hashed" cron expressions to describe a periodical trigger.
19+
*
20+
* @author Kevin Bond <[email protected]>
21+
*
22+
* @experimental
23+
*/
24+
final class HashedCronExpressionTrigger implements TriggerInterface
25+
{
26+
private const ALIAS_MAP = [
27+
'#hourly' => '# * * * *',
28+
'#daily' => '# # * * *',
29+
'#weekly' => '# # * * #',
30+
'#weekly@midnight' => '# #(0-2) * * #',
31+
'#monthly' => '# # # * *',
32+
'#monthly@midnight' => '# #(0-2) # * *',
33+
'#annually' => '# # # # *',
34+
'#annually@midnight' => '# #(0-2) # # *',
35+
'#yearly' => '# # # # *',
36+
'#yearly@midnight' => '# #(0-2) # # *',
37+
'#midnight' => '# #(0-2) * * *',
38+
];
39+
private const RANGES = [
40+
[0, 59],
41+
[0, 23],
42+
[1, 28],
43+
[1, 12],
44+
[0, 6],
45+
];
46+
47+
private CronExpressionTrigger $trigger;
48+
private CronExpression $expression;
49+
50+
public function __construct(string $expression, string $context)
51+
{
52+
$this->trigger = new CronExpressionTrigger(
53+
$this->expression = new CronExpression(self::parse($expression, $context))
54+
);
55+
}
56+
57+
public function getNextRunDate(\DateTimeImmutable $run): ?\DateTimeImmutable
58+
{
59+
return $this->trigger->getNextRunDate($run);
60+
}
61+
62+
public function getExpression(): string
63+
{
64+
return $this->expression;
65+
}
66+
67+
private static function parse(string $expression, string $context): string
68+
{
69+
$expression = self::ALIAS_MAP[$expression] ?? $expression;
70+
$parts = explode(' ', $expression);
71+
72+
if (count($parts) !== 5) {
73+
throw new InvalidArgumentException(sprintf('"%s" is an invalid cron expression.', $expression));
74+
}
75+
76+
foreach ($parts as $position => $part) {
77+
if (preg_match('#^\#(\((\d+)-(\d+)\))?$#', $part, $matches)) {
78+
$parts[$position] = self::hashField(
79+
(int) ($matches[2] ?? self::RANGES[$position][0]),
80+
(int) ($matches[3] ?? self::RANGES[$position][1]),
81+
$context
82+
);
83+
}
84+
}
85+
86+
return implode(' ', $parts);
87+
}
88+
89+
private static function hashField(int $start, int $end, string $context): string
90+
{
91+
$possibleValues = range($start, $end);
92+
93+
return $possibleValues[(int) fmod(hexdec(substr(md5($context), 0, 10)), count($possibleValues))];
94+
}
95+
}

0 commit comments

Comments
 (0)