Skip to content

Commit 5777135

Browse files
committed
Add ClockAware and ImmutableClockAware for writing time-sensitive classes
1 parent 4ce7f40 commit 5777135

File tree

5 files changed

+256
-0
lines changed

5 files changed

+256
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Clock\Extension;
15+
16+
use Nexus\Clock\Clock;
17+
use Nexus\Clock\FrozenClock;
18+
use Nexus\Clock\SystemClock;
19+
use Psr\Clock\ClockInterface;
20+
21+
/**
22+
* A trait to help write time-sensitive classes.
23+
*
24+
* @phpstan-require-implements ClockAwareInterface
25+
*/
26+
trait ClockAware
27+
{
28+
private ?Clock $clock = null;
29+
30+
public function setClock(?ClockInterface $clock): self
31+
{
32+
if (null !== $clock && ! $clock instanceof Clock) {
33+
$clock = new FrozenClock($clock->now());
34+
}
35+
36+
$this->clock = $clock;
37+
38+
return $this;
39+
}
40+
41+
public function getClock(): Clock
42+
{
43+
return $this->clock ?? new SystemClock('UTC');
44+
}
45+
46+
/**
47+
* Gets the current timestamp.
48+
*/
49+
private function now(): int
50+
{
51+
return $this->getClock()->now()->getTimestamp();
52+
}
53+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Clock\Extension;
15+
16+
use Nexus\Clock\Clock;
17+
use Psr\Clock\ClockInterface;
18+
19+
/**
20+
* Interface for mutable classes that require access to a clock.
21+
*/
22+
interface ClockAwareInterface
23+
{
24+
/**
25+
* Sets the clock instance on the class. If the clock is not an instance of
26+
* `Nexus\Clock\Clock`, it will be wrapped in a `Nexus\Clock\FrozenClock`.
27+
* If `null` is passed, the class will use a `Nexus\Clock\SystemClock`.
28+
*/
29+
public function setClock(?ClockInterface $clock): self;
30+
31+
/**
32+
* Gets the clock instance.
33+
*/
34+
public function getClock(): Clock;
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Clock\Extension;
15+
16+
use Nexus\Clock\Clock;
17+
18+
/**
19+
* A trait to help write time-sensitive immutable classes.
20+
*
21+
* Using classes must ensure that a readonly `Clock` instance is already
22+
* injected into the class, preferably in the constructor.
23+
*
24+
* @property Clock $clock
25+
*/
26+
trait ImmutableClockAware
27+
{
28+
/**
29+
* Gets the current timestamp.
30+
*/
31+
private function now(): int
32+
{
33+
return $this->clock->now()->getTimestamp();
34+
}
35+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Tests\Clock\Extension;
15+
16+
use Nexus\Clock\Extension\ClockAware;
17+
use Nexus\Clock\Extension\ClockAwareInterface;
18+
use Nexus\Clock\FrozenClock;
19+
use Nexus\Clock\SystemClock;
20+
use PHPUnit\Framework\Attributes\CoversTrait;
21+
use PHPUnit\Framework\Attributes\DataProvider;
22+
use PHPUnit\Framework\Attributes\Group;
23+
use PHPUnit\Framework\TestCase;
24+
use Psr\Clock\ClockInterface;
25+
26+
/**
27+
* @internal
28+
*/
29+
#[CoversTrait(ClockAware::class)]
30+
#[Group('unit-test')]
31+
final class ClockAwareTest extends TestCase
32+
{
33+
/**
34+
* @param class-string $expected
35+
*/
36+
#[DataProvider('provideSetClockCases')]
37+
public function testSetClock(?ClockInterface $clock, int $time, string $expected): void
38+
{
39+
$instance = new MutableClass();
40+
$instance->setClock($clock);
41+
42+
self::assertGreaterThanOrEqual($time, $instance->getNow());
43+
self::assertInstanceOf($expected, $instance->getClock());
44+
}
45+
46+
/**
47+
* @return iterable<string, array{null|ClockInterface, int, class-string}>
48+
*/
49+
public static function provideSetClockCases(): iterable
50+
{
51+
$dateTime = new \DateTimeImmutable('2025-05-19 12:00:00', new \DateTimeZone('UTC'));
52+
53+
yield 'null' => [null, time(), SystemClock::class];
54+
55+
yield 'system clock' => [new SystemClock('UTC'), time(), SystemClock::class];
56+
57+
yield 'frozen clock' => [new FrozenClock($dateTime), $dateTime->getTimestamp(), FrozenClock::class];
58+
59+
yield 'psr clock' => [new class implements ClockInterface {
60+
public function now(): \DateTimeImmutable
61+
{
62+
return new \DateTimeImmutable('2025-05-19 12:00:00', new \DateTimeZone('UTC'));
63+
}
64+
}, $dateTime->getTimestamp(), FrozenClock::class];
65+
}
66+
}
67+
68+
/**
69+
* @internal
70+
*/
71+
final class MutableClass implements ClockAwareInterface
72+
{
73+
use ClockAware;
74+
75+
public function getNow(): int
76+
{
77+
return $this->now();
78+
}
79+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the Nexus framework.
7+
*
8+
* (c) John Paul E. Balandan, CPA <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Nexus\Tests\Clock\Extension;
15+
16+
use Nexus\Clock\Clock;
17+
use Nexus\Clock\Extension\ImmutableClockAware;
18+
use Nexus\Clock\FrozenClock;
19+
use PHPUnit\Framework\Attributes\CoversTrait;
20+
use PHPUnit\Framework\Attributes\Group;
21+
use PHPUnit\Framework\TestCase;
22+
23+
/**
24+
* @internal
25+
*/
26+
#[CoversTrait(ImmutableClockAware::class)]
27+
#[Group('unit-test')]
28+
final class ImmutableClockAwareTest extends TestCase
29+
{
30+
public function testNowUsingTrait(): void
31+
{
32+
$clock = new FrozenClock(new \DateTimeImmutable('2025-05-19 12:00:00', new \DateTimeZone('UTC')));
33+
$object = new ImmutableClass($clock);
34+
35+
self::assertSame($clock->now()->getTimestamp(), $object->getNow());
36+
}
37+
}
38+
39+
/**
40+
* @internal
41+
*/
42+
final readonly class ImmutableClass
43+
{
44+
use ImmutableClockAware;
45+
46+
public function __construct(
47+
private Clock $clock,
48+
) {}
49+
50+
public function getNow(): int
51+
{
52+
return $this->now();
53+
}
54+
}

0 commit comments

Comments
 (0)