Issue #3618938: Name things the way Drupal core does: a verb and an object on every reader, every hook method named after its hook, the Trait and Hooks suffixes, American spelling, snake_case variables, and Drupal's own words for block, note and chip
This is the merge request for [#3618938].
Every candidate was counted against core itself, over core/lib and core/modules, so a finding here is a difference from core rather than a difference from my taste. A word core never uses is only a defect where core already has a word for the same thing.
What changed, commit by commit
- Test traits. 11 traits did not end in
Trait, against 265 of core's 269 that do (one of the four exceptions is a typo,LinkInputValuesTraits). A sentence-shaped name in auselist gives no clue that it is a trait. - Core's verbs on the readers.
givebecomesrelease(core has nogive-prefixed method and 8release-prefixed ones).activePinning(),activeConfiguration()andactiveOrder()becomegetActive*, matching core's 16 and its zero bareactive.recomputeState()/recomputeOnce()becomecomputeState()/computeOnce(), core having 9compute*and norecompute.giveUpFirst()issortForRelease(), which says that it returns an ordering rather than performing one. - Form template methods.
columns()andcells()do what core callsbuildHeader()(35 uses) andbuildRow()(37), andSelectableOverviewFormBasealready declared abuildRow()beside a bareheader(), so this is the module's own precedent as much as core's. The rest take a verb:getTabRoute(),getCollectionRoute(),getEmptyText(),getSavedMessage(),getStorage(),getTableKey(),getActionOptions(),getBatchTitle(),getSelectionWarning(),getActiveChips(),getEntityTypeId(). Core's form template methods are consistently verb-prefixed:getCancelUrl()52,getQuestion()50,getConfirmText()42. of()readers named for what they return. Core has noof-prefixed method at all.LineFields::of()isfromValues(),SubsectionNumber::of()isparse(),ResourceFieldGroups::of()isgetGroup(),FieldTables::of()isget(),MapLayoutSpacing::ofPitch()isfromPitch(), and the three unrelatedofSlot()methods becomegetTotal(),getAllocations()andgetSlotTariffs(). Naming them for the return value rather than for the argument also keeps them clear of the separatefor*question below.- American spelling.
canceled,canceling,labeled,center,color,signaled,gray.ResourceTypeCataloguewas British and also not core's word for "which of these are available here", so it isResourceTypeRepository, which is what core's own jsonapi calls the same job. - Hook and subscriber classes. 371 of the 442 classes in core's
Hook/directories end inHooks; 46 of ours did and 7 did not, so all 53 do now. 106 of the 127 classes in core'sEventSubscriber/directories end inSubscriber; ten of ours did not and now do. - Names that leaned on a preposition. 101 declarations, the family an
earlier draft of this summary deferred. Deferring it was wrong: these are the
names that actually stop a reader. The rule is the call site, not the
declaration -
getTotal(1)does not say that1is a slot id, and a variable is no better than a literal - so a method whose subject is a scalar names it (getSlotTotal(),getSlotTaken(),getSlotVenue(),getMessageLabel()), while one taking a typed object only needs its verb (getCloseness(),getStretches(),getPartitions()). A trailing preposition survives only behind a verb:applyTo(),refuseWith()andinheritsFrom()are whole sentences,getScopesFor()andgetTotalFor()left the reader asking "for what?". Two were not stylistic at all:SessionPlaceRecord::setUnits()accumulates a signed delta and drops the row when it cancels to nothing, so the name said the opposite of the body and is nowadjustUnits(); anddomainsOf()was one name over two subjects, nowgetTenantDomains()andgetChannelDomains(). - Words that named nothing. Four vocabulary problems, each raised by reading the names aloud rather than by counting core.
getActiveChips()and itschip_element keys carried a Material Design word for what the page calls a filter summary.blocksOf(),freeCountsInBlocks()and their neighbours usedblock, which is Drupal's own noun for something else entirely, so they saysection.notewas banned outright as too vague to survive a reader:noteReleased()/noteGone()aresetReleased()/setGone(), and the payload keys, render keys and CSS classes saydescription. Andpiecewas a second name for a thing already named: a party that cannot sit together sits in several runs of adjacent places, which is what the geometry has always called them, sopieceSizes()isgetRunSizes()andfewestPieces()isfewestRuns(). The ordinary English senses stay - a piece of work, a piece of equipment, the pieces an SVG path is cut into. - snake_case variables. 29 parameter names and 57 local names were lowerCamel while the rest of the module already used snake_case. Promoted constructor parameters are properties, so those stay lowerCamel: that distinction is what makes this not a blind sweep, and phpcs caught four
@paramlines where an early pass got it wrong.
Documentation and the French translations move in the same commits.
-
Every hook implementation named after its hook. Core names 1158 of its 1252
#[Hook]methods exactly after the hook (92%); this module had 52 of 146 and has 141 now. It also settles disagreements the old names hid: three classes answeredyoyaku_booking_deletewithbookingDelete()while a fourth called itbookingDeleted(), andyoyaku_slot_tariff_insertwasslotTariffInsert()in one class andslotTariffWritten()in another. The five left descriptive are core's own exception, a class implementing one hook several times:PaymentUiHooksanswersyoyaku_ui_entity_table_alterthree times andPlacementHookstwice, soaddPriceColumn()andaddGradesColumn()cannot both take the hook's name. -
The fee arithmetic moved out of the hook class.
FeeSummaryHooksheld three private methods doing money maths; core's 429Hook/classes hold 19 private helpers between them, because a hook class answers a hook rather than keeping a module's arithmetic. It isFeeTotalsnow and the hook class is 171 lines down to 84. Not a duplication fix:FeeRecordSubscriberwrites the fee fields and splits charged from absorbed,FeeTotalsreads them back or prices a basket that never reached a checkout. -
Say policy where the name only said attachment. An attachment here is a constraint policy paired with the host it hangs on, and four readers never mentioned the policy:
getAttachedPoliciesForLine(),getAttachedPoliciesInContext(),getPolicyLabel()andisPolicyAttachableTo(). Attached rather than plain policies, because the return is pairs of policy and scope whilegetPoliciesForResource()and its siblings hand back bare policy entries. Ten more came out of the same question, every one hidden by the audit's verb list admittingentity,field,formandnodeas verbs -entityPolicies(),entity(),fieldValue(),formKey(),node()andfield()twice over, which was the cookie field a contributor owns in one class and the query path for a host entity type in another. -
Two words the glossary leaned on without defining.
concepts.mdnever said what an attachment is, whileconstraint-policies.mdused it from its middle onward; the word is also Drupal's for a render array's assets and Orchestra's for a file on a notification. Both are pinned there now.
Deliberately not changed
Recorded on the issue so a later audit does not "fix" them. Each was flagged by a first pass and then cleared by reading what core actually does.
The stored valueIt moved too. The first pass kept the value and renamed only the constant, on the grounds that a stored value is a migration rather than a rename. That left the codebase saying'cancelled'stays.STATE_CANCELED = 'cancelled', which is the worst of both spellings. The project is pre-1.0 and reinstall-only, so there is no migration to write: the value, theorder_retentionconfig key, its schema, and the workflow payloads that route on the literal all saycancelednow. What stays British is only what belongs to another project, below.- kessai's constants are kessai's.
PaymentInterface::STATE_CANCELLEDandPaymentEvents::CANCELLEDkeep their spelling. phpstan caught the second one after a sweep reached it. - Bare-noun readers on value objects stay.
PolicyContext::lines(),PolicyOutcome::violations(),PolicyScope::host()andTransactionContext::transaction()read oddly but core does the same in the same kind of class: jsonapi'sEntityConditionhasfield(),operator()andvalue(), and workflows'Statehasid(),label()andweight(). On config entities, core's ownNodeTypeInterfacedeclaresdisplaySubmitted(), which is the shape ofautoConfirms(). - Snake_case properties on config entities stay, because the property name is the config key, as core's
NodeType::$new_revisionis. - Abstract classes not ending in
Basestay. 31 of the 110 abstract classes in core's lib are named that way,DraggableListBuilderamong them. - PascalCase constraint plugin ids stay, which is what core uses.
- The service ids stay. The ones without a module prefix are core's own shapes (
logger.channel.*,cache_context.*,plugin.manager.*). Onlyresource_type_cataloguemoved, for its spelling. BookingWorkflowStarterkeeps its name. It sits in anEventSubscriberdirectory but implementsWorkflowStarterInterface, notEventSubscriberInterface, so it is a misplaced file rather than a misnamed class. Worth a follow-up, not a rename here.
What the audit got wrong
Recorded because the next audit should not repeat it.
-
Its verb list was learned by frequency, counting any word that leads at least eight distinct core method names. That admitted eleven plain nouns as verbs -
entityon 60 core methods,fieldon 38,formon 36,themeon 25,blockon 23, anduser,node,view,menu,module,comment- so the 64 methods here that start with one were never examined.TicketReference::entityTypeId()was among them. -
Renaming by family left the same name meaning two things.
cells()moved in five form classes and stayed inTariffSummaries;entityTypeId()moved in four and stayed inTicketReference;candidates()moved inResourceManagersand stayed inBookerMessages. All three are finished now. -
A tree-wide replace crossed two unrelated classes.
offersByTariff()was both a route access callback and a predicate on the resource saying bookings must name a tariff. One replace renamed both, so twelve call sites read as permission checks on an entity. The predicate keeps its name and the callback ischeckAccess(). -
A hook matcher that could not read its own target. The pattern for
#[Hook('name')]could not match an attribute carrying anorder:argument, because the brackets inside it close the pattern early. Three implementations kept their old names while every other one took its hook's, and it surfaced only when unrelated work opened that file. -
A promoted-property test that failed both ways. It missed
readonlyand nullable types, so three promoted properties read as plain parameters; the guard added to compensate then protected two real parameters whose constructors are one-liners, renaming their docblocks away from their arguments. phpcs is what caught that, since it checks every@paramname against the signature. -
A container fetched by string is invisible to static analysis. CI's Kernel job caught
TransactionDeleteCascadeTestcalling the transaction access hook by its old name. Nothing local could have: the test fetches the class with a string rather than::class, socontainer->get()returnsmixedand phpstan has no type to check the method against, while the same call written with::classelsewhere was caught before the push. The local Kernel run that covered the rename filtered on hook-shaped class names, and that test is not one. The seventy removed names have since been grepped across every call site; those three were the only ones left.
Also carries Orchestra's renamed API
Orchestra merged its own naming issues while this was open and renamed part of
the API this module implements. yoyaku requires orchestra: 1.x-dev, so CI
installs the tip and every one of these would have failed there.
phpstan caught the loud half: AssignmentInterface::getCandidates() and
getViewerTokens(), DeadlineProviderInterface::getDeadlineFor() and
shouldRearm(), RecipientInterface::getAccount(),
CheckoutEvent::getRefusals() and InteractionContext::getSignalUrl().
It could not catch the quiet half. AudienceBase::getRecipients() and
InteractionBase::getSignalOutcomes() are concrete, so our recipients() and
signalOutcomes() went on compiling while overriding nothing at all. The same
silence is why notifyByDefault() has its name back: this MR's own sweep read
it as a local predicate and made it shouldNotifyByDefault(), which orphaned
the override and quietly defaulted resource managers to not being notified,
since AudienceBase::notifyByDefault() answers FALSE.
Verification
- phpcs
--standard=Drupal,DrupalPracticeover the module root: exit 0, 0 errors and 0 warnings. Six of those warnings were mine, comment lines that the longer identifiers pushed past 80 characters; they are rewrapped by hand. - phpstan level 3:
[OK] No errors. It found a real cross-module break that phpcs could not see: the spelling sweep had rewrittenPaymentEvents::CANCELLED, which is kessai's constant. - cspell over the changed files: 13 words unknown to core's dictionary and absent from
.cspell-project-words.txt, none of them on a line this MR adds. - Kernel classes covering the renamed constants, readers, hook classes and subscribers run green against MySQL: 8 classes.
- eslint with core's
.eslintrc.passing.jsonand.prettierrc.json: clean.
The full suite is left to CI, and it caught two things no linter could. Both are the same mistake in two languages: a rename that collided with a name already in scope.
- Renaming
$chipto$filterin the bookings form gave the summary loop the name of the transaction filter read from the query string above it, so the filtered listing answered 500 while the unfiltered one rendered. - Renaming the calendar's
notetovaliditygave its live region the name of thevalidity()method that works out what to say, so building the region replaced the method with adivand the first call to it threw. Every calendar scenario then sat waiting for a grid that could not arrive, which is why the failure looked like ten unrelated timeouts.
Both are fixed, and both classes of collision are now swept for across the module rather than waited on: no foreach value shadows an assignment it is later read against, and no this.x = clobbers a method x().
AI-Generated: Yes (Claude Code ran the audit and wrote these renames. I reviewed the work before posting it.)
Also carries [#3618956]
kessai merged its own naming issue ([#3618948]) while this was open, renaming part of its public API. That broke this project's blocking jobs, so !307 (closed) is folded in here rather than landing separately: PaymentEvents::CANCELLED to CANCELED, PaymentInterface::STATE_CANCELLED to STATE_CANCELED, refunds() to getRefunds(), claims() to getClaims(), forgetCard() to deleteStoredCard(), and the service reference @kessai.gateway_manager to @plugin.manager.kessai_payment_gateway. That last one is resolved from the container by name, so a stale reference fails compilation rather than a test. #3618956 can be closed against this merge request.