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:
- Upstream issue: How to add multiple --error-format
- Docs: Error Formatters
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
| Comment | File | Size | Author |
|---|
Issue fork drupal-3568641
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
Comment #2
mondrakeComment #3
mstrelan commentedTweaking the title slightly
Comment #4
mondrakeComment #5
mondrakeComment #7
mondrakeTurns 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.
Comment #8
mondrakeComment #9
godotislateNit about whitespace.
Also, maybe there should be a separate MR/build with a PHPStan error to show that works as expected as well.
Comment #11
mondrakeThanks. MR!14460 should be reporting a PHPStan failure.
Comment #12
godotislatelgtm
Comment #13
dwwThis 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
$ais making it to the codequality_report, but not the actual phpstan job output.Haven't looked at the generated updated baseline artifact, yet.
Comment #14
mondrakeBecause 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.
Comment #15
dwwOkay, 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.
Comment #16
dwwHrm, 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
$mondrakeon both code quality and the failed phpstan job log, but we only see the warning about$dwwin 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
Comment #17
dwwSorry, I'm an idiot. The
$dwwfailure isn't a phpstan failure at all, but a phpcs report. No wonder!Comment #18
dwwLooked 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.
Comment #19
mondrakeCome on, no worries!
Comment #20
mondrake14438 rebased to take in latest PHPStan version just committed
Comment #21
godotislateIt might make sense to push another MR that's just HEAD plus the same PHPStan failure and compare the test results/output.
Comment #22
dwwOkay, 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
Comment #23
mondrakeI think it was just the composer build failing. Did not even get to phpstan execution.
Comment #24
mondrakeRebased the failing MR too to keep in sync.
Comment #26
godotislateOpened 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.Comment #27
mondrakeLet's do some heavy lifting. Remove baseline from both failing MRs.
Comment #28
mondrakeAlso 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.
Comment #29
godotislateYeah, 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.
Comment #30
dwwThanks 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
Comment #31
dww1st pass at saving credit. Sorry @mstrelan. 😂 But thanks for the title tweak. Maybe the committer will feel differently. 😉
Comment #34
longwaveWent a step further and made the formatter totally configurable. Perhaps this is worth uploading to packagist as a standalone PHPStan extension package...
Comment #35
mondrakeVery nice! Kudos
Comment #36
longwaveI 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:
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.Comment #37
mondrake#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.
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
Comment #38
longwaveFigured out a way of optionally injecting env vars.
Comment #39
longwaveI 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)
Comment #40
mondrake#39 not sure, that metrics script takes the generated baseline in input, not the PHPStan analysis output.
Comment #42
mondrakeBig bold +1 for RTBC, but I can't, really.
Comment #43
godotislateI'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.
Comment #47
mondrakeComment #48
mondrakeOne nitpicky idea inline, leaving at RTBC.
Comment #49
mondrakeOut 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
Elapsed time: 3 minutes 14 secondsElapsed time: 8.88 secondsElapsed time: 12.26 secondsElapsed time: 21.92 secondsComment #50
mondrakeSmall change to the .gitlab-ci.yml documentation re. PHPStan baseline re-generation. Leaving at RTBC since it's doc only.
Comment #52
catchHave 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!
Comment #55
mondrakeSure, filed #3575080: [CI] Enhance PHPStan ErrorFormatter
Comment #56
mondrakeWas committed to 11.x but not to main
Comment #58
catchSorry, push didn't make it, but done again now.