Style¶
In addition to the standard Drupal coding standards, this module has
a handful of project-specific rules. Everything below applies to
both branches (2.x and 3.x) unless stated otherwise.
American English¶
All written language — docblocks, inline comments, commit messages,
README copy, config descriptions, log messages, exception text —
must be American English. The CI pipeline runs cspell, which is
configured for American spellings; British spellings (colour,
behaviour, organise, customisation, summarise, …) cause
pipeline failures.
Watch for:
-ise→-ize-our→-or-re→-er-yse→-yze- doubled-l past tense (
travelled) → single-l (traveled)
PHP attributes, not annotations¶
For all plugin types defined or extended here, use PHP 8 attributes
(#[RecordType(...)]) rather than Doctrine annotations
(@RecordType(...)). Attribute classes live in src/Attribute/,
never in src/Annotation/.
This is why core_version_requirement is ^10.3 || ^11 in
dns.info.yml — 10.3 is the first widely-stable release for custom
plugin-type attribute discovery.
The same does not apply to entity types, which still use the
@ContentEntityType annotation in this module — broader compat
surface, no functional gain from switching.
Plugin manager service injection — protected, not private readonly¶
Drupal forms (and any class composing
DependencySerializationTrait via an ancestor — controllers, list
builders, plugins of certain types) get serialized and woken from
form-state cache between the page load and the submit. The trait's
__wakeup re-injects services by writing back to the property.
__wakeup runs in the trait's host class scope (typically a parent
of yours), and from that scope it can't write to a private
property of a subclass. The write silently fails, the property stays
uninitialized, and the next read raises "Typed property … must
not be accessed before initialization".
The fix is consistent across this module: declare service
properties as protected (no readonly) in the class body, and
assign them in the constructor body.
// ❌ Broken: __wakeup can't restore this from an ancestor scope.
public function __construct(
EntityRepositoryInterface $entity_repository,
…
private readonly PluginManagerInterface $recordTypeManager,
) { … }
// ✓ Works: protected, non-readonly, assigned in body.
protected PluginManagerInterface $recordTypeManager;
public function __construct(
EntityRepositoryInterface $entity_repository,
…
PluginManagerInterface $record_type_manager,
) {
parent::__construct($entity_repository, …);
$this->recordTypeManager = $record_type_manager;
}
readonly is fine for genuinely-non-cached classes — constraint
validators, entities, value objects, controllers that don't reach
serialization. If in doubt, default to non-readonly.
Constraint validator naming¶
Drupal/Symfony resolves a constraint plugin's validator by appending
Validator to the constraint's class name. So XxxConstraint looks
for XxxConstraintValidator — not XxxValidator. Keep the
Constraint suffix in both names.
No Co-Authored-By trailers in commits¶
Commits in this repo must not include the
Co-Authored-By: Claude … trailer that some tooling appends by
default. Project history attribution is done at the commit-author
level.
Magic values¶
Promote a literal to a constant when:
- It's an RFC limit or other domain-meaningful number used in more
than one place (see
ZoneNameTransformer::MAX_NAME_LENGTH,MAX_LABEL_LENGTH,PUNYCODE_LABEL_PREFIX). - It's a contract between two layers (see
RecordTypeBase::FORM_KEY_RDATA, the form-state key the form builds and plugin validators read).
Don't promote:
- Permission strings, service IDs, config keys, route names, field-storage names — Drupal core uses string literals here and the convention is widely understood.
- Numeric maxlengths used in exactly one field declaration.
- Single-call-site bitmasks and PHP's own constants
(
JSON_THROW_ON_ERROR,IDNA_DEFAULT, etc.).
CI pipeline¶
The CI pipeline is reused verbatim from
field_ipaddress's .gitlab-ci.yml — Drupal's central GitLab
templates handle linting, unit tests, kernel tests, functional
tests, code-quality, and cspell. The only project-specific knob in
the file is _CSPELL_WORDS (project-name terms not in the
dictionary).