chore: run PHPStan level 9 in CI and clear the 14 real errors (#24) - #31
Merged
Merged
Conversation
Adds phpstan/phpstan as a dev dependency, a phpstan.neon at level 9 over src/ with no baseline, a "phpstan" composer script, and a static-analysis job in the test workflow. The 17 errors quoted in #24 are stale: PR #27 removed six Price.php casts and #26 rewrote the transport. Re-measured on 0672df0, level 9 on src/ reports 14. All 14 are fixed here, so the level ships clean: - dataOrFail()/priceOrFail() rebuild decoded JSON objects with string keys, making the array<string, mixed> the SDK declares everywhere actually true (json_decode turns a numeric JSON key into an int array key). - originOf() in Client and CurlTransport reads parse_url() parts through is_string()/is_int() instead of casting mixed, so an unnameable part cannot compare equal to a real origin. - errorMessage() narrows $body['error'] and $body['data'] to arrays before reaching into them. - CurlTransport rejects an empty method or URL, which is also what curl_setopt_array's non-empty-string contract requires. - Removed two provably dead checks: the api-key null test the guard above it already proved, and the assert()/null seed on a loop that always runs. No baseline file and no @PHPStan-Ignore comments. phpstan.neon is export-ignored and excluded from the composer archive - it is dev tooling, not package content. The PHP constraint stays >=8.1: it is pinned by an existing assertion in PublicClaimsTest and narrowing it to ^8.1 is a packaging decision with its own release note, not a CI change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #24
Re-measured on current main — the issue's count was stale
#24 quotes 17 level-9 errors. Six of those were
Price.phpcasts that #27already removed and a seventh was in the transport #26 rewrote. Measured on
0672df0withphpstan/phpstan 2.2.14, level 9,paths: [src], no baseline:src/Level shipped: 9. No baseline file. No
@phpstan-ignorecomments. No casts added to silence anything.What ships
phpstan/phpstan: ^2.1inrequire-devcomposer.jsonlevel: 9,paths: [src], no baselinephpstan.neon(new)composer phpstanscript (+composer check= phpstan then phpunit)composer.jsonstatic-analysisjob on push and pull_request, pinned actions,--error-format=github.github/workflows/test.ymlphpstan.neonexport-ignored and excluded from the composer archive.gitattributes,composer.jsonThe 14 errors and how each was fixed
Client::dataOrFail()225array<mixed, mixed>, declaresarray<string, mixed>stringKeyed()rebuilds the decoded object with string keysClient::priceOrFail()265Price::fromArray()givenarray<mixed, mixed>PriceboundaryClient::request()314$this->apiKey !== nullalways trueClient::request()340assert($response instanceof HttpResponse)always true$response = nullseed;max(1, …)guarantees the loop body runsClient::originOf()414/415/421lowerPart()readsparse_url()parts throughis_string(); port throughis_int()Client::errorMessage()579/589messageon mixed$body['error']/$body['data']withis_array()firstCurlTransport::request()34CURLOPT_URLwantsnon-empty-string,CURLOPT_CUSTOMREQUESTwantsnon-empty-string|nullTransportExceptionCurlTransport::originOf()119/120/125lowerPart()treatmentstringKeyed()is the one worth a second look, because on its face it resemblesa cast that silences an error. It is the inverse of a real decode step:
json_decode(…, true)turns a numeric JSON key such as"2026"into an intarray key, which is exactly why the decoded envelope is
array<array-key, mixed>and not the
array<string, mixed>this SDK declares on every internal signature.Restoring the keys makes the declared type true rather than asserted.
lowerPart()is a behaviour improvement, not just a type one:(string)on anon-string
parse_url()part turnednull/falseinto''and0into'0',so two origins we cannot name could compare equal. An unnameable part is now
always
''and never a plausible-looking value.TDD evidence
RED 1 — PHPStan level 9 on
src/, unmodified main./vendor/bin/phpstan analyse --no-progresswith onlyphpstan.neonadded:GREEN 1 — same command, this branch
RED 2 —
tests/StaticAnalysisCiTest.phpagainst main's configA config file in the repo is not a check that runs, and a baseline is the
documented way to make a failing check green without fixing anything — so the
wiring itself gets a test. Captured by restoring
composer.json,.github/workflows/test.ymland.gitattributesfromorigin/mainanddeleting
phpstan.neon:with, among them:
GREEN 2
Full suite
origin/mainOK (229 tests, 747 assertions)OK (234 tests, 764 assertions)Left alone, deliberately
The
>=8.1PHP constraint stays. The argument for^8.1is real —>=8.1lets Composer install this on a PHP 9 nobody has tested it against. Two reasons
it does not belong in this PR:
PublicClaimsTest::testCanonicalDeveloperContractIsDiscoverableassertsassertSame('>=8.1', $composer['require']['php']). Changing it meanschanging that assertion, which is a decision the repo deliberately recorded.
the package uninstallable on PHP 9 the day it lands, which wants its own
release note and its own PR, not a rider on a CI change.
Recommend a follow-up issue: narrow to
^8.1, update the pinning assertion, addthe next PHP major to the CI matrix when it enters RC, and note it in the
CHANGELOG under the release that ships it.
No CHANGELOG entry and no version bump.
VersioningTestpins the topCHANGELOG heading to
Client::VERSION, so an## Unreleasedsection fails thesuite; this belongs in the next release commit.
PHPStan runs over
src/only, per the issue. Extending it totests/andexamples/is worth doing but is a separate, noisier change.🤖 Generated with Claude Code
https://claude.ai/code/session_015ao5paex73xXvuM424Libo