Migration

Migration From Version 1

While we strive to reduce backwards compatibility breaks, occasionally we find them necessary in order to either correct problems, make usage more predictable, or simplify maintenance. Such changes are reserved for new major releases.

New classes and interfaces

We extracted a new interface, Mezzio\Hal\ResourceGeneratorInterface, from the public API of Mezzio\Hal\ResourceGenerator. Doing so allows providing alternate implementations in your own applications without requiring class extension. The new interface has the following definition:

namespace Mezzio\Hal;

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;

interface ResourceGeneratorInterface
{
    public function getHydrators(): ContainerInterface;

    public function getMetadataMap(): Metadata\MetadataMap;

    public function getLinkGenerator(): LinkGenerator;

    public function fromArray(array $data, ?string $uri = null): HalResource;

    public function fromObject(object $instance, ServerRequestInterface $request, int $depth = 0): HalResource;
}

Please note that the fromObject() method changes slightly:

  • $instance now has the object typeint
  • it adds the optional argument int $depth = 0; this argument is used to help identify and protect against circular references in nested resources.

Mezzio\Hal\ResourceGenerator now implements this interface.

Signature changes

Mezzio\Hal\ResourceGenerator

As noted in the previous section, the signature of the fromObject() method changes from:

public function fromObject(
    $instance,
    Psr\Http\Message\ServerRequestInterface $request
): Mezzio\Hal\HalResource;

to:

public function fromObject(
    object $instance,
    Psr\Http\Message\ServerRequestInterface $request
    int $depth = 0
): Mezzio\Hal\HalResource;

These changes should only impact users extending the ResourceGenerator class.

Mezzio\Hal\LinkCollection

The trait Mezzio\Hal\LinkCollection provides implementation of the PSR-13 EvolvableLinkProviderInterface that other classes may compose. PHP allows adding return typehints to methods defined by an interface so long as the typehint is narrower than the original. In the specification, these methods did not have an explicit return typehint, but did provide one via annotations. As such, the following method signatures were updated to have the associated return typehints:

  • getLinks(): array
  • getLinksByRel($rel): array
  • withLink(Psr\Link\LinkInterface $link): self
  • withoutLink(Psr\Link\LinkInterface $link): self

These changes should not have any effect on users, as signatures of overridden trait methods do not need to match.

Mezzio\Hal\ResourceGenerator\StrategyInterface

The signature of the fromObject() method originally was:

public function createResource(
    $instance,
    Mezzio\Hal\Metadata\AbstractMetadata $metadata,
    Mezzio\Hal\ResourceGenerator $resourceGenerator,
    Psr\Http\Message\ServerRequestInterface $request
): Mezzio\Hal\HalResource;

and is now:

public function createResource(
    object $instance,
    Mezzio\Hal\Metadata\AbstractMetadata $metadata,
    Mezzio\Hal\ResourceGeneratorInterface $resourceGenerator,
    Psr\Http\Message\ServerRequestInterface $request,
    int $depth = 0
): Mezzio\Hal\HalResource;

The changes:

  • Enforce that the $instance value MUST be an object.
  • Allow for alternate resource generator implementations so long as they implement the new ResourceGeneratorInterface.
  • Provide the ability to protect against circular references in nested resources via the $depth value, which will generally be provided when the $resourceGenerator calls on a strategy implementation.

These changes also affect all shipped implementations of the StrategyInterface, including the following:

  • Mezzio\Hal\ResourceGenerator\RouteBasedCollectionStrategy
  • Mezzio\Hal\ResourceGenerator\RouteBasedResourceStrategy
  • Mezzio\Hal\ResourceGenerator\UrlBasedCollectionStrategy
  • Mezzio\Hal\ResourceGenerator\UrlBasedResourceStrategy

These changes will impact users who are extending one of the above classes, or providing their own StrategyInterface implementation.

Mezzio\Hal\Metadata\AbstractMetadata

The Mezzio\Hal\Metadata\AbstractMetadata class, from which all other metadata classes derive, adds a new method, hasReachedMaxDepth(int $currentDepth): bool. Extensions can override this method to provide logic for determining if traversal has reached the maximum depth, and is called by the resource generator in order to help prevent circular references in nested resources.

This change will only affect users creating their own metadata implementations; developers should look at how each of the shipped implementations define the method to determine how they should do so in their own classes.

Mezzio\Hal\Metadata\RouteBasedResourceMetadata

The signature of the __construct method changes from:

public function __construct(
    string $class,
    string $route,
    string $extractor,
    string $resourceIdentifier = 'id',
    string $routeIdentifierPlaceholder = 'id',
    array $routeParams = [],
    array $identifiersToPlaceholdersMapping = [],
    int $maxDepth = 10
) {

to:

public function __construct(
    string $class,
    string $route,
    string $extractor,
    string $resourceIdentifier = 'id',
    array $routeParams = [],
    array $identifiersToPlaceholdersMapping = [],
    int $maxDepth = 10
) {

Note the removal of the $routeIdentifierPlaceholder argument.

If you were previously using this argument, you will need to instead populate the $identifiersToPlaceholdersMapping argument. As an example, if you had an entity that defined an "id" property as an identifier, but your route definition used "calc_id" as the equivalent (e.g., /calculation/{calc_id:\d+}), you might have called the constructor as follows:

$metadata = new RouteBasedResourceMetadata(
    CalculationEntity::class,
    'calculation',
    ObjectHydrator::class,
    'id',
    'calc_id'
);

This would change to:

$metadata = new RouteBasedResourceMetadata(
    CalculationEntity::class,
    'calculation',
    ObjectHydrator::class,
    'id',
    [],
    ['id' => 'calc_id']
);

The new logic provides an explicit mapping of any resource property to their corresponding route placeholders, no longer limiting you to only mapping resource identifiers.

Mezzio\Hal\HalResource

The method jsonSerialize() now has a return typehint of array; this was implied previously by the JsonSerializable interface the method provides an implementation for, but is now explicit.

This change will only affect those extending the HalResource class.

Mezzio\Hal\Exception\UndefinedMetadataException

The signature of the create() method changes from:

public static function create($class)

to:

public static function create(string $class): self

This change will only affect extensions to this class.

Mezzio\Hal\Exception\UndefinedClassException

The signature of the create() method changes from:

public static function create($class)

to:

public static function create(string $class): self

This change will only affect extensions to this class.

Mezzio\Hal\Exception\DuplicateMetadataException

The signature of the create() method changes from:

public static function create(string $class)

to:

public static function create(string $class): self

This change will only affect extensions to this class.

Configuration changes

route_identifier_placeholder removed

In version 1, the configuration setting route_identifier_placeholder could be used when configuring RouteBasedResourceMetadata, and was used to indicate the route placeholder that corresponded with the resource identifier.

Version 2 removes this setting. Users should instead use the identifiers_to_placeholders_mapping setting introduced in the 1.4 series. This configuration allows you to map not just the resource identifier to its route placeholder; with it, you can map ANY resource property that has associated route placeholders. This is particularly interesting when creating nested resources, where the parent resource identifier may be present in the resource itself, allowing routing to the nested resource easily.

As an initial example, we'll cover the original use case for a route_identifier_placeholder on its own. Let's consider an entity that defined an "id" property as an identifier, but your route definition used "calc_id" as the equivalent (e.g., /calculation/{calc_id:\d+}). You might have configured it as follows:

use Laminas\Hydrator\ObjectHydrator;
use Mezzio\Hal\Metadata\MetadataMap;
use Mezzio\Hal\Metadata\RouteBasedResourceMetadata;

return [
    MetadataMap::class => [
        [
            '__class__'                    => RouteBasedResourceMetadata::class,
            'resource_class'               => CalculationEntity::class,
            'route'                        => 'calculation',
            'extractor'                    => ObjectHydrator::class,
            'resource_identifier'          => 'id',
            'route_identifier_placeholder' => 'calc_id',
        ]
    ],
];

With v2, this should be rewritten as:

use Laminas\Hydrator\ObjectHydrator;
use Mezzio\Hal\Metadata\MetadataMap;
use Mezzio\Hal\Metadata\RouteBasedResourceMetadata;

return [
    MetadataMap::class => [
        [
            '__class__'                           => RouteBasedResourceMetadata::class,
            'resource_class'                      => CalculationEntity::class,
            'route'                               => 'calculation',
            'extractor'                           => ObjectHydrator::class,
            'resource_identifier'                 => 'id',
            'identifiers_to_placeholders_mapping' => [
                'id' => 'calc_id',
            ],
        ]
    ],
];

To expand on this example, and demonstrate the new capabilities possible, let's consider a route definition that also expected a value for an "id" associated with the resource's "transaction_id" property (e.g., /transaction/{id:\d+}/calculation/{calc_id:\d+}). You could configure that as follows:

use Laminas\Hydrator\ObjectHydrator;
use Mezzio\Hal\Metadata\MetadataMap;
use Mezzio\Hal\Metadata\RouteBasedResourceMetadata;

return [
    MetadataMap::class => [
        [
            '__class__'                           => RouteBasedResourceMetadata::class,
            'resource_class'                      => CalculationEntity::class,
            'route'                               => 'calculation',
            'extractor'                           => ObjectHydrator::class,
            'resource_identifier'                 => 'id',
            'identifiers_to_placeholders_mapping' => [
                'id'             => 'calc_id',
                'transaction_id' => 'id',
            ],
        ]
    ],
];

In this scenario, the id property of the resource will be substituted for the calc_id route placeholder, and the transaction_id property will be substituted for the id route placeholder.