phly / PhlyRestfully

ZF2 module for creating RESTful JSON APIs using HAL and API-Problem
108 stars 45 forks source link

Add links to resources inside the resources in an HalCollection #59

Closed ghost closed 11 years ago

ghost commented 11 years ago

This is a question (sorry about the recursive title).

Problem: We have the necessity to return links to other resources inside the resources listed by a getList(). Example: I want all the schools in my city (getList() in the controller) and inside each of them links to all the classroom they have.

We are having a problem trying to add Links inside the resources returned from an HalCollection. In the listener we can return an array or an HalCollection (containing an array) without problems, but it's not allowed to us to give HalCollection an array of HalResource (for what we could find out). Without access to the HalResource object we could not find a way to add other links (that are not self) to them. Even if we attach ourself to various trigger in the work-flow we are unable to do it. For what we found out is in the renderer (and specifically in HalLinks) that the collection is parsed and translated to HalResources. HalCollections also have no setter for the main content, so we will probably have to extend the class.

We tried also using the new "child resources" capability of the library, but we decided that we don't like the work-flow (this is just about our code logic, not a comment about the functionality or how it is implemented).

We would like an advice to how to solve our problem. Maybe (probably) we are missing something. Thank you for the time you can dedicate to our question.

Fabrizio

weierophinney commented 11 years ago

In the listener we can return an array or an HalCollection (containing an array) without problems, but it's not allowed to us to give HalCollection an array of HalResource (for what we could find out).

You can do this by assigning a HalCollection to a property of a given resource. As an example, consider the following entity:

class User
{
    public $id;
    public $name;
    public $emailAddresses;
}

If we assign a HalCollection to $emailAddresses, when a HalResource using a User entity is rendered, it will render that collection as an _embedded object, with each member a HalResource.

This means that you may need to do a little munging in your data access layer, unfortunately; I've not yet come up with a way to make it completely transparent.

ghost commented 11 years ago

Thank you for your reply. Even if we try with the following code, no Collection is rendered inside the resources ($user is returned in the get call):

$user = new User();
$user->id = 3;
$user->name = 'test';
$hal   = new HalCollection(array('a','b','c'), 'company', array('id' => 1));
$user->emailAddresses = $hal;

Result

    {
       "id": 3,
       "name": "test",
       "emailAddresses":
       {
       },
       "_links":
       {
           "self":
           {
               "href": "http://api.test.dev/v1/company/3"
           }
       }
    }

We tried the same collection in a getAll and it gets rendered so maybe we are missing something.

Fabrizio

weierophinney commented 11 years ago

In looking through the HalLinks::renderResource() source, it looks like I only check for HalResource objects, and not collections! Working on a fix now for you.

ghost commented 11 years ago

Thank you very much for your help.

weierophinney commented 11 years ago

@fabrizio-blur Can you give the patch in this PR a try, please, and let me know if it resolves your issue?

Essentially, if you add a HalCollection as a property to a resource, it will now render as an embedded collection.

ghost commented 11 years ago

Yes obviously (I am sorry but I also commented as blurgroup in this Issue, I was logged with another account and I didn't notice). Now it does not ignore any more the field, but the rendering (I think) is not correct:

    $user = new User();
    $user->id = 3;
    $user->name = 'test';
    $halResourceInstance = new HalResource(array('role' => 'user'), '4');
    $aLink = new Link('self');
    $aLink->setRoute('role', array('id' => 4));
    $halResourceInstance->getLinks()->add($aLink);
    $halCollectionInstance = new HalCollection(array('a', 'foo' => 'doo', $halResourceInstance), 'company', array('id' => 1));
    $user->roles = $halCollectionInstance;

Result

    {
       "id": 3,
       "name": "test",
       "_embedded":
       {
           "roles":
           [
               [
                   "a"
               ],
               [
                   "doo"
               ],
               {
                   "*id": "4",
                   "*links":
                   {
                   },
                   "*resource":
                   {
                       "role": "user"
                   }
               }
           ]
       },
       "_links":
       {
           "self":
           {
               "href": "http://api.test.dev/v1/company/3"
           }
       }
    }
weierophinney commented 11 years ago

@fabrizio-blur It's a bad collection you're defining. I just tried the following:

<?php
namespace Application\Listener;

use PhlyRestfully\HalCollection;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;

class User extends AbstractListenerAggregate
{
    public function attach(EventManagerInterface $events)
    {
        $this->listeners[] = $events->attach('fetch', array($this, 'onFetch'));
    }

    public function onFetch($e)
    {
        $user = (object) array(
            'id'   => 3,
            'name' => 'test',
        );
        $companies = array(
            (object) array('id' => 'a', 'name' => 'A'),
            (object) array('id' => 'b', 'name' => 'B'),
            (object) array('id' => 'c', 'name' => 'C'),
        );
        $collection = new HalCollection($companies, 'api/companies');
        $user->companies = $collection;
        return $user;
    }
}

And it worked perfectly:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/hal+json
Host: collections.dev:8080
X-Powered-By: PHP/5.4.11

{
    "_embedded": {
        "companies": [
            {
                "_links": {
                    "self": {
                        "href": "http://collections.dev:8080/api/companies/a"
                    }
                },
                "id": "a",
                "name": "A"
            },
            {
                "_links": {
                    "self": {
                        "href": "http://collections.dev:8080/api/companies/b"
                    }
                },
                "id": "b",
                "name": "B"
            },
            {
                "_links": {
                    "self": {
                        "href": "http://collections.dev:8080/api/companies/c"
                    }
                },
                "id": "c",
                "name": "C"
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://collections.dev:8080/api/users/3"
        }
    },
    "id": 3,
    "name": "test"
}
ghost commented 11 years ago

Yes I tried with your code and it works. Thank you for the enhancement to the library.