I'm developing a RESTful web application -- Apigility driven and based on the Zend Framework 2. For the model layer I'm using the ZfcBase
DbMapper
. The model essentially consists of two entities: Project
and Image
(1:n
) and is currently implemented like this:
ProjectCollection extends Paginator
ProjectEntity
ProjectMapper extends AbstractDbMapper
ProjectService implements ServiceManagerAwareInterface
ProjectServiceFactory implements FactoryInterface
The same structure for Image
.
When the resource (/projects[/:id]
) is requested, the responsed project entity/entities should contain a list of its/their Image
entities.
So, how can/should this 1:n
structure be implemented?
Subquestions:
Does [
DbMapper
] provide some "magic" for retrieving such tree structures "automatically" without to writeJOIN
s (or use an ORM)?Does [
Apigility
] provide some "magic" for building nested responses?
{
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects?page=1"
},
"first": {
"href": "http://myproject-api.misc.loc/projects"
},
"last": {
"href": "http://myproject-api.misc.loc/projects?page=1"
}
},
"_embedded": {
"projects": [
{
"id": "1",
"title": "project_1",
"images": [
{
"id": "1",
"title": "image_1"
},
{
"id": "2",
"title": "image_2"
}
],
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
},
{
"id": "2",
"title": "project_2",
"images": [
{
"id": "3",
"title": "image_3"
},
{
"id": "4",
"title": "image_4"
}
],
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
}
]
},
"page_count": 1,
"page_size": 25,
"total_items": 1
}
EDIT
The output I'm currentliy getting is:
/projects/:id
{
"id": "1",
"title": "...",
...
"_embedded": {
"images": [
{
"id": "1",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/1"
}
}
},
{
"id": "2",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/2"
}
}
},
{
"id": "3",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/3"
}
}
}
]
},
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
}
So it works for one single object. But not for collections, where single items include futher collections:
/projects
{
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects?page=1"
},
"first": {
"href": "http://myproject-api.misc.loc/projects"
},
"last": {
"href": "http://myproject-api.misc.loc/projects?page=24"
},
"next": {
"href": "http://myproject-api.misc.loc/projects?page=2"
}
},
"_embedded": {
"projects": [
{
"id": "1",
"title": "...",
... <-- HERE I WANT TO GET ["images": {...}, {...}, {...}]
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
},
{
"id": "2",
"title": "...",
... <-- HERE I WANT TO GET ["images": {...}, {...}, {...}]
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/2"
}
}
},
{
"id": "3",
"title": "...",
... <-- HERE I WANT TO GET ["images": {...}, {...}, {...}]
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/3"
}
}
}
]
},
"page_count": 24,
"page_size": 3,
"total_items": 72
}
EDIT
I edited my code and made a step to the goal.
It could not work, since my ProjectService#getProjects()
was just returning the projects' data from the database, not enriched with the images:
public function getProjects() {
return $this->getMapper()->findAll();
}
edited to:
public function getProjects() {
$projects = $this->getMapper()->findAll();
foreach ($projects as $key => $project) {
$images = $this->getImageService()->getImagesForProject($project['id']);
$projects[$key]['images'] = $images;
}
return $projects;
}
and the ProjectMapper#findAll()
public function findAll() {
$select = $this->getSelect();
$adapter = $this->getDbAdapter();
$paginatorAdapter = new DbSelect($select, $adapter);
$collection = new ProjectCollection($paginatorAdapter);
return $collection;
}
edited to:
public function findAll() {
$select = $this->getSelect();
$adapter = $this->getDbAdapter();
$paginatorAdapter = new DbSelect($select, $adapter);
// @todo Replace the constants with data from the config and request.
$projects = $paginatorAdapter->getItems(0, 2);
$projects = $projects->toArray();
return $projects;
}
Now I get the wished output:
{
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects"
}
},
"_embedded": {
"projects": [
{
"id": "1",
"title": "...",
...
"_embedded": {
"images": [
{
"id": "1",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/1"
}
}
},
{
...
},
{
...
}
]
},
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
},
{
"id": "2",
"title": "...",
...
"_embedded": {
"images": [
...
]
},
...
}
]
},
"total_items": 2
}
But it's a little bit crappy solution, isn't it? What I'm actually doing, is: I'm just replacing a part of the Apigility data retrieving functionality... Anyway, I don't like this solution and want to find a better one (an "Apigility conform solution").