Problem/Motivation

One thing that I find inconvenient and a waste of resources is that we need to run PHPStan 2 o 4 times for every run, even if cached:

once to get output in gitlab format

    - php vendor/bin/phpstan -vvv analyze --configuration=$_PHPSTAN_NEON --error-format=gitlab > $_ARTIFACTS_DIR/phpstan-quality-report.json || EXIT_CODE=$?

once to get output in junit format

    - php vendor/bin/phpstan -vvv analyze --configuration=$_PHPSTAN_NEON --no-progress --error-format=junit > $_ARTIFACTS_DIR/phpstan-junit.xml || true

and if there are errors,

once again to report to humans
php vendor/bin/phpstan analyze --configuration=$_PHPSTAN_NEON --no-progress || true
and once more get a new baseline generated for storing in artifacts
php vendor/bin/phpstan analyze --configuration=$_PHPSTAN_NEON --no-progress --generate-baseline=$_PHPSTAN_BASELINE || true

Proposed resolution

Introduce a custom PHPStan ErrorFormatter to chain output of Junit, Gitlab and Table formatters in one go.

Reference:

Baseline generation to remain a re-run of its own for the failure case.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3568641

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

Status: Needs review » Active
mstrelan’s picture

Title: [CI] Introduce an own PHPStan ErrorFormatter to prevent multiple PHPStan executions » [CI] Introduce our own PHPStan ErrorFormatter to avoid multiple PHPStan executions

Tweaking the title slightly

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Issue summary: View changes

mondrake’s picture

Status: Active » Needs review

Turns out it's not that complicated - just adding a custom formatter that chains the 3 outputs needed in a single file, and a separate script to split them back in three files during the CI job.

mondrake’s picture

Issue tags: +Test suite performance
godotislate’s picture

Nit about whitespace.

Also, maybe there should be a separate MR/build with a PHPStan error to show that works as expected as well.

mondrake’s picture

Thanks. MR!14460 should be reporting a PHPStan failure.

godotislate’s picture

Status: Needs review » Reviewed & tested by the community

lgtm

dww’s picture

Status: Reviewed & tested by the community » Needs work

This is great! Really happy to see it.

However, I don't think this is ready. Haven't debugged to figure out why, or closely reviewed the changes, but something isn't quite right here.

The failed PHPStan job itself has a job log showing only 2 failures (the same violation from 2 different contexts):

https://git.drupalcode.org/issue/drupal-3568641/-/jobs/8176394

Code quality tab on that MR is showing 3 new problems:

https://git.drupalcode.org/issue/drupal-3568641/-/pipelines/721677/codeq...

So the undefined $a is making it to the codequality_report, but not the actual phpstan job output.

Haven't looked at the generated updated baseline artifact, yet.

mondrake’s picture

Because the forced failure is in a trait, and the file path in the report file does not match (it includes “in the context of etc”). Try adding a forced failure somewhere else in a concrete class.

dww’s picture

Status: Needs work » Needs review

Okay, fair enough. Too bad it's not consistent, but I'll grant that failures in Traits is a more weird case.

Tried again with what should be a "simple" failure. Let's see what the pipeline says.

dww’s picture

Status: Needs review » Needs work
StatusFileSize
new130.87 KB
new64.37 KB
new72.79 KB

Hrm, more consistent, but still not there, yet. I'm nervous that some errors are reported in some of the places, but not all.

Now we're seeing the warning about $mondrake on both code quality and the failed phpstan job log, but we only see the warning about $dww in the quality report and not in the actual failed job (nor test failure summary):

https://git.drupalcode.org/issue/drupal-3568641/-/jobs/8178245
vs.
https://git.drupalcode.org/project/drupal/-/merge_requests/14460

dww’s picture

Status: Needs work » Needs review

Sorry, I'm an idiot. The $dww failure isn't a phpstan failure at all, but a phpcs report. No wonder!

dww’s picture

Looked at the artifacts for the 1-failure case. All seems reasonable.

Pushed another commit with a 2nd failure, to ensure everything works well with multiple problems.

mondrake’s picture

Come on, no worries!

mondrake’s picture

14438 rebased to take in latest PHPStan version just committed

godotislate’s picture

It might make sense to push another MR that's just HEAD plus the same PHPStan failure and compare the test results/output.

dww’s picture

Status: Needs review » Needs work

Okay, not good. With multiple failures, the fail MR isn't generating any phpstan output files. No time to actually debug it.

https://git.drupalcode.org/issue/drupal-3568641/-/jobs/8178913

mondrake’s picture

I think it was just the composer build failing. Did not even get to phpstan execution.

mondrake’s picture

Rebased the failing MR too to keep in sync.

godotislate’s picture

Opened 14461 that just has the same multiple failures as 14460, but otherwise is the same as main. The test results and output between 14460 and 14461 look the same to me.

mondrake’s picture

Let's do some heavy lifting. Remove baseline from both failing MRs.

mondrake’s picture

Status: Needs work » Needs review

Also after removing the entire baseline the results seem the same to me, and it seems the script is not quivering. Also, files in artifacts seem correct.

godotislate’s picture

Yeah, code quality reports on both MRs look to be (truncated) the same.

I downloaded the raw logs from both failed PHPStan jobs, and they look effectively the same.

I am +1 for RTBC, but let's have @dww confirm if he can.

dww’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Thanks for the 3rd MR. I didn't do a 100% exhaustive review of every job / pipeline, but from what I checked, everything seems to be working great.

Totally unscientific, but it looks like the new plumbing isn't actually speeding up the phpstan jobs in any noticeable way. Seems the time cost of having to re-run composer for each job (why did we move to that?) and the variation in times that entails hides whatever performance improvements this change is making. But on the face of it, running phpstan only once and then a script to parse a single file into 3 files has got to be less work overall than running phpstan 3 times.

Made the MR we care about a little more self-documenting. Updated the summary a bit. The other 2 MRs are draft, so that'll be obvious.

Don't see any other improvements to make.

Thanks again!
-Derek

dww’s picture

1st pass at saving credit. Sorry @mstrelan. 😂 But thanks for the title tweak. Maybe the committer will feel differently. 😉

longwave made their first commit to this issue’s fork.

longwave’s picture

Status: Reviewed & tested by the community » Needs review

Went a step further and made the formatter totally configurable. Perhaps this is worth uploading to packagist as a standalone PHPStan extension package...

mondrake’s picture

Very nice! Kudos

longwave’s picture

I swapped the keys and values by injecting the container and retrieving the services instead of injecting them directly.

Re env vars it's possible to configure like this:

      outputs:
        gitlab: %env._ARTIFACTS_DIR%/phpstan-quality-report.json
        junit: %env._ARTIFACTS_DIR%/phpstan-junit.xml
        table: php://stdout

but I can't find a way of setting a fallback, so if the var is not set (e.g. when you are running locally) you get Missing parameter 'env._ARTIFACTS_DIR' - this happens even if you aren't using the multiplex formatter.

mondrake’s picture

#36 great - maybe we can do something similar for the env variables and do our own replacement instead of relying on the container? Sorry but I keep thinking hardcoding the artifacts path in the neon file feels wrong.

i.e.

      outputs:
        gitlab: {{{_ARTIFACTS_DIR}}}phpstan-quality-report.json
        junit: {{{_ARTIFACTS_DIR}}}phpstan-junit.xml
        table: php://stdout

would fall back on the PHP side in case of no env variable with that name to just the file name that in that case will be written in the cwdir

longwave’s picture

Figured out a way of optionally injecting env vars.

longwave’s picture

I don't think I have time to do this now but as a followup maybe we could make the metrics generator also be a custom formatter, and then multiplex that as well (if the formatter has access to the baseline, I guess)

mondrake’s picture

#39 not sure, that metrics script takes the generated baseline in input, not the PHPStan analysis output.

mondrake changed the visibility of the branch 3568641-ci-introduce-our to hidden.

mondrake’s picture

Big bold +1 for RTBC, but I can't, really.

godotislate’s picture

Status: Needs review » Reviewed & tested by the community

I'm now wondering whether the file handling should be done with SplFileObject instead of a resource, but I don't think it's worth blocking on. LGTM.

cmlara made their first commit to this issue’s fork.

cmlara changed the visibility of the branch 3568641-test-current-wall-time-usage to hidden.

mondrake’s picture

Issue summary: View changes
mondrake’s picture

One nitpicky idea inline, leaving at RTBC.

mondrake’s picture

Out of curiosity, I tweaked a bit the MR in #3425412: [Sep 14, 2026] Bump PHPStan rule level to 3 to report the timing of each of the 4 steps in the IS here. That one is an extreme case as the bump of rule level from 1 to 2 would currently flag 7k more errors; however it shows how steps 2 and 3 (that will be minimized by this MR) do have an impact on overall time even if cached.

These are the results: https://git.drupalcode.org/issue/drupal-3425412/-/jobs/8245072

  1. initial output in gitlab format: Elapsed time: 3 minutes 14 seconds
  2. second output in junit format: Elapsed time: 8.88 seconds
  3. third output to report to humans: Elapsed time: 12.26 seconds
  4. fourth output to get a new baseline generated: Elapsed time: 21.92 seconds
mondrake’s picture

Small change to the .gitlab-ci.yml documentation re. PHPStan baseline re-generation. Leaving at RTBC since it's doc only.

  • catch committed 3122082f on 11.x
    task: #3568641 [CI] Introduce our own PHPStan ErrorFormatter to avoid...
catch’s picture

Version: main » 11.x-dev
Status: Reviewed & tested by the community » Fixed

Have to admit I didn't really understand the remaining nitpick on the MR - @mondrake would you be up for opening a follow-up for that?

This is a very small change that will knock 10-30s wall time off each MR run for core, so going ahead here.

Committed/pushed to 11.x and main, thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mondrake’s picture

mondrake’s picture

Status: Fixed » Reviewed & tested by the community

Was committed to 11.x but not to main

  • catch committed 5ec4e8da on main
    task: #3568641 [CI] Introduce our own PHPStan ErrorFormatter to avoid...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Sorry, push didn't make it, but done again now.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.