Skip to content

Make CarbonImmutable the framework default#449

Merged
binaryfire merged 34 commits into
0.4from
feat/carbon-immutable-default
Jul 23, 2026
Merged

Make CarbonImmutable the framework default#449
binaryfire merged 34 commits into
0.4from
feat/carbon-immutable-default

Conversation

@binaryfire

@binaryfire binaryfire commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Why

Hypervel inherited Laravel's mutable Carbon default, but it does not inherit Laravel's request-per-process lifecycle.

A Hypervel worker retains services, metadata, caches, and other values across many requests. Mutable dates held by those objects can be changed through an alias, and ordinary modifier calls depend on side effects that are easy to miss when a value crosses a framework boundary. The framework already used immutable dates in several subsystems and exposed an immutable PSR clock, so the effective model was inconsistent.

This change makes immutable dates the default value model across the framework:

$expiresAt = now()->addMinutes(5);
$expiresAt = $expiresAt->addDay();

The API remains Carbon. The difference is that a modifier returns the value that must be retained.

Design

Hypervel\Support\CarbonImmutable is now the canonical framework date class. It is the immutable counterpart to Hypervel\Support\Carbon, not a wrapper around it.

Both classes share Hypervel's existing additions through a small DateHelpers trait:

  • conditionable and dumpable behavior;
  • createFromId() for time-based UUIDs and ULIDs;
  • plus() and minus().

Conversions are overridden at the class boundary so mutable and immutable values stay within the Hypervel class pair. They preserve Carbon settings, timezone, microseconds, subclass behavior, and the identity semantics of immutable-to-immutable conversion.

The ownership rule is:

  • Public or application-configurable construction uses Date, now(), or today() and is typed as CarbonInterface.
  • Framework-owned internal, retained, cached, or shared values use exact Hypervel\Support\CarbonImmutable.
  • Native and third-party boundaries use DateTimeInterface.
  • Hypervel\Support\Carbon remains the explicit mutable implementation.

This keeps configuration where it is useful without allowing application configuration to change the mutability of framework-owned state.

The monorepo and every split package that directly depends on Carbon now require nesbot/carbon:^3.13.1. This matches the version used for the framework-wide audit instead of claiming support for an older, untested Carbon patch.

Date factory

DateFactory now defaults to Hypervel\Support\CarbonImmutable.

Class handlers must be concrete, instantiable CarbonInterface implementations. This matches the return contract already exposed by helpers and the Date facade. Callable and Carbon Factory handlers remain available for deliberate transformations.

Applications that need mutable factory output can opt out during boot:

use Hypervel\Support\Carbon;
use Hypervel\Support\Facades\Date;

Date::use(Carbon::class);

The factory no longer accepts DateTime::class or arbitrary classes with a compatible-looking method. Those handlers could not satisfy the framework's typed date contract.

The generated Date facade, global helpers, Stringable conversion, public metadata, and test-time APIs now describe the configurable interface while asserting the exact immutable default.

Framework integration

The change audits date construction and mutation across the entire components repository.

Eloquent date and datetime casts now follow the configured Date factory and return the Hypervel immutable class by default. Explicit immutable casts always return Hypervel\Support\CarbonImmutable, including when an application opts the general factory into mutable dates.

Request casts and Eloquent formatted-date parsing now let Carbon's creator own format validation. This handles PHP format modifiers that Carbon's preflight helpers do not model completely, performs one parse on the normal path, and falls back to general parsing only after an actual format mismatch.

Data objects support all meaningful declared targets:

  • DateTimeInterface;
  • CarbonInterface;
  • native mutable and immutable DateTime;
  • Hypervel mutable and immutable Carbon;
  • base mutable and immutable Carbon.

Interface targets follow Date factory configuration. Concrete targets remain exact. Serialization is unified through DateTimeInterface.

Foundation lifecycle timestamps capture immutable timezone conversions and retain the converted value in their owning property or coroutine context. Scheduler minute boundaries no longer mutate the start time used by mutex decisions. Validation, queued mail, and queued notifications accept DateTimeInterface wherever immutable values are valid.

Authentication, cache, bus, queue, Horizon, sessions, routing, Telescope, collections, testing, and the remaining framework date consumers now use immutable held values and retain modifier results explicitly.

The existing PSR clock binding remains the only clock service. It now constructs the canonical Hypervel immutable class and remains independent from an application's mutable Date factory opt-out.

Carbon's process-global test state has one cleanup owner. Test clocks, macros, serialization callbacks, string formats, and strict mode are restored between tests without maintaining parallel mutable and immutable reset paths.

Protobuf initialization

Verification exposed a separate worker-lifecycle issue in generated protobuf metadata.

Generated descriptor registration is process-global and is not safe when multiple coroutines initialize the same message class concurrently for the first time. GrpcRouter::compileAndWarm() now constructs every registered request message and every statically named concrete response message that can be created without required arguments before workers fork.

This fixes the registration race and moves descriptor initialization off the request path. Response types hidden behind unions, iterables, or untyped handlers, and application client messages, cannot be discovered by the server router; the gRPC documentation explains that those message classes should be initialized during application boot.

Performance

The implementation does not add a clock abstraction, converter registry, runtime lock, per-instance wrapper, or raw timestamp layer.

Immutable modifier allocation is the mechanism that changes. Construction and factory dispatch remain effectively neutral, and profiling showed that the request-level effect is negligible for normal framework paths. Framework subsystems that already used immutable dates retain the same allocation model.

Internal fixed-type paths construct CarbonImmutable directly. Configurable paths continue through the existing Date facade. Formatted-date parsing removes preflight work from the matching path. Protobuf descriptor work moves from concurrent request execution to pre-fork boot.

The result keeps Carbon's API and predictable performance characteristics without introducing a second time system.

Migration

This intentionally changes the framework default.

Applications should:

  • replace concrete mutable Carbon declarations receiving helper, factory, cast, or request values with CarbonInterface or CarbonImmutable;
  • assign modifier results when the changed value must be retained;
  • ensure custom Date class handlers implement CarbonInterface;
  • use Date::use(Carbon::class) during boot only when mutable factory output is deliberate.

The upgrade guide, release notes, framework documentation, Laravel differences, contributor conventions, and implementation history have been updated together.

Verification

The complete formatter, static analysis, parallel component suite, Testbench contract suite, and dogfood suite pass.

Focused coverage exercises:

  • mutable and immutable class behavior and conversions;
  • factory handlers, helpers, facades, metadata generation, and cleanup;
  • DataObject input and declared-target matrices;
  • Eloquent and request date parsing and casts;
  • lifecycle timestamps and scheduler boundaries;
  • framework package expiry and deadline behavior;
  • PSR clock behavior and mutable opt-out isolation;
  • protobuf cold initialization and coroutine client isolation.

The generated Date facade converges with formatting and passes its dedicated lint check. The final repository-wide structural sweep found no stale mutable-default contracts or discarded modifier results in the migrated paths.

Introduce the canonical immutable counterpart to Hypervel Support Carbon while retaining the existing mutable class as an explicit opt-out.

Move the shared conditionable, dumpable, identifier, plus, and minus behavior into one trait. Override mutability conversions so both directions preserve the Hypervel class pair, subclasses, settings, timezone, and immutable identity semantics.

Cover the complete helper surface, serialization, conversions, late-static behavior, and immutable modifier behavior on both classes.
Resolve class-level @method metadata against the class that owns the docblock instead of borrowing constructor context or falling back to unresolved text.

Make generation fail fast when parsing or type resolution fails, and render imported global classes in the same stable form as PHP CS Fixer without shortening namespaced collisions.

Replace the obsolete fallback test with regressions for constructor-less owners, inherited constructors, global imports, namespaced imports, and colliding basenames.
Change DateFactory, the Date facade, global helpers, and Stringable date conversion to produce Hypervel CarbonImmutable by default while retaining CarbonInterface as the configurable public contract.

Restrict class handlers to instantiable CarbonInterface implementations, normalize callable handlers once, remove the dead generic constructor path, and keep Carbon Factory handlers and callable transformations available.

Regenerate facade metadata from the corrected owner and cover immutable defaults, mutable opt-out, custom Carbon subclasses, factories, callables, macros, locale behavior, invalid handlers, and helper return types.
Route DataObject date hydration through the configured Date factory, then convert the result according to the property declaration across native, Carbon interface, Hypervel Carbon, and base Carbon mutable and immutable targets.

Preserve configured subclasses only at interface boundaries, honor exact concrete targets without losing Carbon settings, accept timestamp zero, and let nullability fail through the constructor contract.

Unify date serialization on DateTimeInterface and add a full input, target, factory-mode, subclass, nullable, nested, serialization, and failure matrix.
Configure the existing PSR clock binding to construct Hypervel CarbonImmutable independently from application Date factory opt-outs.

Keep one authoritative Carbon static-state reset in the PHPUnit subscriber and restore the shared test clock, macros, serializer, string format, and strict-mode state between tests.

Verify exact clock output, frozen-time agreement, mutable factory isolation, and complete cleanup through both Hypervel Carbon variants.
Create frozen dates, wormhole targets, and Sleep deadlines through the configured Date factory while continuing to use Carbon shared static test-clock state.

Capture immutable modifier results for every time-travel operation and preserve callback return values and exception-safe restoration.

Cover immutable defaults, mutable opt-out, numeric deadlines, synchronization, each supported travel unit, and restoration after callback failures.
Store framework-owned HTTP request and console command start times as exact immutable values and capture timezone conversions instead of relying on mutable side effects.

Write the converted HTTP start value back to coroutine context so duration handlers and lifecycle accessors observe the same instance, then clear lifecycle state after completion or failure.

Exercise configured timezones, handler arguments, thresholds, coroutine-visible values, exception cleanup, and post-termination null state.
Use Hypervel CarbonImmutable for maintenance retry timestamps, bypass-cookie expiry, and worker-cached maintenance metadata.

Keep these framework-owned values independent from application Date factory configuration while preserving their existing wire formats and expiry behavior.

Update unit and integration coverage to assert the canonical Hypervel immutable class at each boundary.
Remove the mutable Carbon intermediate from request date casts and create standard, formatted, and fallback dates directly through DateFactory.

Let Carbon createFromFormat own modifier grammar, catch only its exact invalid-format exception, and retain generic parsing as the mismatch fallback.

Cover immutable output and valid PHP format modifiers, escaped literals, and trailing-data formats that Carbon preflight helpers do not model completely.
Use immutable framework dates for schedule listing, frequency calculations, and command logging while retaining CarbonInterface for the configurable scheduler start time.

Compute the repeat-loop end boundary from a copy, stop before work that crosses the minute, and leave the original start instant unchanged for single-server mutex decisions under both mutable and immutable Date modes.

Update scheduler unit and integration coverage for boundaries, frequencies, context propagation, grouped schedules, sub-minute execution, and mutable opt-out behavior.
Create Eloquent date casts, date query boundaries, and factory timestamps through the immutable Hypervel date model while preserving DateFactory configurability for ordinary date and datetime casts.

Return the canonical Hypervel immutable class for explicit immutable casts, capture modifier results, retain intentional mutable serialization conversions, and parse formatted values through Carbon rather than incomplete format preflight helpers.

Migrate database unit and integration expectations across model casts, timestamps, soft deletes, relations, factories, query builders, locks, cache storage, and all supported database drivers.
Widen the internal date parsing contracts from native mutable DateTime to DateTimeInterface so validator fallback parsing can return the configured immutable Carbon implementation.

Retain native DateTime format results while allowing DateFactory parsing for after, before, and sibling date-format comparisons.

Add regressions for immutable fallback results and update date-rule fixtures to capture immutable modifier values.
Return DateTimeInterface from queued mailable and notification retryUntil wrappers so application retry deadlines may use Hypervel CarbonImmutable without native return-type failures.

Preserve nullable property and method-based retry contracts and keep locale-sensitive mail and notification fixtures aligned with immutable test dates.

Cover both queued object types with immutable retry deadlines while retaining existing queue serialization behavior.
Use Hypervel CarbonImmutable for verification-link expiry and password-token repository deadlines so framework-owned authentication timestamps cannot be mutated after construction.

Preserve existing expiry calculations, storage formats, and comparison behavior while capturing all modifier results explicitly.

Update database and cache token repository coverage, verification-related middleware fixtures, and exact date expectations.
Store cache item and lock expiry values as exact Hypervel CarbonImmutable instances across array, worker-array, repository, session, stack, and Swoole stores.

Keep public DateInterval and timestamp behavior unchanged while preventing shared worker records from being shifted by later modifier calls.

Update cache unit and integration suites for immutable record shapes, TTL handling, locks, tags, memoization, sessions, database stores, Redis, and Swoole timers.
Use Hypervel CarbonImmutable for batch creation, cancellation, completion, pruning, debounce locks, batchable state, and batch repository fakes.

Preserve serialized repository data and public batch behavior while ensuring framework-held timestamps use one canonical immutable class.

Update batch, cancellation, debounce, retry-command, and fake-repository coverage to assert exact Hypervel immutable values.
Use Hypervel CarbonImmutable for queue availability, retry, pruning, worker timeout, and command timestamps while retaining DateTimeInterface at user-configurable scheduling boundaries.

Capture deadline modifiers explicitly and keep queue payload timestamps, database storage, pause and resume behavior, and driver protocols unchanged.

Migrate queue unit and integration coverage across database, Redis, SQS, Beanstalkd, deferred execution, rate limiting, throttling, chaining, failed jobs, work commands, and worker behavior.
Replace base CarbonImmutable values with Hypervel CarbonImmutable throughout Horizon job retries, trimming, monitoring, supervisors, process pools, metrics, and Redis repositories.

Keep Redis payloads and Horizon public behavior stable while ensuring repository hydration and worker-held timestamps retain the Hypervel helper surface and immutable semantics.

Update feature coverage for retrieval, metrics, wait monitoring, process repositories, queue processing, supervisors, trimming, and worker processes.
Use Hypervel CarbonImmutable for database and file session expiration and for session middleware lifetime calculations.

Preserve existing garbage collection, cookie lifetime, storage, and route-lock behavior while capturing every modifier result that must persist.

Update array, file, and database session handler coverage to use immutable clocks and exact expiry expectations.
Construct every registered request message and eligible concrete response message during the existing pre-fork router compilation phase.

This moves process-global protobuf descriptor registration out of concurrent request coroutines, avoids first-use registration races, and removes descriptor initialization from the request path. Keep response warming limited to statically named message classes that can be constructed without required arguments.

Also canonicalize gRPC call timestamps on Hypervel CarbonImmutable and add cold-process regressions for request and response descriptor warming alongside coroutine client isolation coverage.
Use Hypervel CarbonImmutable for the optional LazyCollection clock path and capture advancing timestamps explicitly during time-based iteration.

Keep native microtime behavior and lazy evaluation unchanged while making Carbon-backed intervals safe under immutable semantics.

Update lazy collection coverage for clock progression, throttling, and exact immutable values.
Use Hypervel CarbonImmutable for response cache expiry and temporary signed URL deadlines while preserving the existing HTTP and URL signature formats.

Capture date modifiers explicitly and keep user-supplied DateTimeInterface boundaries unchanged.

Align HTTP client, Redis throttling, and URL signing fixtures with the framework immutable default.
Use Hypervel CarbonImmutable for Telescope pruning and exception time windows, and express entry timestamp inputs through the existing CarbonInterface contract.

Preserve query watcher payloads, stored timestamps, and pruning behavior while removing mutable-only metadata.

Update watcher fixtures to capture immutable clock changes explicitly.
Describe Passkey and Sanctum model date attributes as CarbonInterface rather than concrete mutable Carbon classes so their metadata matches configurable Eloquent cast output.

Keep nullable attribute semantics and model behavior unchanged while allowing the immutable framework default and explicit mutable opt-out.

Align Sanctum expiry-pruning coverage with immutable modifier semantics.
Use Hypervel CarbonImmutable when TestResponse converts cookie timestamps and compares them with the current time.

Keep assertion messages and session-cookie handling unchanged while ensuring test helpers follow the same date semantics as the framework they exercise.
Point the WithImmutableDates attribute and Testbench workbench casts at Hypervel CarbonImmutable, and treat the attribute as an explicit forcing mechanism for applications that otherwise opt into mutable dates.

Update Testbench default configuration, timezone, migration, and attribute contracts to assert exact immutable output while preserving package-mode database behavior.

Keep all runtime skeleton cleanup and environment isolation aligned with the shared Testbench base classes.
Freeze JWT tests through Hypervel CarbonImmutable and obtain configured current timestamps through the Date facade.

Keep claim encoding, blacklist TTL, provider, and temporal validation behavior unchanged while making every fixture match the framework default.

Add missing native test return types in the touched validation coverage.
Update array, Fluent, validated input, and InteractsWithData coverage to use and assert exact Hypervel CarbonImmutable results from default date parsing.

Retain explicit coverage that InteractsWithData follows the mutable DateFactory opt-out, keeping configurable public boundaries honest.

Capture stronger exact-class assertions without weakening existing value, timezone, enum, or formatting checks.
Move remaining filesystem, exception throttling, nested set, pagination, permission, and translation fixtures to Hypervel CarbonImmutable.

Type native integration boundaries as DateTimeInterface, capture immutable clock changes, and assert canonical Eloquent date classes where the framework owns the result.

Preserve each package contract and existing behavioral assertions while removing mutable-default assumptions from the test suite.
Describe the immutable default across helpers, Eloquent, requests, data objects, collections, strings, mail, notifications, queues, mocking, and Testbench.

Show modifier-result assignment, CarbonInterface boundaries, exact concrete DataObject targets, explicit mutable opt-out, and the role of WithImmutableDates when an application changes its factory.

Document gRPC descriptor prewarming for route-discoverable messages and the boot-time responsibility for client, iterable, union, or otherwise undiscoverable protobuf message classes.
Record Hypervel CarbonImmutable as a 0.4 framework default and explain the value-semantics and worker-safety motivation.

Provide migration guidance for concrete mutable type declarations, retained modifier calls, custom CarbonInterface handlers, callable handlers, and the boot-time mutable opt-out.

Keep release notes concise while giving upgrading applications the exact code changes required.
Add immutable dates to Hypervel code and porting conventions, including construction boundaries, interface typing, modifier assignment, and the explicit mutable Carbon exception.

Record the Laravel difference, update historical plans whose examples describe current framework types, and remove the completed framework todo.

Include the framework-wide audit and implementation record covering architecture, performance research, migration ledgers, edge cases, testing strategy, and deliberate non-changes.
Restore the original mutable Carbon examples and guidance in the array-cache and passkeys plans so those documents remain accurate records of their implementation context.

Add concise notes near the top of each plan that identify the later immutable-date change, state the current behavior, and link to the framework-wide CarbonImmutable plan.

This keeps past debugging context intact without allowing historical snippets to masquerade as current framework guidance.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 277 files, which is 177 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b611c100-bd44-45c0-81d8-5ff25ae52673

📥 Commits

Reviewing files that changed from the base of the PR and between 9b89db7 and 76e3ff5.

📒 Files selected for processing (279)
  • AGENTS.md
  • composer.json
  • docs/ai/differences-vs-laravel.md
  • docs/plans/2026-06-29-array-cache-coroutine-local-worker-array.md
  • docs/plans/2026-07-01-fortify-passkeys-port.md
  • docs/plans/2026-07-22-carbon-immutable-default.md
  • docs/todo.md
  • src/auth/composer.json
  • src/auth/src/Notifications/VerifyEmail.php
  • src/auth/src/Passwords/CacheTokenRepository.php
  • src/auth/src/Passwords/DatabaseTokenRepository.php
  • src/boost/docs/collections.md
  • src/boost/docs/data-objects.md
  • src/boost/docs/eloquent-mutators.md
  • src/boost/docs/eloquent.md
  • src/boost/docs/grpc.md
  • src/boost/docs/helpers.md
  • src/boost/docs/mail.md
  • src/boost/docs/mocking.md
  • src/boost/docs/notifications.md
  • src/boost/docs/queues.md
  • src/boost/docs/releases.md
  • src/boost/docs/requests.md
  • src/boost/docs/strings.md
  • src/boost/docs/testbench.md
  • src/boost/docs/upgrade.md
  • src/bus/composer.json
  • src/bus/src/Batch.php
  • src/bus/src/BatchFactory.php
  • src/bus/src/Batchable.php
  • src/bus/src/DatabaseBatchRepository.php
  • src/bus/src/DebounceLock.php
  • src/cache/src/AbstractArrayStore.php
  • src/cache/src/ArrayLock.php
  • src/cache/src/ArrayStore.php
  • src/cache/src/Repository.php
  • src/cache/src/SessionStore.php
  • src/cache/src/StackStore.php
  • src/cache/src/SwooleStore.php
  • src/cache/src/WorkerArrayStore.php
  • src/collections/src/LazyCollection.php
  • src/console/src/Commands/ScheduleListCommand.php
  • src/console/src/Commands/ScheduleRunCommand.php
  • src/console/src/Scheduling/ManagesFrequencies.php
  • src/database/composer.json
  • src/database/src/Concerns/BuildsWhereDateClauses.php
  • src/database/src/Eloquent/Concerns/HasAttributes.php
  • src/database/src/Eloquent/Factories/Factory.php
  • src/facade-documenter/facade.php
  • src/foundation/composer.json
  • src/foundation/src/Console/DownCommand.php
  • src/foundation/src/Console/Kernel.php
  • src/foundation/src/Http/Kernel.php
  • src/foundation/src/Http/MaintenanceModeBypassCookie.php
  • src/foundation/src/Http/Traits/HasCasts.php
  • src/foundation/src/Providers/FoundationServiceProvider.php
  • src/foundation/src/Testing/Concerns/InteractsWithTime.php
  • src/foundation/src/Testing/Wormhole.php
  • src/foundation/src/WorkerCachedMaintenanceMode.php
  • src/foundation/src/helpers.php
  • src/grpc/composer.json
  • src/grpc/src/Server/GrpcRouter.php
  • src/grpc/src/Server/Middleware/HandleCall.php
  • src/grpc/src/Server/ServerCallContext.php
  • src/horizon/composer.json
  • src/horizon/src/Jobs/RetryFailedJob.php
  • src/horizon/src/Listeners/MonitorWaitTimes.php
  • src/horizon/src/Listeners/TrimFailedJobs.php
  • src/horizon/src/Listeners/TrimMonitoredJobs.php
  • src/horizon/src/Listeners/TrimRecentJobs.php
  • src/horizon/src/MasterSupervisor.php
  • src/horizon/src/ProcessPool.php
  • src/horizon/src/Repositories/RedisJobRepository.php
  • src/horizon/src/Repositories/RedisMasterSupervisorRepository.php
  • src/horizon/src/Repositories/RedisMetricsRepository.php
  • src/horizon/src/Repositories/RedisProcessRepository.php
  • src/horizon/src/Repositories/RedisSupervisorRepository.php
  • src/horizon/src/Supervisor.php
  • src/horizon/src/WorkerProcess.php
  • src/http/composer.json
  • src/http/src/Middleware/SetCacheHeaders.php
  • src/jwt/composer.json
  • src/mail/src/SendQueuedMailable.php
  • src/nested-set/composer.json
  • src/notifications/src/SendQueuedNotifications.php
  • src/passkeys/src/Passkey.php
  • src/queue/composer.json
  • src/queue/src/Console/PruneBatchesCommand.php
  • src/queue/src/Console/PruneFailedJobsCommand.php
  • src/queue/src/Console/WorkCommand.php
  • src/queue/src/DatabaseQueue.php
  • src/queue/src/Queue.php
  • src/queue/src/Worker.php
  • src/routing/src/UrlGenerator.php
  • src/sanctum/composer.json
  • src/sanctum/src/PersonalAccessToken.php
  • src/session/src/DatabaseSessionHandler.php
  • src/session/src/FileSessionHandler.php
  • src/session/src/Middleware/StartSession.php
  • src/support/composer.json
  • src/support/src/Carbon.php
  • src/support/src/CarbonImmutable.php
  • src/support/src/DataObject.php
  • src/support/src/DateFactory.php
  • src/support/src/Facades/Date.php
  • src/support/src/InteractsWithTime.php
  • src/support/src/Sleep.php
  • src/support/src/Stringable.php
  • src/support/src/Testing/Fakes/BatchFake.php
  • src/support/src/Testing/Fakes/BatchRepositoryFake.php
  • src/support/src/Traits/DateHelpers.php
  • src/support/src/functions.php
  • src/telescope/src/Console/PruneCommand.php
  • src/telescope/src/EntryResult.php
  • src/telescope/src/Http/Controllers/ExceptionController.php
  • src/testbench/src/Attributes/WithImmutableDates.php
  • src/testbench/workbench/database/migrations/2013_07_26_182750_create_testbench_users_table.php
  • src/testing/src/PHPUnit/AfterEachTestSubscriber.php
  • src/testing/src/TestResponse.php
  • src/validation/src/Concerns/ValidatesAttributes.php
  • tests/Auth/AuthDatabaseTokenRepositoryTest.php
  • tests/Auth/CacheTokenRepositoryTest.php
  • tests/Auth/RequirePasswordMiddlewareTest.php
  • tests/Bus/BusBatchTest.php
  • tests/Bus/BusBatchableTest.php
  • tests/Cache/CacheArrayStoreTest.php
  • tests/Cache/CacheDatabaseLockTest.php
  • tests/Cache/CacheDatabaseStoreTest.php
  • tests/Cache/CacheFileStoreTest.php
  • tests/Cache/CacheMemoizedStoreTest.php
  • tests/Cache/CacheRepositoryTest.php
  • tests/Cache/CacheSessionStoreTest.php
  • tests/Cache/CacheStackStoreTest.php
  • tests/Cache/CacheSwooleStoreIntervalTest.php
  • tests/Cache/CacheSwooleStoreTest.php
  • tests/Cache/CacheWorkerArrayStoreTest.php
  • tests/Cache/Redis/Operations/AllTag/FlushStaleTest.php
  • tests/Cache/Redis/RedisCacheTestCase.php
  • tests/Console/Scheduling/CacheSchedulingMutexTest.php
  • tests/Console/Scheduling/EventTest.php
  • tests/Console/Scheduling/FrequencyTest.php
  • tests/Console/Scheduling/ScheduleRunCommandTest.php
  • tests/Console/Scheduling/ScheduleRunContextPropagationTest.php
  • tests/Console/Scheduling/ScheduleTest.php
  • tests/Database/DatabaseEloquentBelongsToManyCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentBelongsToManySyncTouchesParentTest.php
  • tests/Database/DatabaseEloquentBuilderCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentBuilderTest.php
  • tests/Database/DatabaseEloquentFactoryTest.php
  • tests/Database/DatabaseEloquentHasManyCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentHasManyThroughCreateOrFirstTest.php
  • tests/Database/DatabaseEloquentIntegrationTest.php
  • tests/Database/DatabaseEloquentIrregularPluralTest.php
  • tests/Database/DatabaseEloquentModelTest.php
  • tests/Database/DatabaseEloquentRelationTest.php
  • tests/Database/DatabaseEloquentSoftDeletesIntegrationTest.php
  • tests/Database/DatabaseEloquentTimestampsTest.php
  • tests/Database/DatabaseQueryBuilderTest.php
  • tests/Database/DatabaseSoftDeletingTest.php
  • tests/Database/DatabaseSoftDeletingTraitTest.php
  • tests/Database/Eloquent/Concerns/DateFactoryTest.php
  • tests/Database/Eloquent/Factories/FactoryTest.php
  • tests/Database/QueryDurationThresholdTest.php
  • tests/FacadeDocumenter/AstVsTextFallbackTest.php
  • tests/FacadeDocumenter/ClassDocblockResolutionTest.php
  • tests/Filesystem/FilesystemAdapterTest.php
  • tests/Foundation/Console/KernelTerminateTest.php
  • tests/Foundation/FoundationExceptionsHandlerTest.php
  • tests/Foundation/FoundationHelpersTest.php
  • tests/Foundation/FoundationInteractsWithTimeTest.php
  • tests/Foundation/Http/CustomCastingTest.php
  • tests/Foundation/Http/KernelTest.php
  • tests/Foundation/Http/MaintenanceModeBypassCookieTest.php
  • tests/Foundation/Providers/FoundationServiceProviderTest.php
  • tests/Foundation/Testing/WormholeTest.php
  • tests/Foundation/WorkerCachedMaintenanceModeTest.php
  • tests/Grpc/ClientCoroutineIsolationTest.php
  • tests/Grpc/GrpcRouterTest.php
  • tests/Grpc/ServerCallContextTest.php
  • tests/Http/HttpClientTest.php
  • tests/Integration/Cache/Redis/TtlHandlingIntegrationTest.php
  • tests/Integration/Cache/RepositoryTest.php
  • tests/Integration/Console/CommandDurationThresholdTest.php
  • tests/Integration/Console/CommandSchedulingTest.php
  • tests/Integration/Console/Scheduling/ScheduleGroupTest.php
  • tests/Integration/Console/Scheduling/ScheduleListCommandTest.php
  • tests/Integration/Console/Scheduling/ScheduleTestCommandTest.php
  • tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php
  • tests/Integration/Database/DatabaseCacheStoreTest.php
  • tests/Integration/Database/DatabaseEloquentModelAttributeCastingTest.php
  • tests/Integration/Database/DatabaseLockTest.php
  • tests/Integration/Database/Eloquent/CastsTest.php
  • tests/Integration/Database/EloquentBelongsToManyTest.php
  • tests/Integration/Database/EloquentEagerLoadingLimitTest.php
  • tests/Integration/Database/EloquentModelDateCastingTest.php
  • tests/Integration/Database/EloquentModelImmutableDateCastingTest.php
  • tests/Integration/Database/EloquentModelTest.php
  • tests/Integration/Database/EloquentMorphManyTest.php
  • tests/Integration/Database/MariaDb/EloquentCastTest.php
  • tests/Integration/Database/MySql/EloquentCastTest.php
  • tests/Integration/Database/QueryBuilderTest.php
  • tests/Integration/Foundation/Configuration/WithScheduleTest.php
  • tests/Integration/Foundation/MaintenanceModeTest.php
  • tests/Integration/Horizon/Feature/JobRetrievalTest.php
  • tests/Integration/Horizon/Feature/MetricsTest.php
  • tests/Integration/Horizon/Feature/MonitorWaitTimesTest.php
  • tests/Integration/Horizon/Feature/ProcessRepositoryTest.php
  • tests/Integration/Horizon/Feature/QueueProcessingTest.php
  • tests/Integration/Horizon/Feature/SupervisorTest.php
  • tests/Integration/Horizon/Feature/TrimMonitoredJobsTest.php
  • tests/Integration/Horizon/Feature/TrimRecentJobsTest.php
  • tests/Integration/Horizon/Feature/WorkerProcessTest.php
  • tests/Integration/Http/ThrottleRequestsWithRedisTest.php
  • tests/Integration/Mail/Fixtures/timestamp.blade.php
  • tests/Integration/Mail/SendingMailWithLocaleTest.php
  • tests/Integration/Notifications/SendingNotificationsWithLocaleTest.php
  • tests/Integration/Queue/DebouncedJobTest.php
  • tests/Integration/Queue/JobChainingTest.php
  • tests/Integration/Queue/RateLimitedTest.php
  • tests/Integration/Queue/SkipIfBatchCancelledTest.php
  • tests/Integration/Queue/ThrottlesExceptionsTest.php
  • tests/Integration/Queue/ThrottlesExceptionsWithRedisTest.php
  • tests/Integration/Queue/WorkCommandTest.php
  • tests/Integration/Routing/UrlSigningTest.php
  • tests/Integration/Session/DatabaseSessionHandlerTest.php
  • tests/Jwt/BlacklistTest.php
  • tests/Jwt/ClaimFactoryTest.php
  • tests/Jwt/JwtManagerTest.php
  • tests/Jwt/Providers/LcobucciTest.php
  • tests/Jwt/Validations/ExpiredClaimTest.php
  • tests/Jwt/Validations/IssuedAtClaimTest.php
  • tests/Jwt/Validations/NotBeforeClaimTest.php
  • tests/Mail/MailableQueuedTest.php
  • tests/NestedSet/NodeTest.php
  • tests/Notifications/NotificationSendQueuedNotificationTest.php
  • tests/Pagination/CursorTest.php
  • tests/Permission/Traits/HasPermissionsWithCustomModelsTest.php
  • tests/Permission/Traits/HasRolesWithCustomModelsTest.php
  • tests/Queue/DatabaseFailedJobProviderTest.php
  • tests/Queue/DatabaseUuidFailedJobProviderTest.php
  • tests/Queue/FileFailedJobProviderTest.php
  • tests/Queue/QueueBackgroundQueueTest.php
  • tests/Queue/QueueBeanstalkdQueueTest.php
  • tests/Queue/QueueDatabaseQueueIntegrationTest.php
  • tests/Queue/QueueDatabaseQueueUnitTest.php
  • tests/Queue/QueueDeferredQueueTest.php
  • tests/Queue/QueuePauseResumeTest.php
  • tests/Queue/QueueRedisQueueTest.php
  • tests/Queue/QueueSqsQueueTest.php
  • tests/Queue/QueueWorkerTest.php
  • tests/Queue/RetryBatchCommandTest.php
  • tests/Sanctum/PruneExpiredTest.php
  • tests/Session/ArraySessionHandlerTest.php
  • tests/Session/FileSessionHandlerTest.php
  • tests/Support/DataObjectTest.php
  • tests/Support/DateFacadeTest.php
  • tests/Support/Fixtures/CustomDateClass.php
  • tests/Support/SleepTest.php
  • tests/Support/SupportArrTest.php
  • tests/Support/SupportCarbonImmutableTest.php
  • tests/Support/SupportCarbonTest.php
  • tests/Support/SupportFluentTest.php
  • tests/Support/SupportLazyCollectionIsLazyTest.php
  • tests/Support/SupportLazyCollectionTest.php
  • tests/Support/SupportStringableTest.php
  • tests/Support/Traits/InteractsWithDataTest.php
  • tests/Support/ValidatedInputTest.php
  • tests/Telescope/Watchers/QueryWatcherTest.php
  • tests/Testbench/Attributes/WithImmutableDatesTest.php
  • tests/Testbench/Databases/MigrateWithHypervelMigrationsTest.php
  • tests/Testbench/Databases/MigrateWithHypervelMigrationsUsingDatabaseMigrationsTest.php
  • tests/Testbench/Databases/MigrateWithHypervelMigrationsUsingRefreshDatabaseTest.php
  • tests/Testbench/Databases/MigrateWithHypervelTest.php
  • tests/Testbench/DefaultConfigurationTest.php
  • tests/Testbench/TimezoneTest.php
  • tests/Testing/PHPUnit/AfterEachTestSubscriberTest.php
  • tests/Translation/TranslationTranslatorTest.php
  • tests/Validation/ValidationDateRuleTest.php
  • tests/Validation/ValidationValidatorTest.php

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/carbon-immutable-default

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes Hypervel\Support\CarbonImmutable the framework default date class, replacing the inherited mutable Carbon default that was unsafe in Hypervel's long-lived Swoole worker model where held values cross request boundaries. It also pre-warms protobuf descriptor registration in GrpcRouter::compileAndWarm() to eliminate a coroutine-unsafe race on first instantiation.

  • A new CarbonImmutable class and a shared DateHelpers trait provide immutable/mutable conversion (toMutable/toImmutable) and keep createFromId, plus, and minus available on both classes. DateFactory now defaults to CarbonImmutable and validates class handlers to be instantiable CarbonInterface implementations.
  • DataObject gains a full eight-type date target matrix (interface, native, and all four Hypervel/base Carbon concrete classes), with unified DateTimeInterface-based serialization in toArray that fixes previously missing serialization for CarbonImmutable values. Format parsing in HasAttributes and HasCasts drops the Carbon::hasFormat preflight in favour of direct createFromFormat with InvalidFormatException catch, correctly handling PHP format modifiers the preflight didn't model.
  • Framework-wide audit replaces Carbon::now() with Date::now() and captures modifier results on immutable instances; the Carbon requirement across all affected split packages is bumped to ^3.13.1.

Confidence Score: 5/5

Safe to merge. The immutability change is a deliberate breaking change with a documented migration path, and the implementation is consistent, well-tested, and audited across 279 files.

The core CarbonImmutable class, DateHelpers trait, DateFactory validation, and DataObject target matrix are all logically correct. Conversion semantics (crossing the mutability boundary before calling instance() to force construction of the exact target class) are tested across all four Carbon concrete types. The DateTimeInterface instanceof check in toArray correctly unifies serialization for both mutable and immutable values where the old exact-class dispatch missed CarbonImmutable. The GrpcRouter protobuf pre-warming includes a required-argument guard before calling newInstance(). The Date::createFromFormat + InvalidFormatException catch pattern replaces the incomplete Carbon::hasFormat preflight correctly in both HasAttributes and HasCasts. No behavioral regressions are visible in the framework-owned paths, and the full test suite is reported passing.

No files require special attention; the changes are internally consistent.

Important Files Changed

Filename Overview
src/support/src/CarbonImmutable.php New canonical immutable date class; implements toMutable/toImmutable with cast() conversions and shares DateHelpers; docblock @method annotations have an ambiguous "static" qualifier but phpstan passes.
src/support/src/Carbon.php Simplified to delegate shared behavior via DateHelpers trait; conversion methods (toMutable/toImmutable) use cast() and correctly cross the mutability boundary.
src/support/src/DateFactory.php Default changed to CarbonImmutable; useClass now validates instantiable CarbonInterface; callable stored as Closure; __call fallback now always uses instance() on all CarbonInterface paths; handler dispatch order reordered.
src/support/src/DataObject.php Date target matrix expanded to eight types including Hypervel and base Carbon/CarbonImmutable; asDateTime now routes to the correct exact target; toArray serialization uses instanceof DateTimeInterface first, fixing missing serialization for CarbonImmutable values.
src/support/src/Traits/DateHelpers.php New trait consolidating Conditionable, Dumpable, createFromId, plus, and minus into a shared mixin used by both Carbon and CarbonImmutable.
src/database/src/Eloquent/Concerns/HasAttributes.php Switches import from base Carbon\CarbonImmutable to Hypervel\Support\CarbonImmutable; replaces Carbon::hasFormat preflight with try/catch InvalidFormatException on Date::createFromFormat; standard date path now uses Date::parse instead of Date::instance(Carbon::createFromFormat).
src/grpc/src/Server/GrpcRouter.php Adds pre-fork instantiation of request and response protobuf Message classes via compileAndWarm to fix coroutine-unsafe global descriptor registration; response class detection uses handler return type reflection with required-argument guard.
src/support/src/InteractsWithTime.php Replaces Carbon::now() with Date::now() so availableAt, parseDateInterval, and currentTime respect the configured date factory; modifier results are captured in assignments.
src/testing/src/PHPUnit/AfterEachTestSubscriber.php Single-owner cleanup pattern: Carbon-level static resets (macros, test time, format, serialization, strict mode) cover both mutable and immutable through shared Carbon 3.13.1 state.

Reviews (2): Last reviewed commit: "build: raise the Carbon minimum to 3.13...." | Re-trigger Greptile

Comment thread src/grpc/src/Server/GrpcRouter.php
Comment thread src/support/src/DataObject.php
Comment thread src/testing/src/PHPUnit/AfterEachTestSubscriber.php
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptileai I checked all three observations against the current branch, PHP 8.4 reflection, and the installed Carbon 3.13.1 source.

  • The closure self response case already resolves correctly. Class-scoped closure parameters retain their declaring class; a global closure cannot legally use self.
  • The DataObject mutability crossing is deliberate. It prevents Carbon instance() from cloning a same-mutability subclass into an exact concrete target, while preserving Carbon settings. The target and factory-mode matrix covers this behavior.
  • Carbon static cleanup has one real owner in 3.13.1: every relevant operation delegates to FactoryImmutable::getDefaultInstance(). The regression covers macros, test time, string formatting, serialization, and strict mode through both date classes.

The review identified useful maintenance boundaries, but no code change is warranted.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Acknowledged on all three. The explanations are correct:

  1. Closure self — already conceded in the thread; global closures cannot legally declare self, and class-scoped closures carry their declaring class through reflection.
  2. DataObject mutability crossing — the cross-to-immutable-then-instance() pattern is the precise mechanism for preventing instance() from cloning a same-mutability subclass, and the test matrix covers it.
  3. Carbon static cleanup — noted the version correction (3.13.1, not 3.8.4). With all relevant operations explicitly delegating to FactoryImmutable::getDefaultInstance(), a single mutable reset owner is correct. Defensive duplication would be dead code, and a future Carbon refactor will surface through the regression test.

No changes warranted. Thanks for the thorough verification.

Require the audited Carbon release from the monorepo and every split package that declares a direct Carbon dependency.

The immutable-date implementation is built and tested against Carbon 3.13.1. The previous 3.8.4 floor was not covered by a lowest-dependency suite, so retaining it would claim support for behavior this change did not verify.

Record the dependency baseline and its completion check in the implementation plan.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptileai Correction to my earlier summary: the review was right about the declared Carbon version. The local install was 3.13.1, but the root and split-package manifests still allowed ^3.8.4; I conflated the resolved development version with the published support floor.

That mismatch is fixed in 76e3ff5. The monorepo and every split package with a direct Carbon dependency now require ^3.13.1, matching the version used for the implementation audit and full test run. The cleanup design itself remains unchanged.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

The fix is clean and complete. Every composer.json that had a direct Carbon dependency — monorepo root plus all 13 split packages — is updated consistently. The commit message also correctly explains why the old floor was untenable: there was no lowest-deps CI run covering ^3.8.4, so retaining it would have been an unverified support claim.

Thanks for the clear correction. The original comment stands resolved — the version floor now matches the audited implementation baseline.

@binaryfire
binaryfire merged commit 1d48f8b into 0.4 Jul 23, 2026
36 of 38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant