Skip to content

Commit e3b9b19

Browse files
committed
feat: rename Clock service to CarbonClock and introduce CarbonClockInterface for improved type safety
1 parent 3c23e08 commit e3b9b19

24 files changed

Lines changed: 166 additions & 155 deletions

File tree

_changelog/next.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
- `ApiDataScopeContextSettingMiddleware` lets API clients opt out of specific contextual scopes per request via the `?no_scope[resource]=*` query parameter, providing escape hatches for admin queries that need unfiltered data.
140140
- The `AiService` facade has been expanded with: `getAgent(name)`, `tryToGetAgent(name)`, `getMcpClient(server)`, `getSystemPrompts()`, `getSystemModels()` — making it the single entry point for all AI-related service resolution.
141141
- Background queue workers now run with `--timeout=0` (no per-job timeout), preventing long-running AI streaming jobs from being killed mid-response.
142+
- The `Clock` service has been renamed to `CarbonClock` and a new `CarbonClockInterface` (extending `Psr\Clock\ClockInterface`) has been introduced. The interface guarantees `CarbonImmutable` as the return type of `now()`, improving type safety across all time-dependent services. Both `ClockInterface` and `CarbonClockInterface` resolve to the same singleton in the service container. All internal services (caching, crypto, URL signing, file converters, frontend migrations) now use constructor injection of this clock, making time-dependent logic fully testable.
142143

143144
### Deprecation
144145

_docker_production/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ services:
157157
nofile:
158158
soft: 65536
159159
hard: 65536
160-
restart: no
160+
restart: always
161161
volumes:
162162
- mysql_data:/var/lib/mysql
163163
ports:

_documentation/400-Contributing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ Before submitting your PR:
120120
- [ ] Value objects are `readonly` with `from...` / `tryFrom...` factory methods
121121
- [ ] Enums used for all constrained string or int values
122122
- [ ] DocBlocks only where needed (complex types, non-obvious intent)
123-
- [ ] No `now()`, `new \DateTime()`, `Carbon::now()`, or similar — use injected `Psr\Clock\ClockInterface`
123+
- [ ] No `now()`, `new \DateTime()`, `Carbon::now()`, or similar — use injected `CarbonClockInterface` (`Psr\Clock\ClockInterface` if the class must stay PSR-only)
124124
- [ ] No debug statements (`dd()`, `dump()`, `var_dump()`)
125125
- [ ] No hardcoded values (use config or constants)
126126
- [ ] You provided good test coverage for new features and bug fixes

_documentation/500-Backend/100-Architecture/index.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ readonly class AiService
7474
#[Config('hawki.aiHandle')]
7575
private string $aiHandle,
7676
private Psr\Log\LoggerInterface $logger,
77-
private Psr\Clock\ClockInterface $clock,
77+
private App\Services\System\Time\CarbonClockInterface $clock,
7878
) {}
7979
}
8080
```
@@ -86,10 +86,12 @@ readonly class AiService
8686
| Config value | `#[Config('section.key')] string $value` |
8787
| Cache | `#[Cache] Illuminate\Contracts\Cache\Repository $cache` |
8888
| Logging | `Psr\Log\LoggerInterface $logger` |
89-
| Current time | `Psr\Clock\ClockInterface $clock` |
89+
| Current time | `App\Services\System\Time\CarbonClockInterface $clock` |
9090

9191
`now()`, `Carbon::now()`, and `new \DateTime()` are banned in services, repositories, and value objects. They make time non-deterministic in tests.
9292

93+
`CarbonClockInterface` extends the PSR-20 `Psr\Clock\ClockInterface` but types `now()` to return `CarbonImmutable` instead of a plain `DateTimeImmutable`, so services get Carbon's API without an extra cast. Both interfaces are bound to the same `CarbonClock` singleton (see `AppServiceProvider::registerClockForInterface()`); inject `Psr\Clock\ClockInterface` instead only when a class needs to stay framework/PSR-agnostic (e.g. shared library code).
94+
9395
## ServiceLocatorTrait (API Resources only)
9496

9597
`ServiceLocatorTrait` (`App\Services\System\Container\ServiceLocatorTrait`) exists because Laravel's JSON:API library instantiates `JsonApiResource` subclasses outside the container's constructor chain. Constructor injection is not available there.

_documentation/500-Backend/1000-Infrastructure/300-Announcements.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ control.
99
`Session::put()` (session access from a service), and `now()` (direct time construction) — all
1010
of which violate HAWKI's coding standards for services. This is a confirmed deviation tracked
1111
in the [Technical Debt Register](../100-Architecture/300-Technical-Debt.md). Do not copy these
12-
patterns; follow the standard (constructor-injected `ClockInterface`, no facades in services,
12+
patterns; follow the standard (constructor-injected `CarbonClockInterface`, no facades in services,
1313
no session access) in any new code you write in this area.
1414
:::
1515

app/Providers/AppServiceProvider.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
use App\Services\System\Http\Exceptions\SsrfBlockedException;
1414
use App\Services\System\Http\SsrfSafeGetterMacro;
1515
use App\Services\System\ScheduleWithDynamicIntervalFactory;
16-
use App\Services\System\Time\Clock;
16+
use App\Services\System\Time\CarbonClock;
17+
use App\Services\System\Time\CarbonClockInterface;
1718
use App\Services\System\UsageTypes\UsageContext;
1819
use App\Services\System\UserTypes\UserContext;
1920
use App\Services\Translation\LocaleService;
@@ -208,6 +209,7 @@ private function registerDisablingGlobalScopesForEloquentUserProvider(): void
208209

209210
private function registerClockForInterface(): void
210211
{
211-
$this->app->singleton(ClockInterface::class, static fn() => new Clock());
212+
$this->app->singleton(ClockInterface::class, static fn() => new CarbonClock());
213+
$this->app->singleton(CarbonClockInterface::class, static fn() => new CarbonClock());
212214
}
213215
}

app/Services/Ai/ModelInformation/Enrichment/Implementations/LiteLlm/LiteLlmApiDataStore.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
use App\Services\Ai\Providers\Values\AiProviderProxy;
9-
use App\Services\System\Time\Clock;
9+
use App\Services\System\Time\CarbonClock;
1010
use Illuminate\Contracts\Cache\Repository;
1111
use Psr\Log\LoggerInterface;
1212

@@ -18,11 +18,11 @@
1818
class LiteLlmApiDataStore extends AbstractLiteLlmDataStore
1919
{
2020
public function __construct(
21-
private readonly LiteLlmApiClient $apiClient,
22-
private readonly Repository $cache,
21+
private readonly LiteLlmApiClient $apiClient,
22+
private readonly Repository $cache,
2323
LoggerInterface $logger,
2424
LiteLlmDriverNameProviderNameMapping $nameMapping,
25-
private readonly Clock $clock = new Clock()
25+
private readonly CarbonClock $clock = new CarbonClock()
2626
)
2727
{
2828
parent::__construct($logger, $nameMapping);

app/Services/Ai/Tools/Implementations/TestTool.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
namespace App\Services\Ai\Tools\Implementations;
55

66
use App\Services\Ai\Tools\AbstractTool;
7-
use Carbon\Carbon;
7+
use App\Services\System\Time\CarbonClockInterface;
88
use Illuminate\Contracts\JsonSchema\JsonSchema;
9-
use Psr\Clock\ClockInterface;
109
use Psr\Log\LoggerInterface;
1110

1211
/**
@@ -55,9 +54,9 @@ public function schema(JsonSchema $schema): array
5554
}
5655

5756
public function __invoke(
58-
ClockInterface $clock,
59-
string $message,
60-
int $count = 1,
57+
CarbonClockInterface $clock,
58+
string $message,
59+
int $count = 1,
6160
): array
6261
{
6362
$this->logger->info('TestTool executed', [
@@ -73,7 +72,7 @@ public function __invoke(
7372
'instruction' => 'Now greet the user and let them know the tool test was successful. Do not call this tool again.',
7473
'original_message' => $message,
7574
'count' => $count,
76-
'timestamp' => (new Carbon($clock->now()))->toIso8601String()
75+
'timestamp' => $clock->now()->toIso8601String()
7776
];
7877
}
7978
}

app/Services/ExtApp/ConnectRequestCrypto.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use App\Services\ExtApp\Config\ExtAppConfig;
99
use App\Services\ExtApp\Repositories\ExtAppRepository;
1010
use App\Services\Frontend\Connection\Values\ExtAppConnectRequestPayload;
11-
use App\Services\System\Time\Clock;
11+
use App\Services\System\Time\CarbonClock;
1212
use Carbon\Carbon;
1313
use Hawk\HawkiCrypto\SymmetricCrypto;
1414
use Hawk\HawkiCrypto\Value\SymmetricCryptoValue;
@@ -27,7 +27,7 @@ public function __construct(
2727
private ExtAppConfig $config,
2828
private SymmetricCrypto $crypto,
2929
private ExtAppRepository $extAppRepository,
30-
private Clock $clock = new Clock()
30+
private CarbonClock $clock = new CarbonClock()
3131
)
3232
{
3333
}

app/Services/ExtApp/ExtAppUrlBuilder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use App\Models\ExtApp;
99
use App\Services\ExtApp\Config\ExtAppConfig;
10-
use App\Services\System\Time\Clock;
10+
use App\Services\System\Time\CarbonClock;
1111
use GuzzleHttp\Psr7\Uri;
1212
use Illuminate\Contracts\Routing\UrlGenerator;
1313

@@ -20,7 +20,7 @@
2020
public function __construct(
2121
private UrlGenerator $urlGenerator,
2222
private ExtAppConfig $config,
23-
private Clock $clock = new Clock(),
23+
private CarbonClock $clock = new CarbonClock(),
2424
)
2525
{
2626
}

0 commit comments

Comments
 (0)