Skip to content

PR C: Narrow remaining claim accesses and test types for PHPStan max#45

Merged
turegjorup merged 2 commits into
developfrom
feature/phpstan-max-completion
May 12, 2026
Merged

PR C: Narrow remaining claim accesses and test types for PHPStan max#45
turegjorup merged 2 commits into
developfrom
feature/phpstan-max-completion

Conversation

@turegjorup
Copy link
Copy Markdown
Collaborator

Summary

Third and final follow-up in the A → B → C sequence tightening the JSON-payload boundary on the path to level: max PHPStan.

After PR A (#43, constructor option shapes), PR #42 (string-cast guards + MetadataException), and PR B (#44, JSON-payload narrowing + JwksException + exception documentation), 15 max-level errors remain (out of the original 26 — 7 of those are in scope here, 4 are PR A's, and 4 disappear once both branches are on develop). This PR clears the 15 in scope.

Stacked on PR #44 — based on feature/json-payload-narrowing. When PR #42 / PR B merge, this PR rebases onto develop cleanly. The 4 max-level constructor errors ($cacheItemPool, $cacheDuration, $factory, $url at lines 78/81/95/98) are fixed by PR A (#43) — when both A and C merge, develop reaches zero max-level errors.

What changes

src/

  • getJwtVerificationKeys() declares @return array<string, Key> (was array). Matches the shape the method builds; lets validateIdToken pass the keys to JWT::decode without a mixed flow. Cache-hit branch's (array) $item->get() gets an inline @var of the same shape — we only ever store this structure.
  • validateIdToken() declares $claims with \stdClass&object{aud: string|array<string>, iss: string, nonce: string} after JWT::decode. Spec-required claim types; firebase/php-jwt already validates JWT structure before this point. No runtime change.

tests/

  • testCreateResourceOwner adds an assertInstanceOf(ResourceOwnerInterface::class, $owner) guard so $owner->getId() type-checks (the value comes back from ReflectionMethod::invoke() which is mixed).
  • loadMockFixture()'s @return relaxed from array<string, mixed> to array<mixed>json_decode($content, true) doesn't statically guarantee string keys; callers cast / narrow as needed.
  • All 11 $mockJWT = \Mockery::mock('overload:Firebase\\JWT\\JWT', MockJWT::class) sites get an inline /** @var \Mockery\MockInterface $mockJWT */ annotation. phpstan/phpstan-mockery doesn't recognise the overload: mock-syntax (overload mocks are special-cased not to instantiate the target class, so the extension's class-resolution doesn't fire). The annotation gives PHPStan enough type information to type-check the chained shouldReceive(...)->... calls.

Test plan

  • task test:coverage — 100% on OpenIdConfigurationProvider (24/24 methods, 161/161 lines).
  • task analyze:php at level: 8 — no errors.
  • task analyze:php at level: max — 4 errors remain, all in OpenIdConfigurationProvider::__construct (PR A's scope).
  • task lint:php — clean.
  • task lint:markdown — clean.
  • CI matrix on PR.

Final level: max bump

Left as a separate one-line follow-up so the bump lands independently of its prerequisites:

- level: 8
+ level: max

Order: PR A merges, PR #42 / PR B / PR C merge (each rebasing as their dependencies land), then the one-line bump PR. After that bump, the codebase is at PHPStan's strictest level with zero errors.

🤖 Generated with Claude Code

Three small narrowings to clear the remaining `mixed` flow that
PHPStan flags at `level: max`:

- `getJwtVerificationKeys()` declares `@return array<string, Key>`
  matching the shape it actually builds. The cache-hit branch's
  `(array) $item->get()` is annotated with the same shape via inline
  `@var` — we only ever store this structure, so the trust is
  consistent with the method's contract.
- `validateIdToken()`'s `$claims` is annotated with a
  `\stdClass&object{aud: string|array<string>, iss: string, nonce:
  string}` shape after `JWT::decode()`. The fields are required
  string-typed claims per the OIDC spec; `firebase/php-jwt` already
  enforces JWT validity before this point. No runtime change.
- `testCreateResourceOwner` adds an `assertInstanceOf(...)` guard so
  the `$owner->getId()` call type-checks (the value comes back from
  `ReflectionMethod::invoke()` which PHPStan sees as `mixed`).
- `loadMockFixture()`'s `@return` is relaxed from
  `array<string, mixed>` to `array<mixed>` — `json_decode($content,
  true)` doesn't statically guarantee string keys, and the callers
  cast/narrow as needed for their specific fixture.
- 11 `$mockJWT = \Mockery::mock('overload:...')` sites in the test
  file get an inline `@var \Mockery\MockInterface` annotation. The
  `overload:` mock-syntax isn't recognised by `phpstan/phpstan-mockery`
  (overload mocks are special-cased to never instantiate the target
  class, so the extension's class-resolution doesn't fire); the
  annotation tells PHPStan the value is a `MockInterface` so the
  chained `shouldReceive()->...` calls type-check.

After this PR rebases onto develop (post-PR #43 / PR A merge), all
max-level errors are gone. The `phpstan.neon` `level: 8` → `level:
max` bump is left as a follow-up one-line PR so the bump and its
prerequisites land independently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (6dae797) to head (9ef1860).
⚠️ Report is 7 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##             develop       #45   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
+ Complexity        72        71    -1     
===========================================
  Files              1         1           
  Lines            185       185           
===========================================
  Hits             185       185           
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@turegjorup turegjorup self-assigned this May 12, 2026
@turegjorup turegjorup requested a review from jekuaitk May 12, 2026 10:30
Base automatically changed from feature/json-payload-narrowing to develop May 12, 2026 10:35
`[Unreleased]` had two `### Documentation` subsections — one added in
PR #44 for the README "Exception handling" section, one added later
for the per-class PHPDoc audit. markdownlint flags duplicate sibling
headings (MD024) and the CI step was failing. Merge the README
bullet into the per-class section so there's a single `###
Documentation` heading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@turegjorup turegjorup merged commit 820dc08 into develop May 12, 2026
14 checks passed
@turegjorup turegjorup deleted the feature/phpstan-max-completion branch May 12, 2026 10:50
@turegjorup turegjorup mentioned this pull request May 12, 2026
5 tasks
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.

2 participants