Awesome-claude-code check-singleton-antipattern
Detects Singleton anti-pattern in PHP code. Identifies global state via static instances, hidden dependencies, tight coupling, and testability issues.
install
source · Clone the upstream repo
git clone https://github.com/dykyi-roman/awesome-claude-code
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dykyi-roman/awesome-claude-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/check-singleton-antipattern" ~/.claude/skills/dykyi-roman-awesome-claude-code-check-singleton-antipattern && rm -rf "$T"
manifest:
skills/check-singleton-antipattern/SKILL.mdsource content
Singleton Anti-Pattern Detection
Analyze PHP code for Singleton anti-pattern usage that introduces global state and tight coupling.
Detection Patterns
1. Classic Singleton Implementation
// ANTIPATTERN: Static instance with private constructor final class DatabaseConnection { private static ?self $instance = null; private function __construct( private readonly PDO $pdo, ) {} public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(new PDO('mysql:host=localhost')); } return self::$instance; } } // Usage creates hidden dependency class UserRepository { public function find(int $id): User { $db = DatabaseConnection::getInstance(); // Hidden dependency! return $db->query("SELECT * FROM users WHERE id = ?", [$id]); } }
2. Registry / Service Locator (Singleton Variant)
// ANTIPATTERN: Global registry acting as singleton container final class Registry { private static array $services = []; public static function set(string $key, mixed $value): void { self::$services[$key] = $value; } public static function get(string $key): mixed { return self::$services[$key] ?? throw new RuntimeException("Not found: $key"); } } // Usage hides ALL dependencies class OrderService { public function create(array $data): Order { $repo = Registry::get('orderRepository'); // Hidden dependency $mailer = Registry::get('mailer'); // Hidden dependency } }
3. Static State Accumulation
// ANTIPATTERN: Mutable static state class EventBus { private static array $listeners = []; public static function subscribe(string $event, callable $listener): void { self::$listeners[$event][] = $listener; } public static function dispatch(string $event, mixed $data): void { foreach (self::$listeners[$event] ?? [] as $listener) { $listener($data); } } } // Problem: State leaks between tests, no way to reset
4. Late Static Binding Singleton
// ANTIPATTERN: Singleton via late static binding abstract class BaseSingleton { protected static array $instances = []; public static function getInstance(): static { $class = static::class; if (!isset(static::$instances[$class])) { static::$instances[$class] = new static(); } return static::$instances[$class]; } }
5. Framework Anti-Patterns
// ANTIPATTERN: Laravel Facade misuse (global state access) class OrderController { public function store(): Response { Cache::put('key', 'value'); // Static singleton access Log::info('Order created'); // Static singleton access Event::dispatch(new Created()); // Static singleton access // All dependencies hidden — not in constructor } } // CORRECT: Inject dependencies final readonly class OrderController { public function __construct( private CacheInterface $cache, private LoggerInterface $logger, private EventDispatcherInterface $events, ) {} }
Grep Patterns
# Classic singleton Grep: "static.*\$instance|getInstance\(\)|private function __construct" --glob "**/*.php" # Static service access Grep: "static function get|static function getInstance|static::getInstance" --glob "**/*.php" # Global state via static arrays Grep: "private static array|protected static array" --glob "**/*.php" # Registry / Service Locator Grep: "Registry::get|ServiceLocator::get|Container::get" --glob "**/*.php" # Mutable static properties Grep: "static \$[a-z]+ =" --glob "**/Domain/**/*.php"
Severity Classification
| Pattern | Severity |
|---|---|
| Singleton in Domain layer | 🔴 Critical |
| Service Locator pattern | 🔴 Critical |
| Static mutable state | 🟠 Major |
| Framework facade in Domain | 🟠 Major |
| Static helper/utility | 🟡 Minor |
Correct Alternatives
Use Dependency Injection
// CORRECT: DI container manages lifecycle final readonly class UserRepository { public function __construct( private PDO $connection, // Injected, not global ) {} public function find(UserId $id): User { // Use injected dependency $stmt = $this->connection->prepare('SELECT * FROM users WHERE id = ?'); $stmt->execute([$id->value()]); return $this->hydrate($stmt->fetch()); } } // Container configuration (single instance if needed) $container->singleton(PDO::class, fn () => new PDO($dsn));
Use Factory for Complex Creation
// CORRECT: Factory instead of Singleton final readonly class ConnectionFactory { public function __construct( private DatabaseConfig $config, ) {} public function create(): PDO { return new PDO( $this->config->dsn(), $this->config->username(), $this->config->password(), ); } }
Output Format
### Singleton Anti-Pattern: [Description] **Severity:** 🔴/🟠/🟡 **Location:** `file.php:line` **Issue:** [Description of the singleton/global state problem] **Impact:** - Hidden dependency — not visible in constructor - Tight coupling — cannot substitute in tests - Global state — shared mutable state between tests/requests **Code:** ```php // Current singleton usage
Fix:
// Refactored with dependency injection