From drupal-workflow
Enforces Drupal 10/11 coding standards using PHPCS, PHPStan, naming conventions, validation commands, PHPDoc rules, and anti-pattern fixes. Use for code reviews, static analysis, and pre-commit checks.
npx claudepluginhub gkastanis/drupal-workflow --plugin drupal-workflowThis skill uses the workspace's default tool permissions.
Always run PHPCS before committing. Never skip static analysis. Use `declare(strict_types=1)` in every PHP file. Prefer `final` classes unless explicitly designed for extension. Avoid deep nesting — use guard clauses and early returns.
Validates Drupal PHP, CSS, and JS against coding standards, SOLID/DRY principles, security practices before commits. Enforces Gate 1 quality gates using checklists and git diff.
Reviews WordPress PHP/JS/CSS/HTML code against WPCS standards, static analysis with PHPCS/PHPStan/ESLint, architecture patterns, error handling, and deprecated functions. Use for custom themes/plugins.
Enforces core Drupal 10+ rules for services, dependency injection, security including sanitization and access control, code quality, and testing verification. Always use when writing Drupal code.
Share bugs, ideas, or general feedback.
Always run PHPCS before committing. Never skip static analysis. Use declare(strict_types=1) in every PHP file. Prefer final classes unless explicitly designed for extension. Avoid deep nesting — use guard clauses and early returns.
# Check coding standards.
./vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom/my_module/
# Auto-fix standards issues.
./vendor/bin/phpcbf --standard=Drupal web/modules/custom/my_module/
# Static analysis.
./vendor/bin/phpstan analyse web/modules/custom/my_module/
# Deprecation check.
drupal-check web/modules/custom/my_module/
# Security audit.
composer audit
<?php
declare(strict_types=1);
namespace Drupal\my_module;
/**
* Manages content operations.
*/
final class ContentManager implements ContentManagerInterface {
public function __construct(
private readonly EntityTypeManagerInterface $entityTypeManager,
private readonly LoggerInterface $logger,
) {}
| Element | Convention | Example |
|---|---|---|
| Local variables | $snake_case | $user_name |
| Class properties | $lowerCamelCase | $this->entityManager |
| Classes | PascalCase | ContentManager |
| Interfaces | PascalCase + Interface | ContentManagerInterface |
| Constants | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
| Services | module.service_name | my_module.content_manager |
| Hooks | module_hook_name | my_module_form_alter |
| Anti-Pattern | Fix |
|---|---|
foreach with nested if/break/continue | array_filter/array_map/array_reduce |
| Deep nesting (3+ levels) | Guard clauses, early returns |
| Non-final classes without reason | Declare final by default |
Getters/setters where public readonly works | Use public readonly |
\Drupal:: calls in classes | Constructor dependency injection |
PHP json_encode/decode | \GuzzleHttp\Utils::jsonDecode/jsonEncode |
| "Service" namespace | Use logical groupings |
Catching \Exception | Catch narrowest exception type |
/**
* Loads articles by category.
*
* @param int $category_id
* The taxonomy term ID.
* @param int $limit
* Maximum number of results.
*
* @return \Drupal\node\NodeInterface[]
* Array of article nodes.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function loadByCategory(int $category_id, int $limit = 10): array {
modules/custom/my_module/
my_module.info.yml
my_module.module
my_module.services.yml
my_module.routing.yml
my_module.permissions.yml
config/
install/
schema/
src/
Controller/
Form/
Plugin/
Block/
EventSubscriber/
tests/
src/
Unit/
Kernel/
Functional/