diff --git a/CHANGELOG.md b/CHANGELOG.md index e8343c5..383d813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.1.0 - 2022-04-22 +Adds the `WrappingClock` which allows using an object with a `now()` method returning a `DateTimeImmutable` object +as a "real" clock. + ## 2.0.0 - 2022-04-20 This release introduces a compatibility layer with the PSR-20 draft, allowing us to already get some interoperability by depending on a shared interface. diff --git a/README.md b/README.md index 63814d0..cd275b0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A collection of Clock implementations. - [`UTCClock`](#utcclock) - The clock that you should™ use - [`FrozenClock`](#frozenclock) - A clock that stopped moving (perfect for tests) - [`MinuteClock`](#minuteclock) - Who cares about seconds or even less? + - [`WrappingClock`](#wrappingclock) - Allows wrapping a non-clock with a `now()` method in a clock - [Running Tests](#running-tests) ## Installation @@ -136,7 +137,42 @@ printf("For %s, the minute clock still returns %s\n", $frozenClock->now()->format('H:i:s'), $clock->now()->format('H:i:s') ); +``` + +### `WrappingClock` + +If you already have an object with a `now()` method returning a `DateTimeImmutable` object, you can wrap it +in a `WrappingClock` to make it a "real" Clock. + +as a "real" clock. + +```php +# examples/wrapping_clock.php + +use Beste\Clock\WrappingClock; + +// Create a frozen $now so that we can test the wrapping clock. +$now = new DateTimeImmutable('2012-04-24 12:00:00'); + +// Create an object that is NOT a clock, but has a now() method returning the frozen $now. +$clock = new class($now) { + private \DateTimeImmutable $now; + + public function __construct(\DateTimeImmutable $now) + { + $this->now = $now; + } + + public function now(): \DateTimeImmutable + { + return $this->now; + } +}; + +// We can now wrap the object in a clock. +$wrappedClock = WrappingClock::wrapping($clock); +assert($now->format(DATE_ATOM) === $wrappedClock->now()->format(DATE_ATOM)); ``` ## Running tests diff --git a/examples/wrapping_clock.php b/examples/wrapping_clock.php new file mode 100644 index 0000000..48a88bf --- /dev/null +++ b/examples/wrapping_clock.php @@ -0,0 +1,28 @@ +now = $now; + } + + public function now(): \DateTimeImmutable + { + return $this->now; + } +}; + +// We can now wrap the object in a clock. +$wrappedClock = WrappingClock::wrapping($clock); + +assert($now->format(DATE_ATOM) === $wrappedClock->now()->format(DATE_ATOM)); diff --git a/src/Clock/WrappingClock.php b/src/Clock/WrappingClock.php new file mode 100644 index 0000000..5919590 --- /dev/null +++ b/src/Clock/WrappingClock.php @@ -0,0 +1,61 @@ +wrappedClock = $wrappedClock; + } + + public static function wrapping(object $clock): self + { + if ($clock instanceof ClockInterface) { + return new self($clock); + } + + if (!method_exists($clock, 'now')) { + throw new \InvalidArgumentException('$clock must implement StellaMaris\Clock\ClockInterface or have a now() method'); + } + + if (!($clock->now() instanceof DateTimeImmutable)) { + throw new \InvalidArgumentException('$clock->now() must return a DateTimeImmutable'); + } + + $wrappedClock = new class($clock) implements ClockInterface { + private object $clock; + + public function __construct(object $clock) + { + $this->clock = $clock; + } + + public function now(): \DateTimeImmutable + { + assert(method_exists($this->clock, 'now')); + + $now = $this->clock->now(); + + assert($now instanceof \DateTimeImmutable); + + return $now; + } + }; + + return new self($wrappedClock); + } + + public function now(): DateTimeImmutable + { + return $this->wrappedClock->now(); + } +} diff --git a/tests/Clock/WrappingClockTest.php b/tests/Clock/WrappingClockTest.php new file mode 100644 index 0000000..5673082 --- /dev/null +++ b/tests/Clock/WrappingClockTest.php @@ -0,0 +1,88 @@ +now()->format(DATE_ATOM), + $wrappedClock->now()->format(DATE_ATOM) + ); + } + + /** + * @test + * + * @covers ::__construct + * @covers ::create + * @covers ::now + */ + public function itWrapsAnObjectWithANowMethod(): void + { + $now = FrozenClock::fromUTC()->now(); + + $clock = new class($now) { + private \DateTimeImmutable $now; + + public function __construct(\DateTimeImmutable $now) + { + $this->now = $now; + } + + public function now(): \DateTimeImmutable + { + return $this->now; + } + }; + + $wrappedClock = WrappingClock::wrapping($clock); + + self::assertSame( + $now->format(DATE_ATOM), + $wrappedClock->now()->format(DATE_ATOM) + ); + } + + /** + * @test + * + * @covers ::create + */ + public function itRejectsObjectsWithANowMethodReturningANonDateTimeImmutable(): void + { + $clock = new class() { + public function now(): string + { + return 'foo'; + } + }; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('$clock->now() must return a DateTimeImmutable'); + + WrappingClock::wrapping($clock); + } +}