Drupal Development

Rules for maintaining high code quality in Drupal projects.

How these files work together

  • drupal-core.mdc is Always applied, giving every generation a Drupal foundation.

  • drupal-theme.mdc and drupal-tests.mdc are Auto Attached—they load only when you edit Twig or test files, keeping the model focused.

1 drupal-core.mdc — Core coding standards & patterns (Auto Attached)

---
description: >
  Drupal 10 core standards & your team’s personal preferences. Ensures strict
  typing, DI, visibility, final classes, and hook patterns.
globs:
  - "**/*.php"
  - "**/*.module"
  - "**/*.install"
  - "**/*.services.yml"
alwaysApply: true
---

# Drupal 10 Core Rules

1. **Strict Types & PSR‑12**  
   ~~~php
   declare(strict_types=1);
   ~~~

2. **Final Classes & Visibility**  
   - Declare every class `final` unless you explicitly intend it to be extended.  
   - Make all properties `private readonly` when possible; otherwise `private`.  
   - Methods default to `private`; use `protected`/`public` only as needed.

3. **Dependency Injection**  
   - Never call `\Drupal::service()` or `\Drupal::config()` in classes.  
   - Use constructor injection with promoted properties:  
     ~~~php
     public function __construct(
         private readonly ConfigFactoryInterface $config,
         private readonly LoggerChannelInterface $logger,
     ) {}
     ~~~

4. **Hook Implementations**  
   - Thin wrapper: delegate to an invokable class with the `@Hook` attribute.  
     See OOP hooks: https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Hook%21Attribute%21Hook.php/class/Hook/11.x  
   - Provide a `LegacyHook` bridge for procedural modules:  
     https://www.drupal.org/node/3442349

5. **Service Definitions**  
   ~~~yaml
   services:
     my_module.foo:
       class: Drupal\my_module\Foo
       arguments: ['@config.factory', '@logger.channel.my_module']
       tags: ['event_subscriber']
   ~~~

6. **Composer & Vendor**  
   - Add third‑party libraries via `composer require`.  
   - Never commit `vendor/`.

7. **Coding Standards & Checks**  
   - 2‑space indent, 80–120 col soft limit.  
   - Run `phpcbf --standard=Drupal,DrupalPractice` on staged files.

8. **Self‑Verification Checklist**  
   - [ ] Class is `final` and marked `strict_types`.  
   - [ ] All dependencies injected via constructor.  
   - [ ] No static calls to `\Drupal::`.  
   - [ ] Hooks use OOP attribute + LegacyHook.  
   - [ ] Services listed in `<module>.services.yml`.  
   - [ ] Visibility of properties/methods minimized.  

2 drupal-theme.mdc — Twig & front‑end (Auto Attached)

---
description: Drupal 10 theming & Twig best practices.
globs:
  - "**/*.html.twig"
  - "**/*.theme"
alwaysApply: false
---

# Drupal Theme Rules

* Limit Twig logic to simple conditionals/loops; heavy logic goes to `*.theme` preprocess.
* Follow **BEM** classes (`block__element--modifier`).
* Use `{{ attach_library('my_theme/component') }}` for assets.
* Prefer `|t` filter for translatable strings.
* To create template overrides, follow naming conventions (e.g. `node--article.html.twig`).
* Provide a matching `*.libraries.yml` entry for every new asset bundle.

3 drupal-testing.mdc — Testing patterns (Auto Attached)

---
description: PHPUnit Kernel & Functional test scaffolds.
globs:
  - "tests/src/**/*.php"
alwaysApply: false
---

# Drupal Testing Rules

1. **Base classes**  
   * Use `KernelTestBase` for unit‑ish tests needing services.  
   * Use `BrowserTestBase` for full‑stack functional tests.

2. **Fixtures & setup**  
   * Install required modules via `$this->enableModules(['node', 'my_module']);`
   * Use `$this->drupalCreateUser()` and `$this->drupalLogin()` helpers.

3. **Assertions**  
   * Prefer `$this->assertTrue()` / `$this->assertEquals()` over deprecated helpers.  
   * For rendered HTML, use `$this->assertSession()->elementExists('css', '.class');`.

4. **CI**  
   * Tests must pass with `phpunit -c core/phpunit.xml.dist`.