Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Clock Laravel Package

symfony/clock

Symfony Clock decouples your app from the system clock. Inject ClockInterface to get deterministic time via now() and control behavior like sleep() and time zones. Ideal for testable, time-sensitive code without relying on global time functions.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

  • Decoupling from System Clock: Aligns perfectly with Laravel’s dependency injection (DI) patterns, enabling testability and deterministic behavior for time-sensitive logic. The ClockInterface abstraction is a natural fit for Laravel’s service container and IoC principles.
  • Symfony Ecosystem Synergy: Seamlessly integrates with existing Symfony components (e.g., Messenger, HTTP Client) already used in Laravel via bridges like symfony/messenger or symfony/http-client. This reduces friction for teams already leveraging Symfony’s ecosystem.
  • Laravel Compatibility: Works alongside Carbon (Laravel’s default date library) without conflicts, as it focuses on time abstraction rather than date manipulation. Carbon’s now() can still be used where needed, but ClockInterface provides a cleaner abstraction for testing and timezone control.
  • Event-Driven Systems: Ideal for Laravel’s queue workers (e.g., Illuminate\Queue) or event listeners where time-based retries, delays, or deadlines are critical. Decouples logic from system clock inconsistencies (e.g., Lambda cold starts).

Integration Feasibility

  • Low-Coupling Design: Requires minimal refactoring—only services directly using time(), date(), or new DateTimeImmutable() need to depend on ClockInterface. Existing Laravel services (e.g., Carbon::now()) can coexist.
  • Service Container Integration: Laravel’s DI container natively supports ClockInterface, allowing easy binding of NativeClock (production) or MockClock (tests). Example:
    $this->app->bind(ClockInterface::class, function () {
        return new NativeClock(); // or MockClock in tests
    });
    
  • Carbon Bridge: While not native, Carbon can be wrapped in a ClockInterface implementation (e.g., CarbonClock) for gradual adoption:
    class CarbonClock implements ClockInterface {
        public function now(): DateTimeImmutable { return Carbon::now()->toImmutable(); }
        public function sleep(float $seconds): void { usleep($seconds * 1000000); }
    }
    
  • Timezone Handling: Laravel’s built-in timezone configuration (e.g., config/app.php) can be extended to default ClockInterface implementations, ensuring consistency across the app.

Technical Risk

  • Refactoring Overhead: Highly localized to time-dependent logic. Risk is mitigated by:
    • Starting with critical paths (e.g., subscriptions, payments).
    • Using Laravel’s replace() method in the service container to auto-wire ClockInterface without manual changes:
      $this->app->when(SubscriptionService::class)
                 ->needs(ClockInterface::class)
                 ->give(NativeClock::class);
      
  • Testing Impact: Initial test suite adjustments may be needed to replace Carbon::now() or time() calls with ClockInterface. However, this is a one-time cost with long-term benefits (deterministic tests).
  • Performance: Negligible runtime overhead for NativeClock (uses time() under the hood). MockClock adds minimal abstraction cost.
  • PHP Version: Requires PHP 8.1+ (due to Symfony 7+ dependencies). Laravel 10+ (PHP 8.1+) is fully compatible; older versions may need a polyfill or custom implementation.

Key Questions

  1. Scope of Adoption:
    • Should we adopt this globally (all time-dependent services) or incrementally (critical paths first)?
    • Tradeoff: Global adoption ensures consistency but requires broader refactoring; incremental reduces risk but may lead to fragmented time handling.
  2. Testing Strategy:
    • How will we enforce ClockInterface usage in new code? (e.g., static analysis, PHPDoc requirements).
    • Should we deprecate direct Carbon::now() usage in favor of ClockInterface?
  3. Timezone Strategy:
    • Will we enforce a single timezone (e.g., UTC) for business logic, or allow per-service overrides?
    • How will this interact with Laravel’s existing timezone settings (e.g., config/app.php)?
  4. Legacy Code:
    • How will we handle services using time(), date(), or global now() calls? (e.g., wrap in adapters, deprecate).
    • Should we use Laravel’s Facade pattern to hide ClockInterface behind a Time::now() facade for legacy code?
  5. Serverless/Queue Workers:
    • How will we handle sleep() in distributed environments? (e.g., MockClock::sleep(-1) to skip delays in tests).
    • Will we need custom ClockInterface implementations for specific workers (e.g., AwsClock for Lambda)?
  6. Observability:
    • Should we log "business time" (e.g., clock->now()) separately from server time for debugging?
    • How will this integrate with Laravel’s logging (e.g., Monolog) or APM tools (e.g., New Relic)?

Integration Approach

Stack Fit

  • Laravel Core: Fully compatible with Laravel’s DI container, service providers, and IoC. No conflicts with existing components (e.g., Carbon, Eloquent).
  • Symfony Components: Seamless integration with Symfony’s Messenger, HTTP Client, or UX components already used in Laravel via bridges.
  • Testing Frameworks: Works with PHPUnit (via MockClock), Pest, and Laravel’s testing helpers. Supports PHPUnit 10/11 attributes for test isolation.
  • Queue Workers: Ideal for Laravel Queues, Horizon, or serverless (e.g., AWS SQS, Lambda). Decouples job execution from system clock inconsistencies.
  • Event Sourcing/CQRS: Enables deterministic replay of time-based events (e.g., "expire subscription after 30 days") in tests.

Migration Path

  1. Assessment Phase (1–2 weeks):
    • Audit time-dependent logic: Identify services using time(), date(), Carbon::now(), or global now().
    • Prioritize critical paths (e.g., subscriptions, payments, scheduled jobs).
  2. Pilot Phase (2–4 weeks):
    • Refactor a single module (e.g., SubscriptionService) to use ClockInterface.
    • Implement NativeClock in production and MockClock in tests.
    • Example:
      // Before
      class SubscriptionService {
          public function checkExpiry() {
              $now = Carbon::now();
              // ...
          }
      }
      
      // After
      class SubscriptionService {
          public function __construct(private ClockInterface $clock) {}
          public function checkExpiry() {
              $now = $this->clock->now();
              // ...
          }
      }
      
  3. Container Integration (1 week):
    • Bind ClockInterface in AppServiceProvider:
      public function register() {
          $this->app->bind(ClockInterface::class, function () {
              return new NativeClock();
          });
      }
      
    • Override for tests:
      $this->app->instance(ClockInterface::class, new MockClock('2024-01-01'));
      
  4. Gradual Rollout (Ongoing):
    • Refactor remaining time-dependent services incrementally.
    • Deprecate direct Carbon::now() usage via static analysis (e.g., PHPStan rules).
  5. Testing Migration (Parallel):
    • Replace Carbon::now() in tests with MockClock.
    • Example:
      $clock = new MockClock('2024-01-01T12:00:00+00:00');
      $service = new SubscriptionService($clock);
      $service->checkExpiry(); // Deterministic!
      

Compatibility

  • Carbon: No conflicts; ClockInterface returns DateTimeImmutable, which Carbon can work with. Use CarbonClock adapter for gradual adoption.
  • Eloquent: Works alongside Eloquent timestamps (e.g., created_at). Use ClockInterface for business logic, not database operations.
  • Laravel Scheduler: Decouple scheduled jobs by injecting ClockInterface into job classes. Example:
    class SendReminderJob implements ShouldQueue {
        public function __construct(private ClockInterface $clock) {}
        public function handle() {
            if ($this->clock->now() > $user->reminder_at) {
                // ...
            }
        }
    }
    
  • Third-Party Packages: Most packages using time() or date() can be wrapped in a ClockInterface adapter. Example for Guzzle:
    $client = new Client(['clock' => new NativeClock()]);
    

Sequencing

  1. Critical Paths First:
    • Subscriptions/Payments → Scheduled Jobs → API Rate Limiting → User Sessions.
  2. Testing Infrastructure:
    • Update test suites to use MockClock before refactoring production code.
  3. Symfony Components:
    • Integrate with Messenger or HTTP Client after core time logic is
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport