Marker-interface exception contract (5.0 — BREAKING)#40
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #40 +/- ##
===========================================
Coverage 100.00% 100.00%
Complexity 62 62
===========================================
Files 1 1
Lines 172 172
===========================================
Hits 172 172
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
2103d54 to
0d75a11
Compare
The public exception contract becomes a marker interface — `\ItkDev\OpenIdConnect\Exception\OpenIdConnectExceptionInterface extends \Throwable` — instead of the abstract `ItkOpenIdConnectException` class. Every concrete exception is re-parented to the SPL type that best describes its failure category (`\RuntimeException` for transient/data-shape failures; `\LogicException` for programmer/config bugs; `\InvalidArgumentException` for invalid constructor input) and implements the marker. Consumers can now catch every OIDC failure with `catch (OpenIdConnectExceptionInterface $e)` or scope to a specific SPL parent. Concretes no longer extend the abstract `ItkOpenIdConnectException`; the abstract is kept for 5.x as a `@deprecated` alias that still implements the marker, but existing `catch (ItkOpenIdConnectException $e)` blocks will not match anything thrown by 5.0+ code. This is the user-visible BC break behind the MAJOR bump. The README "Exception handling" section walks through the consumer migration. Adds `ConfigurationException` (extending `\InvalidArgumentException`) for missing/invalid constructor options, replacing the raw `\InvalidArgumentException` previously thrown from the constructor. The new type still extends `\InvalidArgumentException`, so existing catches at that level keep matching. Narrows `getIdToken`'s `catch (\Exception $e)` boundary to `IdentityProviderException|ClientExceptionInterface|\JsonException` — the three actually-thrown families — so unexpected failures are no longer silently wrapped as `CodeException`. Cache failures during `getConfiguration` (called for the token-endpoint lookup) now propagate as `CacheException` rather than being re-wrapped. Adds `tests/Exception/ExceptionHierarchyTest.php` to lock the contract: every concrete is verified to implement the marker, extend the correct SPL parent, and be catchable via the marker. Failing this test class is the early warning that the public contract has drifted. Updates the README with a new "Exception handling" subsection (marker interface, SPL parent table, PSR-18 co-implementation note on `HttpException`, and 4.x → 5.0 catch-block migration) and updates the `validateIdToken` example to catch the marker interface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0d75a11 to
4f0f6ed
Compare
| // @phpstan-ignore deadCode.unreachable | ||
| $this->fail('Catch on OpenIdConnectExceptionInterface should have matched '.$concrete); |
There was a problem hiding this comment.
If its unreachable lets just remove it.
| // @phpstan-ignore deadCode.unreachable | |
| $this->fail('Catch on OpenIdConnectExceptionInterface should have matched '.$concrete); |
There was a problem hiding this comment.
It is unreachable in current code, and as such is flagged by phpstan unless ignored. The purpose of the test is to flag future regressions if somebody updates the code and breaks the exception design contract. To do that we need to catch by interface (the contract) and fail if something falls through the catch. As long as the code follows the contract, the "fail" will be dead code
`@phpstan-ignore deadCode.unreachable` now carries a parenthesized reason (PHPStan's documented format for ignore justifications) so a reviewer can tell at a glance why the suppression exists. The suppressed line is intentionally unreachable in the happy path; it serves as a fail-safe if a future regression breaks the catch-by-marker contract this test verifies. The anonymous `ClientExceptionInterface` implementer in `testGetIdTokenFailure` now has a one-line comment explaining the choice. Guzzle's real exception constructors require a RequestInterface we don't have in a unit test, and any PSR-18 implementer satisfies the contract `getIdToken`'s narrowed catch actually targets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds PHPStan's `reportIgnoresWithoutComments: true` setting (available in `phpstan/phpstan` 2.1.41+, so bump the require-dev constraint). With this setting active, any `@phpstan-ignore` directive without a parenthesized justification fails CI — turning the documented convention into a hard rule that won't quietly drift back. To make the setting actually fire on the test where the original violation surfaced, `tests/` is added to PHPStan's `paths`. That surfaces 46 pre-existing static-analysis issues unrelated to the contract migration (mostly `string|false` flow into `json_decode`, Mockery stub method typing, and dynamic property access on `\stdClass` claims). Generating `phpstan-baseline.neon` grandfathers those so they don't block this PR while still letting future regressions in test code surface as fresh errors. The baseline is deliberately a temporary measure: it should shrink over time as the test-code static-analysis debt is paid down in follow-up PRs. The alternative (keeping `tests/` out of PHPStan scope) would leave the new ignore-comments rule unenforced in exactly the location where the original reviewer complaint landed — which defeats the point of adding the rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
The public exception contract becomes a marker interface —
\ItkDev\OpenIdConnect\Exception\OpenIdConnectExceptionInterface extends \Throwable— instead of the abstractItkOpenIdConnectExceptionclass. Every concrete exception is re-parented to the SPL type that best describes its failure category and implements the marker.See the new "Exception handling" subsection in
README.mdfor the consumer-facing migration guide.What changes
Hierarchy
OpenIdConnectExceptionInterface(marker,extends \Throwable).ConfigurationException extends \InvalidArgumentException(replaces raw\InvalidArgumentExceptionthrown from the constructor when a required option is missing).ItkOpenIdConnectExceptionabstract class. Kept for 5.x with@deprecated; still implements the marker. Concretes no longer extend it, so existingcatch (ItkOpenIdConnectException $e)blocks will not match anything thrown by 5.0+ code. This is the BC break behind the MAJOR bump.BadUrlException,IllegalSchemeException,MissingParameterException\LogicExceptionConfigurationException,NegativeCacheDurationException,NegativeLeewayException\InvalidArgumentExceptionCacheException,HttpException,JsonException,DecodeException,KeyException,CodeException,ValidationException,ClaimsException\RuntimeExceptionHttpExceptionkeeps its second interfacePsr\Http\Client\ClientExceptionInterfaceso PSR-18-aware consumers can still catch on the standard PSR marker.Boundary fixes in
OpenIdConfigurationProvider\InvalidArgumentExceptionasConfigurationException(still extends\InvalidArgumentException, so existing catches at that level keep matching).getIdTokennarrows its boundarycatchfrom\ExceptiontoIdentityProviderException|ClientExceptionInterface|\JsonException. Failures fromgetConfiguration(e.g.CacheExceptionduring the token-endpoint lookup) now propagate as their own concrete type rather than being re-wrapped asCodeException.@throwsPHPDoc now referencesOpenIdConnectExceptionInterfaceinstead of the deprecated abstract.Test additions
tests/Exception/ExceptionHierarchyTest.phplocks the contract: every concrete is verified to implement the marker, extend the correct SPL parent, and be catchable viacatch (OpenIdConnectExceptionInterface $e). Failing this class is the early warning that the public contract has drifted.Documentation
README.mdcovering the marker interface, the SPL parent table, the PSR-18 co-implementation, and the 4.x → 5.0 catch-block migration.validateIdTokenexample in the README now catches the marker interface instead of the deprecated abstract.Consumer migration
Mechanical, no semantic changes:
catch (ItkOpenIdConnectException $e)→catch (OpenIdConnectExceptionInterface $e).CacheException,ValidationException,ClaimsException, …) need no change — class names and namespaces are stable.\Exceptioncontinue to match.\RuntimeException(e.g. generic retry decorators) now match transient OIDC failures, which is the intended semantic; previously such code was invisible to library exceptions. Flagged for upgrade-notes review.Test plan
task test:coverage— 87 tests, 135 assertions; 100% coverage onOpenIdConfigurationProvider(24/24 methods, 148/148 lines) preserved; newExceptionHierarchyTestclass adds 43 assertions across 15 data sets.task analyze:php— PHPStan max, no errors.task lint:php— clean.task lint:markdown— clean.🤖 Generated with Claude Code