Problem/Motivation

How about bumping PHPStan rule level to 2 before D11 is shipped, e.g. during the beta phase?

The baseline will likely be at its lowest after all the deprecated D10 code will have been removed, before new deprecations will start getting in. Also, the PHPStan-2 preparation issues have been lingering forever, not much focus; bumping the level and adding to the baseline, even if it will bring a large amount of errors, would potentially drive the focus on the fixes.

Proposed resolution

Bump PHPStan rule level to 2.

Add jangregor/phpstan-prophecy as a dev dependency. This would allow resolving Prophecy mocks into their mocked types, and avoid approx 2k additional false positives in the baseline-

Dependency evaluation:

jangregor/phpstan-prophecy

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3425412

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mondrake created an issue. See original summary.

mondrake’s picture

We'd probably need to add Jan0707/phpstan-prophecy as a dependency to interpret the prophesized methods that are yielding a lot of L2 errors.

longwave’s picture

mondrake’s picture

#4 yes, it's the contrary... :)

Alternative approach.

mondrake changed the visibility of the branch 3425412-bump-phpstan-rule to hidden.

mondrake changed the visibility of the branch 3425412-bump-phpstan-rule to active.

mondrake’s picture

Status: Active » Needs review

Adds 7785 new level-2 errors to the baseline.

Requires --dev jangregor/phpstan-prophecy dev-master. Release 1.0.0 has a failure that was fixed by https://github.com/Jan0707/phpstan-prophecy/pull/316. We would need to wait for a point release here.

NR for the concept.

idebr’s picture

Component: batch system » base system
mondrake’s picture

Tagging.

mstrelan’s picture

I generated a baseline for each level from 1-6 so see where is the biggest jump.

level 1: 630 errors
level 2: 8415 errors
level 3: 10657 errors
level 4: 14012 errors
level 5: 16015 errors
level 6: 64641 errors

I think if we're happy with a baseline of ~8000 errors at level 2 then we should aim a little higher, perhaps level 3, or, if there is appetite, then 4 or 5.

mondrake’s picture

TBH I do not think we can be happy with a baseline of 8k. But the pre-work to reduce it is nowhere.

I am rather hoping that bumping the level 1 notch up will drive more fixing, and in any case prevent further non-L2-compliant code to be introduced.

We have introduced L0 2 years ago with 1k errors more or less, and bumped to L1 in Nov 22 - still having 630 errors now is not a great score... bumping to 8k is a very large step, and more than enough for some time to come.

Somewhere I read L2 should be the decent minimum for any codebase, let's walk there before we run further.

mondrake’s picture

Let me also say there's another reason not to skip levels: each level up will require contributors some time to adapt their coding style - use of assertions or inline doctypes to clarify type of variables, later on description of iterative types etc... it takes time to digest.

I remember contributing to Doctrine DBAL (that is on L9), without prior experience of PHPStan reports... at first I simply could not understand what I was doing wrong... then I joined the pieces and started seeing the benefits, but you really need to have a codebase that is already at that level to be able to contribute meaningfully. In the current shape of Drupal codebase, jumping ahead would mean that new contribution will have to do all sort of workarounds to satisfy PHPStan, whereas it should be PHPStan helping us improve the code.

longwave’s picture

Was hoping for some low hanging fruit we can knock off to quickly reduce the number but it's not looking that promising:

$ grep message core/.phpstan-baseline.php|sort|uniq -c|sort -rn|head
    203 	'message' => '#^Access to an undefined property Drupal\\\\Core\\\\Session\\\\AccountInterface\\:\\:\\$passRaw\\.$#',
    201 	'message' => '#^Access to an undefined property Drupal\\\\Core\\\\Session\\\\AccountInterface\\:\\:\\$name\\.$#',
     85 	'message' => '#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForElementVisible\\(\\)\\.$#',
     80 	'message' => '#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForElement\\(\\)\\.$#',
     79 	'message' => '#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:assertWaitOnAjaxRequest\\(\\)\\.$#',
     68 	'message' => '#^Call to an undefined method Drupal\\\\Core\\\\TypedData\\\\TraversableTypedDataInterface\\:\\:get\\(\\)\\.$#',
     48 	'message' => '#^Call to an undefined method Drupal\\\\Core\\\\Entity\\\\EntityInterface\\:\\:get\\(\\)\\.$#',
     41 	'message' => '#^Call to an undefined method Drupal\\\\workflows\\\\WorkflowTypeInterface\\:\\:addEntityTypeAndBundle\\(\\)\\.$#',
     32 	'message' => '#^Call to an undefined method Drupal\\\\Core\\\\Entity\\\\EntityInterface\\:\\:getTranslation\\(\\)\\.$#',
     30 	'message' => '#^Cannot access property \\$uri on object\\|false\\.$#',

Even solving all those will only take 867 off the 8,000+ errors. The WebAssert ones look like they might just need a return type declaring correctly but e.g. the raw password one is test specific and might need us to rethink how we handle passwords during functional tests.

smustgrave’s picture

Status: Needs review » Needs work

MR appears to be unmergable, surprised the bot never picked it up.

I'm not super into the phpstan stuff like others here in this ticket but in regards to bumping levels. When a new rule or anything is added usually involves having to go back and update several MRs. Can be argued that's just the name of the game with Drupal but can understand community frustration when they have an MR all ready and it keeps getting sent back because of changing rules. So think doing small increments is good.

mondrake’s picture

Status: Needs work » Needs review

Please let’s focus on opinions if this should go or not, rather than starting NR-NW tennis. The MR will become unmergeable every day, but it’s not rerolling it that the question changes.

Also please keep comments to the scope of this issue: bump to level 2. There’s another issue that preaches level 9, lets have the appropriate level discussion there, in one place.

Thanks

mstrelan’s picture

Just pointing out that phpstan-prophecy 1.0.1 has been released and the MR has been updated to that version, so the concerns in #8 about the dev release are no longer applicable. Let's get this in.

mondrake’s picture

Issue tags: +no-needs-review-bot
mondrake’s picture

@mstrelan thanks a lot for clarifying in #17 that phpstan-prophecy is no longer blocking

spokje’s picture

Looks like when we bump to PHPStan L2 we get an initial addition of ~7800 suppressions.
Seeing the amount of active issues to tackle the L1 suppressions, I think it's safe to assume these won't go away quickly.

So the benefit would be preventing new L2 issues creeping in new/refactored code.

It's probably my lack of Google skills, but what are the extra checks in L2 compared to L1, what will be prevented from creeping in by us doing PHPStan L++?

mstrelan’s picture

Level 2 gives you:

unknown methods checked on all expressions (not just $this), validating PHPDocs

See https://phpstan.org/user-guide/rule-levels

Or more specifically see https://github.com/phpstan/phpstan-src/blob/1.11.x/conf/config.level2.neon

spokje’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @mstrelan, should have found that myself, but somehow didn't. (Makes mental note to check caffeine-percentage of new bought coffee-brand).

Seeing those checks and also the fact that if we want to do it with the least amount of disruptions to other MRs, it's probably now or when D12 arrives, I'm pulling the trigger and put the ball in core commiters court: RTBC for me.

catch’s picture

I'm +1 here for the obvious reason that it will stop us introducing new errors, so even if it takes years to reduce the current baseline, at least it will be visibly getting smaller instead of silently getting larger.

On going to L3 or L5 etc., staggering the MR breakage for new rules seems like a good reason to do one at a time. We could still try to go up a level every 6 months or 3 months if we want to (I'm not saying we should definitely do that, but it's worth thinking about). It does look like level 5 would be a couple of small jumps.

mstrelan’s picture

I don't want to change the scope, will almost instantly open a follow up if this goes in, but level 3 gives us:

return types, types assigned to properties

On all new code, which most code reviews are picking up anyway, but would be much nicer if it was for free.

spokje’s picture

One argument against staggering would be that raising the PHPStan level would be disruptive to (almost) all open MRs for each bump, so bumping more then one level would make it less disruptive.

That might be the case when the baseline was in the NEON format, and IIRC was one (of many) reasons the Bump-to-L9 issue is stalling.
But as part of that issue, we switched the baseline over to PHP with the reasoning that Git would be much smarter about merging it.

Is bumping a PHPStan level (with each one bringing in loads of changes on the baseline file) still as disruptive to other MRs as we (or at least I) think, or is this a non-issue by now?

If we're in non-issue land on that, I'm all about staggering, if not, we might want to prevent all/a lot of open MRs grinding to a rebase-halt each time we stagger-bump and do it in one go?

catch’s picture

One argument against staggering would be that raising the PHPStan level would be disruptive to (almost) all open MRs for each bump, so bumping more then one level would make it less disruptive.

This is true to an extent, but it will only be disruptive in the following cases:
1. The MR is changing the baseline already, and has a conflict.
2. The MR is introducing new PHPStan failures which are caught by the new level.

Hopefully this won't be almost all open MRs but just a small subset.

There have been 698 commits to the 11.x branch in the past 90 days, so even if we went up a level every three months, anything committed in the interim between bumps by definition won't get affected. That still leaves the thousands of open MRs, but not all of these are rebased that often too.

mondrake’s picture

Just noticed that phpstan-prophecy specifies this in their composer.json

            "conflict": {
                "phpspec/prophecy": "<1.7.0 || >=2.0.0",
                "phpunit/phpunit": "<6.0.0 || >=10.0.0"
            },

So this ATM would block the PHPUnit bump to 10.

We probably want to wait for a release upstream that is compatible?

mondrake’s picture

Status: Reviewed & tested by the community » Postponed

Actually https://github.com/Jan0707/phpstan-prophecy/pull/334 allowing PHPUnit 10 & 11 was already merged (yesterday!), so it's a matter of waiting for a new point release.

mstrelan’s picture

Does this need a dependency evaluation also?

mondrake’s picture

Issue summary: View changes

Added a dependency evaluation

mondrake’s picture

phpstan-prophecy just released 1.0.2 that is PHPUnit 10 compatible, bumping

catch’s picture

Status: Postponed » Needs review

Back to NR?

mondrake’s picture

I refrained from self-RTBC, but it should be a quick check to do it.

It will certainly require frequent rebasing.

mstrelan’s picture

Does this still need framework manager review? Otherwise RTBC from me.

spokje’s picture

Status: Needs review » Reviewed & tested by the community

Let's bump it to RTBC and see if the core committers think we still need FM approval and if so, if they can ping them :)

mstrelan’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

We probably need a change record :D

spokje’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record

Added two CRs.

mstrelan’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for creating the CRs. This looks good. Baseline may need to be regenerated when committing.

longwave’s picture

@alexpott was concerned about the size of the baseline, a handful of quick fixes in MR!7547 cut it down by about 25%.

spokje’s picture

a handful of quick fixes in MR!7547 cut it down by about 25%.

Great, but not sure this is part of this issue? Or even what an acceptable amount of added errors to a baseline would be?

Personally, (n=1, yadayada) this seems like follow-up issue material to me and might be derailing the original intent?
But what do I know? _shrug_

longwave’s picture

Oh yeah, that's why I made a separate MR, only as a demo. But maybe we move that to a separate issue and try and pick off these and any other widespread ones caused by traits or base classes, before doing this, if there are concerns about baseline size.

catch’s picture

Title: Bump PHPStan rule level to 2 » [PP-1] Bump PHPStan rule level to 2
Status: Reviewed & tested by the community » Postponed

Let's do what #42 suggests, it's not just the actual baseline size, but that'll also help to avoid some MR conflicts potentially later on.

mondrake’s picture

Added inline comments, even though it's for the separate issue now I think.

andypost’s picture

mondrake’s picture

Rebased.

New stats:

----------------------------------------
PHPStan baseline statistics
----------------------------------------
 16312 * Total baselined errors
----------------------------------------
Breakdown by error identifier:
  8885 missingType.return
  3411 method.notFound
  2163 property.notFound
   315 method.nonObject
   248 property.nonObject
   241 return.missing
   168 variable.undefined
   152 class.notFound
   108 arguments.count
    89 missingType.generics
    56 isset.variable
    55 parameter.phpDocType
    47 varTag.differentVariable
    45 varTag.nativeType
    40 empty.variable
    39 return.phpDocType
    39 staticClassAccess.privateMethod
    31 constructor.unusedParameter
    27 varTag.variableNotFound
    18 property.phpDocType
    15 pluginManagerSetsCacheBackend.missingCacheBackend
    11 binaryOp.invalid
     9 class.extendsDeprecatedClass
     8 class.nameCase
     8 assignOp.invalid
     8 interface.nameCase
     8 method.void
     6 parameter.defaultValue
     6 phpDoc.parseError
     5 staticMethod.nonObject
     5 varTag.misplaced
     4 parameter.notFound
     4 cast.string
     4 encapsedStringPart.nonString
     3 varTag.noVariable
     3 staticClassAccess.privateProperty
     3 method.deprecated
     2 throws.notThrowable
     2 impureMethod.pure
     2 property.protected
     2 parameter.deprecatedClass
     2 staticClassAccess.privateConstant
     2 includeOnce.fileNotFound
     1 smallerOrEqual.invalid
     1 generics.existingClass
     1 staticServiceDeprecatedService.deprecated
     1 greater.invalid
     1 nullCoalesce.variable
     1 varTag.trait
     1 method.protected
     1 function.deprecated
     1 equal.invalid
     1 unset.offset
     1 callable.void
     1 staticMethod.void
     1 phpunit.coversMethod

HEAD 11.x stats:

----------------------------------------
PHPStan baseline statistics
----------------------------------------
  9442 * Total baselined errors
----------------------------------------
Breakdown by error identifier:
  8885 missingType.return
   168 variable.undefined
   121 return.missing
    89 missingType.generics
    56 isset.variable
    40 empty.variable
    31 constructor.unusedParameter
    15 pluginManagerSetsCacheBackend.missingCacheBackend
     9 class.extendsDeprecatedClass
     8 method.notFound
     4 property.notFound
     3 arguments.count
     3 method.deprecated
     2 parameter.deprecatedClass
     2 includeOnce.fileNotFound
     1 staticServiceDeprecatedService.deprecated
     1 nullCoalesce.variable
     1 function.deprecated
     1 unset.offset
     1 staticMethod.void
     1 phpunit.coversMethod

Changed

  • phpstan-baseline: 16312 (9442)
  • phpstan-baseline.method.notFound: 3411 (8)
  • phpstan-baseline.property.notFound: 2163 (4)
  • phpstan-baseline.return.missing: 241 (121)
  • phpstan-baseline.arguments.count: 108 (3)
mondrake’s picture

Version: 11.x-dev » main
ghost of drupal past’s picture

andypost’s picture

Faced that NoDiscard is skipped because checkThisOnly: true in #3560672-12: [policy, no patch] Decide if and where to adopt the #[NoDiscard] attribute

longwave’s picture

Title: [PP-1] Bump PHPStan rule level to 2 » Bump PHPStan rule level to 2
Status: Postponed » Needs review

When we added return types to the baseline we had 12,810 entries:

$ git show 4548e0708681707ce1eb99226dfedb5819301ae8:core/.phpstan-baseline.php|grep '^]'|wc -l
12810

We are currently at 6,632 entries in main. We've completed #42 at level 1 by fixing most test traits and trivial cases; we're left with largely concrete return types now that we can't necessarily add because of downstream users. #3486376: Extend Symfony DebugClassLoader to report missing cross-module @return types will notify those users in most cases, but there isn't a huge amount we can do until then.

The latest job shows the baseline would be bumped to 13,881 entries if we go to level 2 now. Perhaps it's time for Drupal 12?

longwave’s picture

Level 3 takes us up to 15,343 entries, which isn't significantly more. Maybe we can make that jump?

longwave’s picture

phpstan/phpstan-symfony is at 15,311 which is only a reduction of 32 entries. It looks like if we can feed it an XML container definition it might be more useful, but I don't think it's worth adding in this issue.

mondrake’s picture

Title: Bump PHPStan rule level to 2 » Bump PHPStan rule level to 3

I'm all for this, +1

mstrelan’s picture

Issue tags: -PHPStan-2 +PHPStan-3

CR needs updating to mention level 3

smustgrave’s picture

Appears to need a rebase but there are some trait tickets and other random ticket that add/remove chunks for the baseline. Should those land and then bump?

mondrake’s picture

#57 this will likely require a rebase anytime another commit happens, so it’s rather pointless getting into the guinea pig wheel 😀

We probably need to agree it’s worth doing, when positive set it to be committed on a specific date (in the beta window?), then set it to a status it does not get forgotten (RTBC?).

BTW when this is committed, a large part of the MRs in the queue will need rebase too

catch’s picture

During the beta window sounds like a good idea for commit timing. As well as lots of rebases this is also going to mean most backports to 11.x won't cherry pick, although that's increasingly the case already.

About 15k entries seems like it should hopefully be manageable to deal with.

PHPStan has got a lot faster at generating a baseline so it's not too bad doing that as an extra step before commit occasionally.

longwave’s picture

Title: Bump PHPStan rule level to 3 » [May 18, 2026] Bump PHPStan rule level to 3
Status: Needs review » Postponed
Issue tags: +beta target
andypost’s picture

mondrake’s picture

Title: [May 18, 2026] Bump PHPStan rule level to 3 » [Sep 14, 2026] Bump PHPStan rule level to 3

mondrake changed the visibility of the branch 3425412-phpstan-2-quick-fixes to hidden.