From acc
Analyzes PHP code for dependency injection issues: detects new keyword in business logic, service locator antipatterns, static calls, missing interfaces, and hidden dependencies.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:check-dependency-injectionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Analyze PHP code for proper dependency injection patterns.
Analyze PHP code for proper dependency injection patterns.
// BAD: Hard-coded dependency
class OrderService
{
public function process(Order $order): void
{
$mailer = new Mailer(); // Can't mock this
$mailer->send($order->getCustomer(), 'confirmation');
}
}
// GOOD: Injected dependency
class OrderService
{
public function __construct(
private MailerInterface $mailer,
) {}
public function process(Order $order): void
{
$this->mailer->send($order->getCustomer(), 'confirmation');
}
}
// BAD: Service locator
class UserService
{
public function register(UserData $data): User
{
$hasher = Container::get(PasswordHasher::class);
$repository = Container::get(UserRepository::class);
$mailer = Container::get(Mailer::class);
// Dependencies are hidden
}
}
// GOOD: Constructor injection
class UserService
{
public function __construct(
private PasswordHasher $hasher,
private UserRepository $repository,
private Mailer $mailer,
) {}
// Dependencies are explicit
}
// BAD: Static calls can't be mocked
class ReportGenerator
{
public function generate(): Report
{
$data = Database::query('SELECT ...'); // Static
$date = Carbon::now(); // Static
$id = Uuid::uuid4(); // Static
return new Report($data, $date, $id);
}
}
// GOOD: Injectable services
class ReportGenerator
{
public function __construct(
private Connection $database,
private ClockInterface $clock,
private UuidGenerator $uuidGenerator,
) {}
public function generate(): Report
{
$data = $this->database->query('SELECT ...');
$date = $this->clock->now();
$id = $this->uuidGenerator->generate();
return new Report($data, $date, $id);
}
}
// BAD: Concrete class dependency
class PaymentProcessor
{
public function __construct(
private StripeGateway $gateway, // Concrete class
) {}
}
// GOOD: Interface dependency
class PaymentProcessor
{
public function __construct(
private PaymentGatewayInterface $gateway, // Interface
) {}
}
// BAD: Uses global/superglobal
class UserController
{
public function current(): User
{
$userId = $_SESSION['user_id']; // Hidden dependency
return $this->repository->find($userId);
}
}
// GOOD: Explicit dependency
class UserController
{
public function __construct(
private SessionInterface $session,
private UserRepository $repository,
) {}
public function current(): User
{
$userId = $this->session->get('user_id');
return $this->repository->find($userId);
}
}
// BAD: Optional setter injection
class OrderService
{
private ?Logger $logger = null;
public function setLogger(Logger $logger): void
{
$this->logger = $logger;
}
public function process(): void
{
$this->logger?->info('Processing'); // May be null
}
}
// GOOD: Constructor injection
class OrderService
{
public function __construct(
private LoggerInterface $logger,
) {}
public function process(): void
{
$this->logger->info('Processing');
}
}
// BAD: Factory logic in service
class NotificationService
{
public function send(string $type, string $message): void
{
$channel = match($type) {
'email' => new EmailChannel(),
'sms' => new SmsChannel(),
'push' => new PushChannel(),
};
$channel->send($message);
}
}
// GOOD: Inject factory
class NotificationService
{
public function __construct(
private ChannelFactory $channelFactory,
) {}
public function send(string $type, string $message): void
{
$channel = $this->channelFactory->create($type);
$channel->send($message);
}
}
// BAD: Direct environment access
class ApiClient
{
public function request(): Response
{
$key = getenv('API_KEY'); // Hidden dependency
// ...
}
}
// GOOD: Config injection
class ApiClient
{
public function __construct(
private string $apiKey, // Or ApiConfig object
) {}
}
# New keyword in methods
Grep: "new\s+[A-Z]\w+\(" --glob "**/*.php"
# Service locator
Grep: "Container::(get|make)|App::(make|resolve)" --glob "**/*.php"
# Static method calls
Grep: "[A-Z]\w+::\w+\(" --glob "**/*.php"
# Superglobals
Grep: "\$_(GET|POST|SESSION|COOKIE|ENV|SERVER)" --glob "**/*.php"
# getenv/putenv
Grep: "(getenv|putenv)\(" --glob "**/*.php"
// OK: Value objects and DTOs
new Money(100, 'USD');
new DateTime('now');
new OrderId($uuid);
// OK: Exceptions
throw new InvalidArgumentException();
// OK: In factories (that's their job)
class UserFactory {
public function create(): User {
return new User();
}
}
| Pattern | Severity |
|---|---|
| Service locator | 🟠 Major |
| New keyword for services | 🟠 Major |
| Static method calls | 🟠 Major |
| Superglobal access | 🟠 Major |
| Missing interface | 🟡 Minor |
| Setter injection | 🟡 Minor |
### DI Issue: [Description]
**Severity:** 🟠/🟡
**Location:** `file.php:line`
**Type:** [Service Locator|New Keyword|Static Call|...]
**Issue:**
[Description of the DI problem]
**Current:**
```php
$mailer = new Mailer();
Suggested:
public function __construct(
private MailerInterface $mailer,
) {}
Testing Impact: Cannot mock Mailer in unit tests. With injection, tests can use MockMailer.
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accDetects Singleton anti-pattern in PHP code. Identifies global state via static instances, hidden dependencies, tight coupling, and testability issues.
Designing loosely coupled code through dependency injection, reducing testability barriers and hidden dependencies.
Reviews code for testability issues: direct dependency imports, inline side effects, tight coupling, and global state access. Suggests DI, pure/impure separation, and abstraction patterns.