awd-studio/vo-date-time
Immutable PHP 8.3+ date-time value object. Create from strings, compare (equal/greater/less/between), and return new instances for changes like nextDay(), copy(), or modified() with DateTimePeriod offsets (days, minutes, weeks).
OrderCreatedAt, EventScheduledAt). The immutability enforces thread safety and predictable state management, critical for financial systems, scheduling tools, or audit-heavy applications.Carbon but enforces stricter type safety and encapsulation. Can coexist with Carbon for parsing/serialization while enforcing VO constraints in domain layers (e.g., service layer or domain models).spatie/laravel-data or custom VO containers.isBetween(), modified(), and nextDay() encapsulate domain-specific date logic, reducing boilerplate and improving readability (e.g., $order->dueDate->isBetween($start, $end) vs. manual Carbon comparisons).DateTime::fromCarbon()/toCarbon()), easing migration from existing Carbon-heavy codebases.Validator::extend()) to enforce VO constraints (e.g., "due_date must be after created_at").2024-02-29, UTC vs. local time, negative time periods).Carbon for critical paths.DateTimePeriod objects where possible (e.g., new DateTimePeriod(days: 1)).JsonSerializable or custom accessors (e.g., getTimestamp()).modified() with timezone shifts). Workaround: Explicitly set timezone in constructor (e.g., DateTime::fromString('...', 'UTC')).spatie/calendar or ramsey/recurring-event.Order::createdAt) or entities with behavior (e.g., Event::scheduledAt with methods like isOverdue())?modified() should respect VO’s timezone) or localized comparisons?Carbon entirely or adopt a hybrid approach (e.g., VOs in domain layer, Carbon in persistence/API layers)?Carbon or timestamps?DateTime::fromTimestamp($row['created_at']) or custom Eloquent casts).TIMESTAMP columns) that need alignment?phpstan, PHPDoc @return annotations)?// Domain Model
class Order {
public function __construct(
public readonly DateTime $createdAt,
public readonly DateTime $dueDate,
) {}
}
| Phase | Action | Tools/Libraries |
|---|---|---|
| Assessment | Audit existing datetime usage (e.g., Carbon, timestamps, strings) across the codebase. Identify critical paths (e.g., billing, scheduling) and low-risk areas (e.g., logging). |
phpstan, psalm, grep |
| Pilot | Replace Carbon in 1–2 domain models (e.g., Invoice::dueDate, Event::scheduledAt). Use spatie/laravel-data or custom VO containers to enforce immutability. |
spatie/laravel-data, phpunit |
| Core Integration | Extend Laravel’s Form Requests and Validation to use VOs. Example: | Validator::extend() |
| ```php | ||
| Validator::extend('after_vo', function ($attribute, $value, $params) { | ||
| $vo = DateTime::fromString($value); | ||
| return $vo->isAfter(DateTime::fromString($params[0])); | ||
| }); | ||
| Persistence | Create accessors or Eloquent casts for database mapping. Example: | Eloquent, Carbon |
| ```php | ||
| protected $casts = [ | ||
| 'created_at' => DateTime::class, | ||
| ]; | ||
| ``` | ||
| Or use a custom cast: | ||
| ```php | ||
| public function getCreatedAtAttribute($value) { return DateTime::fromTimestamp($value); } | ||
| API Layer | Standardize datetime serialization (e.g., ISO 8601) via JsonSerializable or API transformers. Example: |
fractal/manager, laravel-transformer |
| ```php | ||
| class OrderTransformer extends Transformer { | ||
| public function transform(Order $order) { | ||
| return [ | ||
| 'due_date' => $order->dueDate->toString(), | ||
| ]; | ||
| } | ||
| Full Replacement | Replace Carbon with VOs in domain logic (e.g., services, repositories). Use Carbon polyfills for legacy persistence/API layers. Example: |
carbon/carbon |
| ```php | ||
| // Legacy DB query | ||
| $results = DB::table('orders |
How can I help you explore Laravel packages today?