Declare Drupal 12 support, and run the whole suite against it
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3618644. -->
Reported by: [mably](https://www.drupal.org/user/3375160)
Related to !287
>>>
<p><strong>The next-major lane has never run to completion, so nothing is known about this project on Drupal 12.</strong> The jobs are opted in and manual; played by hand they die in the composer step, before a single file is analyzed, and the phpstan and phpunit jobs behind it stay skipped. This makes it green, and reports what it found on the way.</p>
<p><strong>Dependency resolution came first, and the fault was in this project's own CI file.</strong> <code>_LENIENT_ALLOW_LIST</code> named its projects with a <code>drupal/</code> prefix, and the templates' own <code>expand_composer_json.php</code> prepends that prefix itself: every entry became <code>drupal/drupal/webform</code>, matched no package, and the lenient plugin printed the list back while relaxing nothing. Bare names fix it. Two of the entries have to be there transitively, which is not guessable from this project's own dependencies: <code>easy_email</code> pulls <code>jquery_ui_resizable</code>, which pulls <code>jquery_ui</code>, and both cap core at <code>^11</code>. With that, core <code>dev-main</code> resolves.</p>
<p><strong>Two projects left the list rather than joining it.</strong> <code>orchestra</code> and <code>kessai</code> declare <code>^11.3 || ^12</code> now and need no leniency. <code>domain</code> reaches Drupal 12 through its 4.x branch, so <code>composer.json</code> admits <code>^4.0</code> and its six test classes keep running on 12 instead of being skipped; 4.x carries no tagged release, so <code>prefer-stable</code> keeps every other lane on 3.x.</p>
<p><strong>The declaration is load-bearing, not a claim to make afterwards.</strong> Holding <code>core_version_requirement</code> at <code>^11.3</code> until the lane went green cannot work: Drupal's own module installer reads it, so every test that installs a module through <code>ModuleInstaller</code> fails with "incompatible with this version of Drupal core" no matter what composer resolved. All 42 <code>info.yml</code> files and <code>composer.json</code> now say <code>^11.3 || ^12</code>.</p>
<p><strong>One test installed a table Drupal 12 deletes.</strong> <code>installSchema('system', ['sequences'])</code> throws on core 12, which ships <code>system_update_12000()</code> to drop that table and no longer defines its schema. Nothing here ever called <code>nextId()</code>, the only API it backed, and that method is gone from core 12 as well, so the line goes rather than being worked around.</p>
<p><strong>webform is the one dependency that needs its tests standing down.</strong> Installing a project leniently only gets it past composer and the module installer; <code>WebformHandlerManager::getGroupedDefinitions()</code> is not compatible with the core 12 <code>CategorizingPluginManagerInterface</code>, so a test that boots it cannot run there. Six classes skip themselves through <code>DependencyWithoutDrupal12Trait</code>, which names the project and the reason: the two in <code>yoyaku_webform</code>, and four in <code>yoyaku_orchestra_example</code>, which reaches webform through its dependency on <code>yoyaku_webform</code>. <code>grep skipWithoutDrupal12()</code> lists them, and the entry comes out of the trait and the allow list together once webform declares Drupal 12. The pattern, the trait and the <code>SYMFONY_DEPRECATIONS_HELPER</code> handling below are all as the orchestra project settled them; the trait is copied rather than shared, because a test standing down over webform must not need orchestra installed to know how.</p>
<p><strong>Core main runs <code>--fail-on-deprecation</code> unconditionally</strong>, and the templates set <code>SYMFONY_DEPRECATIONS_HELPER</code>, which core 11.5 deprecated using at all: one deprecation would then fail every test class with no assertion failure behind it. The next-major phpunit job unsets it and appends core's own ignore patterns, which is what core's CI does.</p>
<p><strong>The lane then reported 37 deprecations</strong>, all of them core's <code>DebugClassLoader</code> asking an override to declare the return type core intends to add. Twenty-two came from one place, <code>InterfaceLanguageUrlTrait::toUrl()</code>, which every entity uses; the rest are three <code>label()</code>, five <code>buildEntityQuery()</code>, two <code>create()</code>, one <code>createInstance()</code> and one <code>applyDefaultValue()</code>. <code>validateForm()</code> cannot take one at all: core 12 declares it returning the entity, core 11's own implementation returns nothing, and a nullable type would widen what core 12 declares, which PHP refuses. Those two take the other remedy the deprecation offers, an explicit <code>@return</code>.</p>
<p><strong>The last one was ours, and it is fixed here rather than deferred.</strong> Six <code>startTransaction()</code> sites committed by letting the Transaction go out of scope, deprecated in 11.5 and removed in drupal:13.0.0. They say <code>commitOrRelease()</code> now, which exists as far back as the 11.3 floor this module supports. Four already marked that commit point with an <code>unset()</code> and a comment about it, so those are the same statement said out loud; <code>withRowLock()</code> had no single commit point, since two returns inside its <code>try</code> relied on the variable's lifetime, and each commits now, the callback's after the callback so its writes stay inside the lock. Verified on MySQL as well as SQLite, because SQLite's driver drops <code>FOR UPDATE</code> and a green run there says nothing about this code.</p>
<p><strong>What is left in <code>.deprecation-ignore.txt</code> is one line naming somebody else's class:</strong> the token module has not declared the return type core will add to <code>Token::getInfo()</code>, and every test that renders a resource form reaches it. A deprecation naming this project belongs fixed, which is why none is listed there.</p>
<p><strong>PHPUnit 12 came with the lane.</strong> It leaves no mock without an expectation, so the stubs that only stand in are built with <code class="language-php">createStub()</code>, and <code class="language-php">$this->any()</code>, deprecated there, gives way to <code class="language-php">atLeastOnce()</code>. The load scripts read <code class="language-php">$_SERVER['argv']</code> as well, since <code class="language-php">$argv</code> exists only where <code class="language-php">register_argc_argv</code> is on.</p>
<p><strong>Three of the queries core 12 appeared to add were a missing module, not a regression.</strong> The testing profile brings <code class="language-php">dynamic_page_cache</code> along on core 11 and does not on core 12, so the page under measurement was rebuilt per request and its availability recomputed. Both performance tests name it among their modules, and the same figures then hold on both cores.</p>
<p><strong>The asset counts do differ, and both figures are asserted rather than tolerated.</strong> Core 12 groups its aggregates by library set and by category rather than into one bundle, and serves <code class="language-php">once.min.js</code> on its own, so the same page arrives as four scripts and two stylesheets where core 11 sent one of each. A ceiling loose enough for both cores is the same as not asserting the counts at all, so each core is held to what it actually does and a page that grows an aggregate still fails on the core it grew on. The byte counts differ by well under the tolerance <code class="language-php">assertMetrics()</code> already allows for them, so one figure covers both. One real difference is left: the click that puts a place in the basket costs one query fewer on core 12, and that too is stated per core.</p>
<p><strong>Both lanes are green.</strong> Every job, including the two next-major phpunit jobs that run the whole suite against core <code class="language-php">dev-main</code>, where the two performance classes pass rather than skip.</p>
<p>AI-Generated: Yes (Claude Code was used to diagnose the lane, write the changes and draft this summary. I reviewed all of it before posting.)</p>
issue
GitLab AI Context
Project: project/yoyaku
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/yoyaku/-/raw/1.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/yoyaku
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD