Skip to content

feat(core): introduce interfaces for the main services - #709

Merged
Spomky merged 1 commit into
4.3.xfrom
feature/service-interfaces
Aug 29, 2026
Merged

feat(core): introduce interfaces for the main services#709
Spomky merged 1 commit into
4.3.xfrom
feature/service-interfaces

Conversation

@Spomky

@Spomky Spomky commented Aug 28, 2026

Copy link
Copy Markdown
Member

Closes #684.

What

Every main service of the library now implements an interface, the library type-hints those interfaces internally, and the bundle ships a decorator for each of its event dispatching services. Nothing changes for existing code: no signature is narrowed, no service id moves, no runtime type changes.

Library

New interfaces, implemented by the existing classes:

Interface Implementation
Jose\Component\Signature\JWSBuilderInterface JWSBuilder
Jose\Component\Signature\JWSVerifierInterface JWSVerifier
Jose\Component\Signature\JWSLoaderInterface JWSLoader
Jose\Component\Encryption\JWEBuilderInterface JWEBuilder
Jose\Component\Encryption\JWEDecrypterInterface JWEDecrypter
Jose\Component\Encryption\JWELoaderInterface JWELoader
Jose\Component\NestedToken\NestedTokenBuilderInterface NestedTokenBuilder
Jose\Component\NestedToken\NestedTokenLoaderInterface NestedTokenLoader
Jose\Component\Checker\ClaimCheckerManagerInterface ClaimCheckerManager
Jose\Component\Checker\HeaderCheckerManagerInterface HeaderCheckerManager

JWSLoader, JWELoader, NestedTokenBuilder and NestedTokenLoader accept and return the interfaces instead of the concrete classes, so a decorator can be injected into any of them.

Extending the concrete classes is deprecated: they carry a @final annotation and their constructor raises a deprecation notice through InheritanceChecker. The event dispatching services of the bundle are excluded from that notice - they are deprecated themselves and cannot be avoided in 4.3 - so the notice only ever points at code the user owns.

Bundle

  • Ten decorators, one per event dispatching service: EventDispatchingJWSBuilder, EventDispatchingJWSVerifier, EventDispatchingJWSLoader, EventDispatchingJWEBuilder, EventDispatchingJWEDecrypter, EventDispatchingJWELoader, EventDispatchingNestedTokenBuilder, EventDispatchingNestedTokenLoader, EventDispatchingClaimCheckerManager and EventDispatchingHeaderCheckerManager. They dispatch the same events without inheriting anything.
  • The inheritance based Services\* classes are annotated @deprecated and keep doing the work in 4.3: swapping them for the decorators changes the runtime type of jose.jws_builder.* and friends, which would break every JWSBuilder $builder type hint. That swap belongs to 5.0.0.
  • Every service is now aliased with its interface as well as with its class, so JWSBuilderInterface $builder1JwsBuilder autowires exactly like JWSBuilder $builder1JwsBuilder does.

One bug fixed along the way

Services\JWSVerifier::verifyWithKeySet() and Services\JWEDecrypter::decryptUsingKeySet() declared five parameters and forwarded only those five to the parent, dropping the callable the loaders pass as a sixth argument to observe the discarded keys. The exception chaining added in #699 was therefore lost as soon as the bundle services were in use. Both now read and forward it with func_num_args()/func_get_arg(5), as the new decorators do.

Deliberate limitations

  • create() is deliberately absent from the two builder interfaces. Make the JWS/JWE builders truly immutable and deprecate create() #683 made the builders immutable and deprecated it, so there is no state to reset and the method disappears in 5.0.0. The concrete classes still carry it, so nothing existing breaks.
  • The failure events of the two builders carry internal state (signature_algorithm, key_encryption_algorithm) that a decorator cannot read. The decorators report the key and the headers they were given instead; the class docblocks say so. Exposing that data through the events themselves is part of the 5.0.0 plan.
  • The factories keep their concrete return types. A shared factory interface would have to settle the parameter name mismatch between Component\NestedToken\NestedTokenLoaderFactory::create() and its bundle counterpart, which is a change of its own.

Backward compatibility

Constructor parameters were widened from classes to interfaces (contravariant, safe for callers). JWSLoader::getJwsVerifier(), JWSLoader::getHeaderCheckerManager(), JWELoader::getJweDecrypter() and JWELoader::getHeaderCheckerManager() now return the interface; the interfaces expose the full public API of the classes they replace, so no call site can break. Everything else is additive.

Checks

Rebased on top of #706. phpunit (949 tests), ecs, phpstan, deptrac and rector --dry-run are green. The PHPStan baseline was regenerated: it gained the entries for the bundle's own use of its now deprecated services and lost four missingType.iterableValue entries that the new interface docblocks fix.

@Spomky Spomky self-assigned this Aug 28, 2026
@Spomky Spomky added the feature label Aug 28, 2026
@Spomky Spomky added this to the 4.3.0 milestone Aug 28, 2026
None of the main services of the library was final and none of them had an
interface: they could neither be relied upon as closed, nor be decorated. They
were left open only because the bundle extends them to dispatch events, which is
also why the state of the JWS and JWE builders is protected instead of private.

Every service now implements an interface - JWSBuilderInterface,
JWSVerifierInterface, JWSLoaderInterface, JWEBuilderInterface,
JWEDecrypterInterface, JWELoaderInterface, NestedTokenBuilderInterface,
NestedTokenLoaderInterface, ClaimCheckerManagerInterface and
HeaderCheckerManagerInterface - and the library type-hints those interfaces
internally, so that a decorator can be injected anywhere a service is expected.
The create() deprecated by #683 is left out of the two builder interfaces: the
builders are immutable, there is no state to reset and the method is removed in
5.0.0.

Extending the concrete classes is deprecated: they carry the @Final annotation
and their constructor raises a deprecation notice.

The bundle gains a decorator for every event dispatching service and deprecates
the inheritance based ones, which keep their class names, their service ids and
their behaviour. Each service is now also aliased with its interface, so that
"MyInterface $fooJwsBuilder" can be autowired the same way the concrete class
already was.

The event dispatching verifier and decrypter of the bundle used to drop the
callable the loaders pass as a sixth argument to observe the discarded keys, so
the reason of a failure was lost as soon as the bundle services were used. They
forward it now, as the new decorators do.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce interfaces for the main services and stop extending them in the bundle

1 participant