Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/Ecotone/src/Messaging/Scheduling/Clock.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Clock implements EcotoneClockInterface
public function __construct(
private readonly ?PsrClockInterface $clock = null,
) {
if (!self::$globalClock) {
self::$globalClock = $this->clock ? $this : self::defaultClock();
}
}

public static function set(PsrClockInterface $clock): void
Expand All @@ -28,7 +31,12 @@ public static function set(PsrClockInterface $clock): void
*/
public static function get(): EcotoneClockInterface
{
return self::$globalClock ??= new NativeClock();
return self::$globalClock ??= self::defaultClock();
}

public static function resetToNativeClock(): void
{
self::$globalClock = null;
}

public function now(): DatePoint
Expand All @@ -48,7 +56,12 @@ public function sleep(Duration $duration): void
if ($clock instanceof SleepInterface) {
$clock->sleep($duration);
} else {
(new NativeClock())->sleep($duration);
self::defaultClock()->sleep($duration);
}
}

private static function defaultClock(): EcotoneClockInterface
{
return new NativeClock();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Fixture\Scheduling;

/*
* licence Apache-2.0
*/
class CustomNotifier
{
private array $notifications = [];

public function notify(string $eventName, $value): void
{
if (!isset($this->notifications[$eventName])) {
$this->notifications[$eventName] = [];
}

$this->notifications[$eventName][] = $value;
}

public function getNotificationsOf(string $eventName): array
{
return $this->notifications[$eventName] ?? [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Fixture\Scheduling;

use Ecotone\Messaging\Attribute\Asynchronous;
use Ecotone\Messaging\Attribute\Endpoint\Delayed;
use Ecotone\Modelling\Attribute\EventHandler;

/**
* licence Apache-2.0
*/
class NotificationService
{
#[Asynchronous('notifications')]
#[Delayed(1000 * 60)] // 60 seconds
#[EventHandler(endpointId: 'notifyOrderWasPlaced')]
public function notify(OrderWasPlaced $event, CustomNotifier $notifier): void
{
$notifier->notify('placedOrder', $event->orderId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Fixture\Scheduling;

use Ecotone\Modelling\Attribute\CommandHandler;
use Ecotone\Modelling\EventBus;

/**
* licence Apache-2.0
*/
final class OrderService
{
#[CommandHandler('order.register')]
public function handle(PlaceOrder $command, EventBus $eventBus): void
{
$eventBus->publish(new OrderWasPlaced($command->orderId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Fixture\Scheduling;

/**
* licence Apache-2.0
*/
final class OrderWasPlaced
{
public function __construct(public string $orderId)
{
}
}
15 changes: 15 additions & 0 deletions packages/Ecotone/tests/Messaging/Fixture/Scheduling/PlaceOrder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Fixture\Scheduling;

/**
* licence Apache-2.0
*/
final class PlaceOrder
{
public function __construct(public string $orderId)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Fixture\Scheduling;

use DateTimeImmutable;
use Ecotone\Messaging\Scheduling\Duration;
use Ecotone\Messaging\Scheduling\SleepInterface;
use Psr\Clock\ClockInterface;

/**
* licence Apache-2.0
*/
final class StaticPsrClock implements ClockInterface, SleepInterface
{
private static DateTimeImmutable $now;

public function __construct(string $now)
{
self::$now = new DateTimeImmutable($now);
}

public function now(): DateTimeImmutable
{
return self::$now;
}

public function sleep(Duration $duration): void
{
self::$now = self::$now->modify("+{$duration->zeroIfNegative()->inMicroseconds()} microseconds");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Integration\Scheduling;

use Ecotone\Lite\EcotoneLite;
use Ecotone\Messaging\Channel\SimpleMessageChannelBuilder;
use Ecotone\Messaging\Scheduling\Clock;
use Ecotone\Messaging\Scheduling\Duration;
use Ecotone\Messaging\Scheduling\EcotoneClockInterface;
use PHPUnit\Framework\TestCase;
use Test\Ecotone\Messaging\Fixture\Scheduling\CustomNotifier;
use Test\Ecotone\Messaging\Fixture\Scheduling\NotificationService;
use Test\Ecotone\Messaging\Fixture\Scheduling\OrderService;
use Test\Ecotone\Messaging\Fixture\Scheduling\PlaceOrder;
use Test\Ecotone\Messaging\Fixture\Scheduling\StaticPsrClock;

/**
* Class StaticGlobalClockTest
* @package Test\Ecotone\Messaging\Unit\Scheduling
* @author JB Cagumbay <cagumbay.jb@gmail.com>
*
* @internal
*/
/**
* licence Apache-2.0
* @internal
*/
class DelayedMessageAgainstGlobalClockTest extends TestCase
{
protected function setUp(): void
{
parent::tearDown();
Clock::resetToNativeClock();
}

protected function tearDown(): void
{
parent::tearDown();
Clock::resetToNativeClock();
}

public function test_delayed_message_observes_clock_changes()
{
$ecotoneTestSupport = EcotoneLite::bootstrapFlowTesting(
[EcotoneClockInterface::class, OrderService::class, NotificationService::class, CustomNotifier::class],
[$clock = new Clock(new StaticPsrClock('2025-08-11 16:00:00')), new OrderService(), new NotificationService(), $notifier = new CustomNotifier()],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgafka just out of curiosity, can you explain why instance of EcotoneClockInterface on this test is reinstantiated? It's the reason why I added this if condition in Clock::__constructor();

        if (!self::$globalClock) {
            self::$globalClock = $this->clock ? $this : self::defaultClock();
        }

enableAsynchronousProcessing: [
// 1. Turn on Delayable In Memory Pollable Channel
SimpleMessageChannelBuilder::createQueueChannel('notifications', true)
]
);

$ecotoneTestSupport->sendCommandWithRoutingKey('order.register', new PlaceOrder('123'));

$clock->sleep(Duration::minutes(1));

// 2. Releasing messages awaiting for 60 seconds
$ecotoneTestSupport->run('notifications', releaseAwaitingFor: Clock::get()->now());

$this->assertEquals(
1,
count($notifier->getNotificationsOf('placedOrder'))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

namespace Test\Ecotone\Messaging\Unit\Scheduling;

use DateTimeImmutable;
use Ecotone\Messaging\Scheduling\Clock;
use Ecotone\Messaging\Scheduling\Duration;
use Ecotone\Messaging\Scheduling\SleepInterface;
use PHPUnit\Framework\TestCase;
use Psr\Clock\ClockInterface;
use Test\Ecotone\Messaging\Fixture\Scheduling\StaticPsrClock;

/**
* Class SleepableStaticClockTest
Expand All @@ -26,38 +24,9 @@ class SleepableStaticClockTest extends TestCase
{
public function test_when_given_psr_clock_instance_with_sleep_support_it_increase_the_clock()
{
$staticClock = $this->createStaticClock('2025-08-11 16:00:00');
$clock = new Clock($staticClock);
$clock = new Clock(new StaticPsrClock('2025-08-11 16:00:00'));
$clock->sleep(Duration::seconds(30));

$this->assertEquals('2025-08-11 16:00:30', $clock->now()->format('Y-m-d H:i:s'));
}

/**
* @description Create static clock with given date time to mimic external PsrClockInterface implementation
* as Ecotone's Clock dependency override.
* @param string $currentDateTime
* @return ClockInterface
*/
private function createStaticClock(string $currentDateTime): ClockInterface
{
return new class ($currentDateTime) implements ClockInterface, SleepInterface {
private static DateTimeImmutable $now;

public function __construct(string $now)
{
self::$now = new DateTimeImmutable($now);
}

public function now(): DateTimeImmutable
{
return self::$now;
}

public function sleep(Duration $duration): void
{
self::$now = self::$now->modify("+{$duration->zeroIfNegative()->inMicroseconds()} microseconds");
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Test\Ecotone\Messaging\Unit\Scheduling;

use Ecotone\Messaging\Scheduling\Clock;
use Ecotone\Messaging\Scheduling\DatePoint;
use Ecotone\Messaging\Scheduling\NativeClock;
use PHPUnit\Framework\TestCase;
use Test\Ecotone\Messaging\Fixture\Scheduling\StaticPsrClock;

/**
* Class StaticGlobalClockTest
* @package Test\Ecotone\Messaging\Unit\Scheduling
* @author JB Cagumbay <cagumbay.jb@gmail.com>
*
* @internal
*/
/**
* licence Apache-2.0
* @internal
*/
class StaticGlobalClockTest extends TestCase
{
protected function setUp(): void
{
parent::tearDown();
Clock::resetToNativeClock();
}

protected function tearDown(): void
{
parent::tearDown();
Clock::resetToNativeClock();
}

public function test_when_clock_is_not_instantiated_returns_default_native_clock()
{
$now = new DatePoint('now');
$globalClock = Clock::get();

$this->assertInstanceOf(NativeClock::class, $globalClock);
$this->assertEquals($now->format('Y-m-d H:i:s'), $globalClock->now()->format('Y-m-d H:i:s'));
}

public function test_when_clock_is_not_instantiated_with_null_internal_clock_returns_default_native_clock()
{
$now = new DatePoint('now');
$clock = new Clock();
$globalClock = Clock::get();

$this->assertInstanceOf(NativeClock::class, $globalClock);
$this->assertEquals($now->format('Y-m-d H:i:s'), $globalClock->now()->format('Y-m-d H:i:s'));
}

public function test_when_clock_is_not_instantiated_with_not_null_internal_clock_returns_ecotone_clock()
{
$clock = new Clock(new StaticPsrClock('2025-08-11 16:00:00'));

$globalClock = Clock::get();

$this->assertInstanceOf(Clock::class, $globalClock);
$this->assertEquals('2025-08-11 16:00:00', $globalClock->now()->format('Y-m-d H:i:s'));
}
}
Loading