如何使用基于ZfcBase-DbMapper的模型在Apigility驱动的应用程序中构建嵌套响应? [英] How to build nested responses in an Apigility driven application with a ZfcBase-DbMapper based model?

查看:77
本文介绍了如何使用基于ZfcBase-DbMapper的模型在Apigility驱动的应用程序中构建嵌套响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发RESTful Web应用程序- Apigility 驱动并基于 ZfcBase

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

Image相同的结构.

当请求资源(/projects[/:id])时,响应的项目​​实体应包含其/c3实体的列表.

When the resource (/projects[/:id]) is requested, the responsed project entity/entities should contain a list of its/their Image entities.

那么,如何/应该实现这种1:n结构?

So, how can/should this 1:n structure be implemented?

子问题:

  1. [DbMapper]是否提供一些魔术"来自动"检索此类树结构而无需编写JOIN(或使用ORM)?

  1. Does [DbMapper] provide some "magic" for retrieving such tree structures "automatically" without to write JOINs (or use an ORM)?

[Apigility]是否为构建嵌套响应提供了一些魔术"?

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
}


编辑

我当前得到的输出是:

/projects/:id

/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

/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
}


编辑

我编辑了代码,并朝着目标迈出了一步.

I edited my code and made a step to the goal.

这不起作用,因为我的 ProjectService#getProjects() 只是从数据库中返回项目的数据,而没有充实图像:

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();
}

修改为:

public function getProjects() {
    $projects = $this->getMapper()->findAll();
    foreach ($projects as $key => $project) {
        $images = $this->getImageService()->getImagesForProject($project['id']);
        $projects[$key]['images'] = $images;
    }
    return $projects;
}

ProjectMapper#findAll()

and the ProjectMapper#findAll()

public function findAll() {
    $select = $this->getSelect();
    $adapter = $this->getDbAdapter();
    $paginatorAdapter = new DbSelect($select, $adapter);
    $collection = new ProjectCollection($paginatorAdapter);
    return $collection;
}

修改为:

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
}

但这是一个糟糕的解决方案,不是吗?我实际上正在做的是:我只是替换了Apigility数据检索功能的一部分...无论如何,我不喜欢这种解决方案,而是想找到一个更好的解决方案("Apigility兼容解决方案").

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").

推荐答案

我终于找到了解决方案. (再次感谢@ poisa 为他的

I have finally found a solution. (Thanks once again @ poisa for his solution suggestion on GitHub.) In short, the idea is to enrich the (projects) list items with nested (image) items lists on the hydration step. I actually don't really like this way, since it's too much model logic on the hydration level for me. But it works. Here we go:

/module/Portfolio/config/module.config.php

/module/Portfolio/config/module.config.php

return array(
    ...
    'zf-hal' => array(
        'metadata_map' => array(
            ...
            'Portfolio\\V2\\Rest\\Project\\ProjectEntity' => array(
                'entity_identifier_name' => 'id',
                'route_name' => 'portfolio.rest.project',
                'route_identifier_name' => 'id',
                'hydrator' => 'Portfolio\\V2\\Rest\\Project\\ProjectHydrator',
            ),
            'Portfolio\\V2\\Rest\\Project\\ProjectCollection' => array(
                'entity_identifier_name' => 'id',
                'route_name' => 'portfolio.rest.project',
                'route_identifier_name' => 'id',
                'is_collection' => true,
            ),
            ...
        ),
    ),
);

Portfolio\Module

Portfolio\Module

class Module implements ApigilityProviderInterface {

    ...

    public function getHydratorConfig() {
        return array(
            'factories' => array(
                // V2
                'Portfolio\\V2\\Rest\\Project\\ProjectHydrator' => function(ServiceManager $serviceManager) {
                    $projectHydrator = new ProjectHydrator();
                    $projectHydrator->setImageService($serviceManager->getServiceLocator()->get('Portfolio\V2\Rest\ImageService'));
                    return $projectHydrator;
                }
            ),
        );
    }

    ...

}

Portfolio\V2\Rest\Project\ProjectHydrator

Portfolio\V2\Rest\Project\ProjectHydrator

namespace Portfolio\V2\Rest\Project;

use Zend\Stdlib\Hydrator\ClassMethods;
use Portfolio\V2\Rest\Image\ImageService;

class ProjectHydrator extends ClassMethods {

    /**
     * @var ImageService
     */
    protected $imageService;

    /**
     * @return ImageService the $imageService
     */
    public function getImageService() {
        return $this->imageService;
    }

    /**
     * @param ImageService $imageService
     */
    public function setImageService(ImageService $imageService) {
        $this->imageService = $imageService;
        return $this;
    }

    /*
     * Doesn't need to be implemented:
     * the ClassMethods#hydrate(...) handle the $data already as wished.
     */
    /*
    public function hydrate(array $data, $object) {
        $object = parent::hydrate($data, $object);
        if ($object->getId() !== null) {
            $images = $this->imageService->getImagesForProject($object->getId());
            $object->setImages($images);
        }
        return $object;
    }
    */

    /**
     * @see \Zend\Stdlib\Hydrator\ClassMethods::extract()
     */
    public function extract($object) {
        $array = parent::extract($object);
        if ($array['id'] !== null) {
            $images = $this->imageService->getImagesForProject($array['id']);
            $array['images'] = $images;
        }
        return $array;
    }

}

Portfolio\V2\Rest\Project\ProjectMapperFactory

Portfolio\V2\Rest\Project\ProjectMapperFactory

namespace Portfolio\V2\Rest\Project;

use Zend\ServiceManager\ServiceLocatorInterface;

class ProjectMapperFactory {

    public function __invoke(ServiceLocatorInterface $serviceManager) {
        $mapper = new ProjectMapper();
        $mapper->setDbAdapter($serviceManager->get('PortfolioDbAdapter_V2'));
        $mapper->setEntityPrototype($serviceManager->get('Portfolio\V2\Rest\Project\ProjectEntity'));
        $projectHydrator = $serviceManager->get('HydratorManager')->get('Portfolio\\V2\\Rest\\Project\\ProjectHydrator');
        $mapper->setHydrator($projectHydrator);
        return $mapper;
    }

}

Portfolio\V2\Rest\Project\ProjectMapper

Portfolio\V2\Rest\Project\ProjectMapper

namespace Portfolio\V2\Rest\Project;

use ZfcBase\Mapper\AbstractDbMapper;
use Zend\Paginator\Adapter\DbSelect;
use Zend\Db\ResultSet\HydratingResultSet;

class ProjectMapper extends AbstractDbMapper {

    ...

    /**
     * Provides a collection of all the available projects.
     *
     * @return \Portfolio\V2\Rest\Project\ProjectCollection
     */
    public function findAll() {
        $resultSetPrototype = new HydratingResultSet(
            $this->getHydrator(),
            $this->getEntityPrototype()
        );
        $select = $this->getSelect();
        $adapter = $this->getDbAdapter();
        $paginatorAdapter = new DbSelect($select, $adapter, $resultSetPrototype);
        $collection = new ProjectCollection($paginatorAdapter);
        return $collection;
    }

    /**
     * Provides a project by ID.
     *
     * @param int $id
     * @return \Portfolio\V2\Rest\Project\ProjectEntity
     */
    public function findById($id) {
        $select = $this->getSelect();
        $select->where(array(
            'id' => $id,
        ));
        $entity = $this->select($select)->current();
        return $entity;
    }

    ...

}

正如我在GitHub上的帖子中已经说过的,从Apigility核心团队得到某人的反馈非常好,而该解决方案是"Apigility兼容"的,如果不是,那么什么是更好/正确的"解决方案.

As I already said in my post on GitHub, it would be great to get a feedback from someone from the Apigility core team, wheter this solution is "Apigility conform" and, if not, what is a better/"correct" solution.

这篇关于如何使用基于ZfcBase-DbMapper的模型在Apigility驱动的应用程序中构建嵌套响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆