acc-check-singleton-antipattern
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
More from dykyi-roman/awesome-claude-code
psr-overview-knowledge
PHP Standards Recommendations (PSR) overview knowledge base. Provides comprehensive reference for all accepted PSRs including PSR-1,3,4,6,7,11,12,13,14,15,16,17,18,20. Use for PSR selection decisions and compliance audits.
22detect-code-smells
Detects code smells in PHP codebases. Identifies God Class, Feature Envy, Data Clumps, Long Parameter List, Long Method, Primitive Obsession, Message Chains, Inappropriate Intimacy. Generates actionable reports with refactoring recommendations.
15clean-arch-knowledge
Clean Architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Clean Architecture and Hexagonal Architecture audits.
15ddd-knowledge
DDD architecture knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Domain-Driven Design audits.
14testing-knowledge
Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.
12bug-root-cause-finder
Root cause analysis methods for PHP bugs. Provides 5 Whys technique, fault tree analysis, git bisect guidance, and stack trace parsing.
12