Skip to content

Epic — Introduce typed input contracts for decoder strategies #16

Closed
2 of 2 issues completed
Closed

Description

@Alexandre-T

Context

The decoder currently exposes a generic input contract allowing data to be provided as a string, an array, or an object.

public function decode(string|array|object $data): SpatialInterface;

Decoder strategies, however, do not necessarily support all these input representations.

For example, a text-based strategy may only accept strings, while another strategy may operate on structured arrays or objects.

Requiring every strategy to expose the complete string|array|object union weakens the strategy contract and forces individual implementations to handle input types they do not actually support.

PHP parameter variance does not allow an implementation to narrow such a parameter type. A strategy implementing a generic contract accepting string|array|object therefore cannot redefine its method to accept only string.

Objective

Introduce explicit input contracts for decoder strategies while preserving a unified public Decoder API.

Each strategy must explicitly declare the input representation or representations it supports.

The Decoder remains responsible for accepting the public set of supported input types and delegating the input only to a compatible strategy.

Target architecture

DecoderStrategyInterface becomes the common marker interface for all decoder strategies.

Specialized interfaces define the actual decoding contracts:

interface DecoderStrategyInterface
{
}

interface StringDecoderStrategyInterface extends DecoderStrategyInterface
{
    public function decode(string $data): SpatialInterface;
}

interface ArrayDecoderStrategyInterface extends DecoderStrategyInterface
{
    /**
     * @param array<string, mixed> $data
     */
    public function decode(array $data): SpatialInterface;
}

interface ObjectDecoderStrategyInterface extends DecoderStrategyInterface
{
    public function decode(object $data): SpatialInterface;
}

A strategy may implement one or several input contracts.

For example, a strategy supporting both textual and structured representations may implement:

final class ExampleStrategy implements
    StringDecoderStrategyInterface,
    ArrayDecoderStrategyInterface
{
    public function decode(string|array $data): SpatialInterface
    {
        // ...
    }
}

Decoder contract

The main decoder keeps the unified public API:

public function decode(string|array|object $data): SpatialInterface;

Before delegating the decoding operation, the Decoder must verify that the configured strategy implements the interface corresponding to the provided input type.

The compatibility mapping is:

Input Required strategy contract
string StringDecoderStrategyInterface
array ArrayDecoderStrategyInterface
object ObjectDecoderStrategyInterface

When the configured strategy does not support the provided input representation, the decoder must reject the call before invoking the strategy.

Exception contract

Introduce:

final class InvalidArgumentException extends \InvalidArgumentException implements DecoderExceptionInterface
{
}

This exception is raised when the supplied input type is incompatible with the currently configured decoder strategy.

This allows consumers to handle the exception either through the standard SPL hierarchy:

catch (\InvalidArgumentException $exception)

or through the Spatial Decoder exception contract:

catch (DecoderExceptionInterface $exception)

Acceptance criteria

  • DecoderStrategyInterface is the common parent contract for every decoder strategy.
  • Dedicated strategy interfaces exist for string, array, and object inputs.
  • Each strategy explicitly implements every input contract it supports.
  • A strategy may implement multiple input contracts.
  • Strategy decode() signatures use the narrowest native PHP type compatible with their declared contracts.
  • DecoderInterface::decode() continues to accept string|array|object.
  • Decoder delegates only when the configured strategy supports the supplied input representation.
  • An incompatible input/strategy combination throws the library InvalidArgumentException.
  • InvalidArgumentException extends \InvalidArgumentException and implements DecoderExceptionInterface.
  • Existing decoder strategies are migrated to the appropriate specialized contracts.
  • Tests cover every supported input/strategy combination.
  • Tests cover rejection of incompatible input/strategy combinations.
  • Tests cover strategies implementing more than one input contract.
  • Public documentation explains the distinction between the generic Decoder contract and the specialized strategy contracts.
  • Strategy implementation documentation explains how third-party strategies declare their supported input representations.
  • All existing quality and CI gates remain green.

Compatibility considerations

This Epic changes the public strategy API and must therefore be treated as a backward compatibility break if DecoderStrategyInterface is already part of the stable public API.

The unified DecoderInterface::decode() signature remains unchanged.

The migration impact is primarily limited to implementations of DecoderStrategyInterface, which must explicitly select one or more specialized input contracts.

Out of scope

This Epic does not introduce new input representations.

It does not change the spatial objects returned by decoding strategies.

It does not define format-specific validation rules. Validation of the content itself remains the responsibility of each strategy.

It does not introduce automatic conversion between input representations. The Decoder only verifies compatibility and delegates decoding.

Metadata

Metadata

Assignees

Labels

impact:apiAffects the public APIimpact:breakingIntroduces a backward-incompatible change

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions