*/ final class UseDateTimeAbstractionRule implements Rule { /** @var array */ private array $forbiddenDateTimeClasses = [ 'DateTime' => 'Use App\\Framework\\DateTime\\DateTime instead for consistent timezone handling', 'DateTimeImmutable' => 'Use Clock::now() or Clock::fromString() instead for testable time handling', 'DateInterval' => 'Use App\\Framework\\DateTime\\DateTime::createInterval() instead for consistent error handling', 'DatePeriod' => 'Use App\\Framework\\DateTime\\DateRange instead for better domain modeling', ]; public function getNodeType(): string { return New_::class; } public function processNode(Node $node, Scope $scope): array { if (! $node instanceof New_) { return []; } if (! $node->class instanceof Node\Name) { return []; } $className = $node->class->toString(); // Skip if we're already in Framework DateTime layer (allow direct usage there) $currentClass = $scope->getClassReflection()?->getName() ?? ''; if (str_starts_with($currentClass, 'App\\Framework\\DateTime\\')) { return []; } if (! isset($this->forbiddenDateTimeClasses[$className])) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Direct instantiation of %s is forbidden: %s', $className, $this->forbiddenDateTimeClasses[$className] )) ->tip('Use Clock interface or Framework DateTime abstractions via dependency injection') ->build(), ]; } }