Drupal 10/11 Engineering Best Practices¶
Classification: PROCEDURE. Not authority; the governing standard below owns the rules.
Authority: drupal-standard
Applies to: All Drupal rigs, agents, recipes, and contrib modules across Bluefly.
1. Upstream Documentation First (Mandatory Preflight)¶
Before changing, configuring, extending, or debugging any contributed Drupal module:
1. Identify: Composer package name and machine name (composer.json).
2. Open Project Page: https://www.drupal.org/project/<machine_name>
3. Ecosystem Discovery: Check https://www.drupal.org/project/<machine_name>/ecosystem for companion modules and existing integration points.
4. Canonical Documentation: Follow the documentation link on the project page (project.pages.drupalcode.org/<project> or official guide).
5. Verify Version: Confirm composer.lock version matches documentation branch.
6. No Speculation: Never infer behavior from stale notes, memory, or random copies.
2. Coding & Architecture Invariants¶
2.1 Dependency Injection¶
- Never call
\Drupal::service(),\Drupal::currentUser(), or\Drupal::database()in classes that can receive dependencies (services, controllers, forms, plugins). The static wrapper is for procedural code only (.module,.install, hooks without a class) where injection is impossible (drupal.org: services and DI). - Use constructor injection via
services.yml. - Implement
ContainerInjectionInterfacefor Controllers, Forms, and Services. - Implement
ContainerFactoryPluginInterfacefor all Plugins (Blocks, Field formatters, Tool API plugins).
2.2 OOP Hooks (Drupal 11+) vs Event Subscribers¶
- OOP Hooks (
#[Hook('form_alter')]): Use for altering core/contrib behavior where module weight matters. - Event Subscribers (
KernelEvents::*, Symfony events): Use for decoupled, cross-cutting integrations, API responses, and external event listeners.
2.3 Configuration Schema (Mandatory)¶
- Every custom config object or third-party setting MUST have an explicit schema in
config/schema/<module>.schema.yml. - Schema strictness must pass
drush config:inspect --show-errors.
2.4 Cache Metadata¶
- Every render array, custom response, or computed data must carry full cache metadata:
$build['#cache'] = [ 'tags' => ['node_list', 'ai_context_item_list'], 'contexts' => ['user.permissions', 'url.query_args'], 'max-age' => 3600, ];
2.5 Security & Parameterized Queries¶
- Parameterized database queries only (
$database->select()or$database->query()with placeholders). - Never concatenate raw strings or user input into queries.
- Sanitize user strings with
t('@variable')orXss::filterAdmin().
3. CLI-First Scaffolding & Configuration Workflow¶
3.1 Drush Generation (Zero Manual Boilerplate)¶
Always use non-interactive Drush generators with --answers JSON:
# Non-interactive module generation
drush generate module --answers='{
"name": "ContextControl Bridge",
"machine_name": "contextcontrol_bridge",
"package": "Custom",
"dependencies": "ai_context:ai_context, eca:eca"
}'
3.2 Field & Content Type Creation¶
- Use
drush field:createinstead of manual YAML crafting:drush field:create ai_context_item agent_memory \ --field-name=field_kb_fingerprint \ --field-type=string \ --is-required=0 - Always follow with
drush config:export -y.
4. Production Release & Worktree Law¶
- Never edit code in
web/core,web/modules/contrib, orweb/themes/contrib. - Fix producer packages in their owning git repository under
BluCity/rigs/<name>orworktrees/. - Create bead-named worktree, make changes, run tests, commit, push MR to
release/v0.1.x. - Verify CI green in GitLab (
gitlab_components). - Run
composer update <package>in the consumer site repo to pull the released version.