Skip to content

Commit

Permalink
Add a WrappingClock
Browse files Browse the repository at this point in the history
The `WrappingClock` allows using an object with a `now()` method
returning a `DateTimeImmutable` object as a "real" clock.
  • Loading branch information
jeromegamez committed Apr 24, 2022
1 parent 98d1e6f commit f3edef9
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions examples/wrapping_clock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

require __DIR__.'/../vendor/autoload.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));
61 changes: 61 additions & 0 deletions src/Clock/WrappingClock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Beste\Clock;

use Beste\Clock;
use DateTimeImmutable;
use StellaMaris\Clock\ClockInterface;

final class WrappingClock implements Clock
{
private ClockInterface $wrappedClock;

private function __construct(ClockInterface $wrappedClock)
{
$this->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();
}
}
88 changes: 88 additions & 0 deletions tests/Clock/WrappingClockTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Beste\Clock\Tests;

use Beste\Clock\FrozenClock;
use Beste\Clock\WrappingClock;
use PHPUnit\Framework\TestCase;

/**
* @internal
* @coversDefaultClass \Beste\Clock\WrappingClock
*/
final class WrappingClockTest extends TestCase
{
/**
* @test
*
* @covers ::__construct
* @covers ::create
* @covers ::now
*/
public function itWrapsAClockInterface(): void
{
$clock = FrozenClock::fromUTC();

$wrappedClock = WrappingClock::wrapping($clock);

self::assertSame(
$clock->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);
}
}

0 comments on commit f3edef9

Please sign in to comment.