Skip to content

TableErrorFormatter: Limit number of errors#5136

Merged
ondrejmirtes merged 25 commits intophpstan:2.1.xfrom
staabm:limiterr
Mar 10, 2026
Merged

TableErrorFormatter: Limit number of errors#5136
ondrejmirtes merged 25 commits intophpstan:2.1.xfrom
staabm:limiterr

Conversation

@staabm
Copy link
Copy Markdown
Contributor

@staabm staabm commented Mar 5, 2026

closes phpstan/phpstan#14232

"mago benchmark" running PHPStan with warmed cache on macbook m4 pro with PHP 8.3.34
before this PR: 20-21 seconds
after this PR: 3-4 seconds


running on wordpress with warmed result cache

note the difference in "Elapsed time"

before the PR

grafik grafik

after the PR

../../bin/phpstan -v

grafik

PHPSTAN_ERRORS_LIMIT=0 ../../bin/phpstan -v

grafik

PHPSTAN_ERRORS_LIMIT=50 ../../bin/phpstan -v

grafik

@staabm staabm requested a review from VincentLanglet March 5, 2026 13:43
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A (big) file with looooot of errors will still display way more than 1000 errors since you break between files.

Also I find weird that something like PHPSTAN_ERRORS_LIMIT=1 will display more than 1 error (if the first file has more than 1 error).

Should we break inside the file foreach too ?

@staabm
Copy link
Copy Markdown
Contributor Author

staabm commented Mar 5, 2026

A (big) file with looooot of errors will still display way more than 1000 errors since you break between files.

yeah, I did it intentionally like this.
adjusted it now, as I found an easy way to fit the limit

@staabm staabm requested a review from ondrejmirtes March 5, 2026 14:19
Copy link
Copy Markdown
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of PHPSTAN_ERRORS_LIMIT variable where user can shoot themselves in the foot with a non-sense value like 3, I'd more like a boolean-like var with the name of PHPSTAN_TABLE_ERROR_FORMATTER_FORCE_SHOW_ALL_ERRORS. The default (undefined or 0) would only show the first 1000 errors, setting to 1 would show everything and hide the note.

if ($errorsBudget > 0 && $printedErrors > $errorsBudget) {
$note = [];
$note[] = sprintf('Result is limited to the first %d errors', $errorsBudget);
$note[] = '- Consider lowering the PHPStan level';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only output this on rule level > 0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this line show "1000+ errors" when applicable. Yeah, it's meant to be annoying :)

Comment thread src/Command/ErrorFormatter/TableErrorFormatter.php
@staabm
Copy link
Copy Markdown
Contributor Author

staabm commented Mar 5, 2026

latest result

grafik

$fileErrors[$fileSpecificError->getFile()][] = $fileSpecificError;
}

$forceShowAll = getenv('PHPSTAN_TABLE_ERROR_FORMATTER_FORCE_SHOW_ALL_ERRORS');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have this name here in a private constant, it's referred to twice in the source code.

if (in_array($forceShowAll, [false, '0'], true)) {
$errorsBudget = self::ERRORS_LIMIT;
} else {
$errorsBudget = 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null would be nicer?

#[AutowiredParameter]
private ?string $editorUrlTitle,
#[AutowiredParameter]
private ?int $level,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usedLevel might work a bit better, that's what's used across the codebase for this.

@staabm
Copy link
Copy Markdown
Contributor Author

staabm commented Mar 6, 2026

Memo to me: check whether the new limit also affects the baseline generation

@ondrejmirtes
Copy link
Copy Markdown
Member

check whether the new limit also affects the baseline generation

It won't, you just touched TableErrorFormatter, baseline is generated with a different one.


$printedErrors += count($rows);
if ($errorsBudget !== null && $printedErrors > $errorsBudget) {
$rows = array_slice($rows, 0, $errorsBudget - ($printedErrors - count($rows)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last thing - I'm not sure whether this line works correctly, a lot is going on there.

For example it's easy to make a mistake in $errorsBudget - ($printedErrors - count($rows)) based on whether $printedErrors already includes current count($rows) or if the += operation happened after this computation.

I'm thinking this should really have some unit tests.

My suggestion - constructor of TableErrorFormatter should have a new optional nullable parameter. If it's null, then the autodetection based on the env variable will happen. If it's not null then that will become the error limit.

The unit test will then construct TableErrorFormatter with a specific value, can be 5. And we should test the output for < 5, exactly 5 and > 5.

int $generateErrorsCount = 4,
): void
{
putenv('COLUMNS=200');
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

took me a while to realize that I need this env var in the test, to get stable results across local workstation and CI :-)

@staabm
Copy link
Copy Markdown
Contributor Author

staabm commented Mar 8, 2026

@ondrejmirtes finally the tests "work" cross plattform. had to skip windows because of slight line-break differences enforced by symfony/console. but I think that doesn't matter for the tests we do here.

@staabm
Copy link
Copy Markdown
Contributor Author

staabm commented Mar 9, 2026

remaining errors also happen on 2.1.x - so seem unrelated

@staabm staabm requested a review from VincentLanglet March 9, 2026 09:09
@mvorisek
Copy link
Copy Markdown
Contributor

Based on the issue description the performance issue should be given by symfony/console. Why not fix the issue there and possibly patch it until released?

I personally prefer all errors shown as very often I do ctrl+f and want to know how many occurences of something are present.

@ondrejmirtes
Copy link
Copy Markdown
Member

Love it, thank you!

@ondrejmirtes ondrejmirtes merged commit 38cdabb into phpstan:2.1.x Mar 10, 2026
630 of 648 checks passed
@ondrejmirtes
Copy link
Copy Markdown
Member

@mvorisek For your use-case, see the error message that's printed when there are 1000+ errors.

@staabm
Copy link
Copy Markdown
Contributor Author

staabm commented Mar 10, 2026

opened a upstream issue, so symfony knows about the performance and might improve it in the future:
symfony/symfony#63630

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants