You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[P3] No static analysis in CI (PHPStan L9 finds 17 errors incl. the #21 coercion bug) and an open-ended >=8.1 PHP constraint #24
Confirmed by execution, 2026-09-13. Two packaging gaps that let the coercion bug class in #15/#18/#21 reach a release.
1. No static analysis anywhere in the toolchain
composer.json:64-66 defines one script, "test": "phpunit". No phpstan, no psalm, no php-cs-fixer, no phpcs, and .github/workflows/test.yml runs only composer validate --strict and vendor/bin/phpunit.
Running PHPStan level 9 against src/ on the #18 branch reports 17 errors, six of which are precisely the defect filed as #21:
$ phpstan analyse --level=9 src
src/Price.php:96 Cannot cast mixed to string. (cast.string)
src/Price.php:99 Cannot cast mixed to string.
src/Price.php:100 Cannot cast mixed to string.
src/Price.php:101 Cannot cast mixed to string.
src/Price.php:102 Cannot cast mixed to string.
src/Price.php:103 Cannot cast mixed to string.
src/Client.php:387 Cannot cast mixed to string.
src/Client.php:388 Cannot cast mixed to string.
src/Client.php:394 Cannot cast mixed to string.
src/Client.php:296 Strict comparison using !== between string and null will always evaluate to true.
src/Client.php:313 Call to function assert() with true will always evaluate to true.
src/Http/CurlTransport.php:27 Parameter #2 $options of curl_setopt_array ...
[ERROR] Found 17 errors
For a library whose defining hazard is PHP coercing untrusted JSON at a function boundary, a level-8/9 gate in CI is the cheapest possible control. declare(strict_types=1) is correctly present in all 19 PHP files, but it does not police an explicit (string)/(float) cast — which is exactly where both #15 and #21 live.
Suggested: add phpstan/phpstan to require-dev, a phpstan.neon at level 8 with a short baseline for the existing 17, a "lint"/"analyse" composer script, and a CI step. Ratchet the level up as the baseline shrinks.
2. "php": ">=8.1" is an open upper bound
composer.json:47. Composer will happily install this package on PHP 9.0 or 10.0, neither of which exists yet and neither of which is tested — the CI matrix is 8.1 through 8.5 (.github/workflows/test.yml:19). The honest constraint for a library is ^8.1, which covers 8.x and requires a deliberate release to claim 9.x.
Related: ext-curl: "*" carries no version floor either, which matters for #22 — libcurl < 7.58.0 forwards the Authorization header across a cross-origin redirect.
Found during the post-merge PHP expert review of #17/#18/#19.
Verified the count on origin/main @ 21339d43e (PHP 8.5.8, phpstan 2.x): phpstan analyse src --level 9 reports [ERROR] Found 17 errors. Full inventory:
src/Client.php:225 Method Client::dataOrFail() should return array<string, mixed> but returns array<mixed, mixed>. [return.type]
src/Client.php:265 Parameter #1 $data of Price::fromArray() expects array<string, mixed>, array<mixed, mixed> given. [argument.type]
src/Client.php:314 Strict comparison using !== between string and null will always evaluate to true. [notIdentical.alwaysTrue]
src/Client.php:340 Call to function assert() with true will always evaluate to true. [function.alreadyNarrowedType]
src/Client.php:340 Instanceof between HttpResponse and HttpResponse will always evaluate to true. [instanceof.alwaysTrue]
src/Client.php:414 Cannot cast mixed to string. [cast.string]
src/Client.php:415 Cannot cast mixed to string. [cast.string]
src/Client.php:421 Cannot cast mixed to string. [cast.string]
src/Client.php:579 Cannot access offset 'message' on mixed. [offsetAccess.nonOffsetAccessible]
src/Client.php:589 Cannot access offset 'message' on mixed. [offsetAccess.nonOffsetAccessible]
src/Http/CurlTransport.php:27 Parameter #2 $options of curl_setopt_array expects ... given. [argument.type]
src/Price.php:96 Cannot cast mixed to string. [cast.string]
src/Price.php:99 Cannot cast mixed to string. [cast.string]
src/Price.php:100 Cannot cast mixed to string. [cast.string]
src/Price.php:101 Cannot cast mixed to string. [cast.string]
src/Price.php:102 Cannot cast mixed to string. [cast.string]
src/Price.php:103 Cannot cast mixed to string. [cast.string]
Wiring should wait, and here is the ordering. The six src/Price.php errors are exactly the blind (string) casts being removed in #27, and src/Http/CurlTransport.php:27 sits in the file rewritten in #26. Adding PHPStan before those land means either a baseline whose line references conflict with two open PRs, or absorbing Client.php's ten mixed errors into an unrelated change.
After #26 and #27 merge, seven of the seventeen are gone by construction and only Client.php's ten remain — small enough to fix properly and turn level 9 on with no baseline, which is the only version of this worth having.
Considered and rejected in #28 (the 3.0.0 versioning PR) for exactly this reason; that PR says so in its description rather than bundling the work.
The PHP constraint half of this issue (>=8.1 open-ended) is untouched and still open. Separately, #26 adds "lib-curl": ">=7.58.0" to composer.json — that is the libcurl floor, not the PHP one; ext-curl's own version tracks PHP's and cannot express it.
Verified against a local analysis run on 2026-09-13, not against CI (there is no static-analysis job yet — that is this issue).
Activity
karlwaldman commentedon Sep 13, 2026
Verified the count on
origin/main@21339d43e(PHP 8.5.8, phpstan 2.x):phpstan analyse src --level 9reports[ERROR] Found 17 errors. Full inventory:Wiring should wait, and here is the ordering. The six
src/Price.phperrors are exactly the blind(string)casts being removed in #27, andsrc/Http/CurlTransport.php:27sits in the file rewritten in #26. Adding PHPStan before those land means either a baseline whose line references conflict with two open PRs, or absorbingClient.php's tenmixederrors into an unrelated change.After #26 and #27 merge, seven of the seventeen are gone by construction and only
Client.php's ten remain — small enough to fix properly and turn level 9 on with no baseline, which is the only version of this worth having.Considered and rejected in #28 (the 3.0.0 versioning PR) for exactly this reason; that PR says so in its description rather than bundling the work.
The PHP constraint half of this issue (
>=8.1open-ended) is untouched and still open. Separately, #26 adds"lib-curl": ">=7.58.0"tocomposer.json— that is the libcurl floor, not the PHP one;ext-curl's own version tracks PHP's and cannot express it.Verified against a local analysis run on 2026-09-13, not against CI (there is no static-analysis job yet — that is this issue).
chore: release as 3.0.0 and write down the BC break #18 shipped (#28)
chore: run PHPStan level 9 in CI and clear the 14 real errors (#24) (#31
chore(release): 3.0.1 (#32)