*/ final class UseFrameworkAbstractionsRule implements Rule { /** @var array */ private array $forbiddenClasses = [ 'DateTime' => 'Use App\\Framework\\DateTime\\DateTime instead', 'DateTimeImmutable' => 'Use App\\Framework\\DateTime\\DateTime instead', 'PDO' => 'Use App\\Framework\\Database\\ConnectionInterface instead', 'mysqli' => 'Use App\\Framework\\Database\\ConnectionInterface instead', 'Redis' => 'Use App\\Framework\\Cache\\CacheDriver instead', 'GuzzleHttp\\Client' => 'Use App\\Framework\\HttpClient\\HttpClient instead', 'Predis\\Client' => 'Use App\\Framework\\Cache\\CacheDriver instead', ]; 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 layer (allow direct usage there) $currentClass = $scope->getClassReflection()?->getName() ?? ''; if (str_starts_with($currentClass, 'App\\Framework\\')) { return []; } if (! isset($this->forbiddenClasses[$className])) { return []; } return [ RuleErrorBuilder::message(sprintf( 'Direct instantiation of %s is forbidden: %s', $className, $this->forbiddenClasses[$className] ))->tip('Use dependency injection to get the framework abstraction')->build(), ]; } }