Generate templated Link with Zend Expressive HAL

zend-expressive-hal
#1

As the title say I’m tryng to generate a templated Link as defined by the PSR-13 and documented in zend expressive hal.

My router is the FastRoute.

This is my actual routing configuration in the config/routes.php file:

    $app->get('/api/test/{id:\d+}',App\Handler\TestHandler::class,'api.test.get');

The code I use to generate the link:

/* Code */
 $linkGenerator=$this->resourceGenerator
        ->getLinkGenerator();
$linkGenerator->templatedFromRoute(
                    'test',
                    $request,
                    'api.test.get'
                );
/* Code */

The problem is that the code continue to throw an error with the following message:

Zend\Expressive\Router\Exception\RuntimeException raised in file D:\Sites\expressive-test\vendor\zendframework\zend-expressive-fastroute\src\FastRouteRouter.php line 299:
Message: Route `api.test.get` expects at least parameter values for [id], but received []

The json response that I expect is something similar to this:

{
    "id": 1,
    "name": "Test",
    "_links": {
        "self": {
            "href": "http://localhost/expressive-test/public/api/workflow-resources/1"
        },
        "test": {
            "href": "http://localhost/expressive-test/public/api/test/{id}",
            "templated": true
        }
    }
}
#2

Does it by chance work if you use {id} instead of {id:\d+}?

The json response that I expect is something similar to this

Where would the “1” come from?

#3

I already tried to remove the constraints and to rename the placeholder and it didn’t solve the error.
Maybe it’s a problem related to FastRoute, I will try with ZendRouter.

#4

Even with the Zend Router the problem persists.

The routes.php file contains the following with FastRoute:

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    $app->get('/api/test',App\Handler\TestHandler::class,'api.test.list');
    $app->post('/api/test',App\Handler\TestHandler::class,'api.test.create');
    $app->get('/api/test/{id}',App\Handler\TestHandler::class,'api.test.get');
    $app->get('/api/workflow-resources/{id} ',Workflow\Handler\GetWorkflowResource::class,'api.workflowresources.get');

};

The code of GetWorkflowResourceHandler:

<?php

declare(strict_types=1);

namespace Workflow\Handler;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;
use Zend\Expressive\Hal\LinkGenerator;
use Zend\Hydrator\ObjectPropertyHydrator;
use Workflow\Entity\WorkflowResource;

class GetWorkflowResource implements RequestHandlerInterface
{

    private $configJson;

    /** @var ResourceGenerator */
    private $resourceGenerator;

    /** @var HalResponseFactory */
    private $responseFactory;

    private $linkGenerator;

    /**
     * 
     */
    public function __construct(
        $configJson,
        ResourceGenerator $resourceGenerator,
        HalResponseFactory $responseFactory,
        LinkGenerator $linkGenerator
    ){

        $this->configJson=$configJson;
        $this->resourceGenerator = $resourceGenerator;
        $this->responseFactory = $responseFactory;
        $this->linkGenerator=$linkGenerator;
    }

/**
     * {@inheritDoc}
     */
    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $workflowResourceJson=array_shift($this->configJson);

        $hydrator=new ObjectPropertyHydrator();
        $workflowResource=$hydrator->hydrate($workflowResourceJson,new WorkflowResource());

        $resource = $this->resourceGenerator->fromObject($workflowResource, $request);
        //$linkGenerator = $this->resourceGenerator->getLinkGenerator();

        $linkGenerator=$this->linkGenerator;
        $actionLink=$linkGenerator->templatedFromRoute(
                    'test',
                    $request,
                   'api.test.get',
                    [],
                    []
                );
        $actionLink= $actionLink->withAttribute('method','GET');
        $resource=$resource->withLink($actionLink);

        return $this->responseFactory->createResponse($request, $resource);
    }

}

What I tried is to compose and use the class LinkGenerator instead of retrieving it directly from the $this->resourceGenerator->getLinkGenerator() but again the error appears.

Can anyone help me or show me a code where a templated link is generated with the zend expressive hal component?

#5

Also I searched the test developed for this feature and it seems to me that it is missing a specific case where some route parameters exist in the URI of the request.

#6

The error message that is thrown is correct, because the url helpers (used by the url generator) can not create the url without all required parameters. But if you add the missing parameter, you still do not get the right link with the placeholder. The url helpers return the fully generated link.

And the documentation does not say that you’re getting a templated link with placeholders.

fromRoute() will generate a non-templated Link instance, while templatedFromRoute() generates a templated instance.

Templated instance of the Link class, not templated link or URI which can be used in an output.
Unfortunately very misleading – in my opinion!

Conclusion: It looks like this feature is not implemented and should be reported at the issue tracker.

#7

Well, it was meant to be implemented… but clearly I didn’t do any integration tests. So, yes, please, report it in the tracker; ideally submit a PR with a failing test case.

#8

@matthew
Thanks for the quick feedback and the clarification!

#9

Ok thanks for the info. I will try to create a PR even if I dont know exactly where to start.