From drupal-core
Quick inline code review for refactoring opportunities. Identifies over-engineering, god classes, missing interfaces, fat controllers, and Drupal anti-patterns.
npx claudepluginhub ajv009/drupal-devkitThis skill uses the workspace's default tool permissions.
You identify refactoring opportunities in Drupal custom code. Focus on practical improvements, not theoretical perfection.
You are a code refactoring expert specializing in clean code principles, SOLID design patterns, and modern software engineering best practices. Analyze and refactor the provided code to improve its quality, maintainability, and performance.
Analyzes code for smells, duplication, complexity, SOLID violations; suggests refactorings like Extract Method; implements incrementally with explanations, risks, and test preservation.
Applies 89 refactoring techniques across composing methods, moving features, organizing data, and generalization; detects 22 code smells like bloaters and OO abusers using PHP 8.3+ examples with before/after comparisons.
Share bugs, ideas, or general feedback.
You identify refactoring opportunities in Drupal custom code. Focus on practical improvements, not theoretical perfection.
Classes doing too much. Signs:
Fix: Extract responsibilities into focused services.
// BEFORE: God class
class ContentManager {
// Handles content creation, validation, notification, logging, caching...
}
// AFTER: Single responsibility
class ContentCreator { /* creates content */ }
class ContentValidator { /* validates content */ }
class ContentNotifier { /* sends notifications */ }
Controllers with business logic. Controllers should only:
// BAD: Logic in controller
public function process(Request $request): Response {
$data = $request->get('data');
// 50 lines of business logic...
return new JsonResponse($result);
}
// GOOD: Thin controller
public function process(Request $request): Response {
$result = $this->processingService->process($request->get('data'));
return new JsonResponse($result);
}
Hook implementations with business logic. Hooks should delegate to services:
// BAD: Logic in hook class
#[Hook('node_presave')]
public function nodePresave(NodeInterface $node): void {
// 30 lines of business logic directly in hook...
}
// GOOD: Hook delegates to service
#[Hook('node_presave')]
public function nodePresave(NodeInterface $node): void {
$this->contentProcessor->onPresave($node);
}
Custom services that are injected elsewhere should have interfaces:
// Define interface
interface DataProcessorInterface {
public function process(array $data): array;
}
// Implement
class DataProcessor implements DataProcessorInterface {
public function process(array $data): array { /* ... */ }
}
// Register with interface in services.yml
services:
my_module.data_processor:
class: Drupal\my_module\Service\DataProcessor
\Drupal:: or other static calls in src/ classes:
// BAD
class MyService {
public function getData(): array {
$config = \Drupal::config('my_module.settings');
$entityTypeManager = \Drupal::entityTypeManager();
}
}
// GOOD
class MyService {
public function __construct(
protected readonly ConfigFactoryInterface $configFactory,
protected readonly EntityTypeManagerInterface $entityTypeManager,
) {}
}
Same patterns repeated across files:
Signs of unnecessary complexity:
Rule: If it's not needed yet, don't build it. Three similar lines are better than a premature abstraction.
System boundaries (HTTP, database, file system, external APIs) need error handling:
// BAD: No error handling at API boundary
$response = $this->httpClient->get($url);
$data = json_decode($response->getBody());
// GOOD: Handle failures
try {
$response = $this->httpClient->get($url);
$data = json_decode($response->getBody(), TRUE, 512, JSON_THROW_ON_ERROR);
}
catch (GuzzleException $e) {
$this->logger->error('API request failed: @message', ['@message' => $e->getMessage()]);
return [];
}
| Anti-Pattern | Refactoring |
|---|---|
node_load() in loops | Node::loadMultiple($nids) |
| Raw SQL queries | Entity query or database abstraction |
db_query() (removed) | \Drupal::database()->query() or injected $database |
drupal_set_message() (removed) | \Drupal::messenger()->addMessage() |
format_date() (removed) | $this->dateFormatter->format() |
l() / url() (removed) | Url::fromRoute(), Link::fromTextAndUrl() |
variable_get/set() (D7) | Config API or State API |