Best Practices Decision Framework
When to Use
When making any decision about abstracting, reusing, or duplicating code in Drupal.
Decision Framework
+---------------------------------------------+
| Is it configuration or code? |
+-------------+-------------------------------+
|
+---------+----------+
| |
v v
Configuration Code Logic
| |
v v
Use config entity Is it used in
or config file multiple places?
| |
| +-------+--------+
| | |
| v v
| No (1-2) Yes (3+)
| | |
| v v
| Keep in class Extract to service,
| (don't abstract base class, trait,
| prematurely) or plugin
| |
v v
Export, version Apply Rule of Three
control, deploy (wait for 3rd use)
Security Considerations
| Practice |
Security Impact |
| Config as single source of truth |
Reduces XSS risk (config is sanitized), ensures consistent validation |
| Services instead of static calls |
Easier to audit dependencies, better access control |
| Avoiding duplicate validation logic |
One validation rule = one place to secure properly |
| Template inheritance |
Reduces XSS risk by reusing secured parent templates |
| Render arrays over raw HTML |
Automatic XSS protection, consistent sanitization |
Critical Security Anti-Pattern: Duplicating validation or sanitization logic across multiple places creates security vulnerabilities. One copy gets patched, others don't.
| Practice |
Performance Impact |
| Config caching |
Config reads are cached by default; reusing config is fast |
| Service instantiation |
Services are singletons; reusing services is free |
| Render array caching |
Structured render arrays support granular cache tags/contexts |
| Template inheritance |
Minimal overhead; Twig compiles templates to PHP |
| Over-abstraction |
Excessive layers of abstraction add function call overhead |
Performance Anti-Pattern: God services with 20+ methods often load more dependencies than needed, slowing instantiation.
Development Standards
| Standard |
DRY Application |
| Dependency injection over static calls |
Reuse container-managed services, don't duplicate \Drupal:: calls |
| Configuration over code |
Don't duplicate site settings in code |
| Services over utility classes |
Testable, injectable, reusable |
| Traits for cross-cutting concerns |
Reuse translation, logging, messaging patterns |
| Plugin system for extensibility |
Reuse plugin scaffolding instead of custom discovery |
Testing Implications
| Practice |
Testing Benefit |
| Services with DI |
Easy to mock dependencies, unit-testable |
| Config as single source |
Test config export/import, not hardcoded values |
| Shared base classes |
Test base class once, subclasses inherit tests |
| Thin controllers/commands |
Business logic in services is easier to test |
Decision Table
| Scenario |
Decision |
Reasoning |
| Need site name in 5 places |
Read from system.site config |
Single source of truth |
| Same validation in 3 forms |
Extract to service method |
Rule of Three, reusable |
| Similar blocks with shared config |
Create base block class |
Inherit common patterns |
| Translation needed in service |
Use StringTranslationTrait |
Standard Drupal pattern |
| API call from web and CLI |
Service called by controller and Drush |
Reuse business logic |
| Override one section of template |
{% extends %} parent template |
DRY markup structure |
| Reusable UI component |
SDC with props/slots |
Design system consistency |
See Also