0

I've some problems to return a paginator object as HAL json collection. I'm using the latest versions of zend-expressive and zend-expressive-hal.
This is the setting from my ConfigProvider:

public function __invoke() : array
{
    return [
        'dependencies' => $this->getDependencies(),
        MetadataMap::class => $this->getHalConfig(),
    ];
}

public function getHalConfig() : array
{
    return [
        [
            '__class__' => RouteBasedCollectionMetadata::class,
            'collection_class' => RoleCollection::class,
            'collection_relation' => 'user_roles',
            'route' => 'api.user.roles',
        ],
    ];
}

And these are my handler methods:

public function get(ServerRequestInterface $request) : ResponseInterface
{
    // read some records from the database
    $select = new Select();
    $select->from(['r' => 'user_roles']);
    $select->columns(['id', 'name']);

    $paginator = new RoleCollection(new DbSelect($select, $this->dbAdapter));
    $paginator->setItemCountPerPage(25);
    $paginator->setCurrentPageNumber(1);

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

private function createResponse(ServerRequestInterface $request, $instance) : ResponseInterface
{
    return $this->responseFactory->createResponse(
        $request,
        $this->resourceGenerator->fromObject($instance, $request)
    );
}

The RoleCollection class is only an inheritance of the Paginator:

class RoleCollection extends Paginator
{
}

The error message which I get is:

Cannot generate Zend\Expressive\Hal\HalResource for object of type ArrayObject; not in metadata map
altralaser
  • 2,035
  • 5
  • 36
  • 55

1 Answers1

0

I think you are missing the metadata for the Role object itself.

For example this is something similar for my posts object:

MetadataMap::class => [
    [
        '__class__'           => RouteBasedCollectionMetadata::class,
        'collection_class'    => Posts::class,
        'collection_relation' => 'posts',
        'route'               => 'api.posts',
    ],
    [
        '__class__'      => RouteBasedResourceMetadata::class,
        'resource_class' => Post::class,
        'route'          => 'api.posts.view',
        'extractor'      => ArraySerializable::class,
    ],
],

You have only described the collection and the resource class is missing for a single role.

I also see the resource generator tries to parse an ArrayObject. This should be wrapped in a Role object, which you can add to the MetadataMap.

Where it goes wrong in your code is this line:

$paginator = new RoleCollection(new DbSelect($select, $this->dbAdapter));

This adds the result of a query into the paginator, but the paginator does not know how to handle it. If I remember correctly, the DbSelect return a ResultSet. I'm guessing this is where the ArrayObject is coming from. What you probably need is to override that ResultSet and make sure it returns an array of Role objects. You might want to look into the dbselect adapter and the hydrating resultset.

Once you have the Role object in the paginator, you can describe it in the metadata.

[
    '__class__'      => RouteBasedResourceMetadata::class,
    'resource_class' => UserRole::class,
    'route'          => 'api.roles',
    'extractor'      => ...,
],

I use doctrine myself with hal so zend-db is out of my scope. If you need more help, I suggest the zf forums.

xtreamwayz
  • 1,285
  • 8
  • 10
  • Unfortunately that does not work. You also defined the resource for a different route than the collection. This probably means that you want to retrieve a single entry and once a list. I think my problem is that the getCurrentItems() method is called on the Paginator object, which of course returns an ArrayObject. Maybe zend-paginator and expressive-hal are incompatible, I don't know. – altralaser Jul 14 '19 at 17:45
  • zend-paginator and hall are definitely working together. I'v got it working. And yes, the single resource route is different than the collection route. In your case the collection might be something like `/api/users/{user_id}/roles` and the roles resource route might look like `/api/roles/{role_id}`. Where it goes wrong is that you inject your roles as standard classes in the collection, straight from the query. I'll update my answer. – xtreamwayz Jul 15 '19 at 16:02
  • About the different routes, I'm not sure if you can just skip the route for a resource. The whole reason to use HAL is adding routes to resource and collections. But I've seen different metadata classes, so maybe there is something like a NonRouteResourceMetadata class. – xtreamwayz Jul 15 '19 at 16:25