简体   繁体   English

在将数据发送给客户端之前,如何在Apigility中访问和操纵数据?

[英]How to access and manipulate data in Apigility before it is sent to the client?

I'm developing an Apigility driven application based on the Zend Framework 2 . 我正在开发基于Zend Framework 2Apigility驱动的应用程序。

Currently I'm sending the data retrieved in the database directly to the client: a request comes in, the MyResource#fetch(...) or MyResource#fetchAll(...) gets triggered and calls an appropriate method on MyService class, that calls MyMapper to retireve the data with its methods like findFooByBar(...) . 目前,我正在将数据库中检索到的数据直接发送到客户端:出现请求,触发MyResource#fetch(...)MyResource#fetchAll(...) ,并在MyService类上调用适当的方法,调用MyMapper以其诸如findFooByBar(...)方法findFooByBar(...)数据。

Now I'd like to process the data, before the response is sent. 现在,我想在发送响应之前处理数据。 How can I do that? 我怎样才能做到这一点?


The Apigility ZF HAL documentation shows, how to access the entity data between it has been retrieved and sent to the client. Apigility ZF HAL文档显示了如何检索已检索并发送到客户端之间的实体数据。 Well I tried this out. 好吧,我尝试了一下。 It's ugly and to much code for such task. 这样的任务很丑陋,而且代码很多。 And... it doesn't work. 而且...不起作用。 I want however post here my attept: 我想在这里张贴我的见解:

namespace Portfolio;

...

class Module implements ApigilityProviderInterface {

    private $serviceManager;

    public function onBootstrap(MvcEvent $event) {
        $application = $event->getTarget();
        $this->serviceManager = $serviceManager = $application->getServiceManager();
        $viewHelperManager = $serviceManager->get('ViewHelperManager');
        $hal = $viewHelperManager->get('Hal');
        $hal->getEventManager()->attach('renderEntity', array($this, 'onRenderEntity'));
        $hal->getEventManager()->attach('renderCollection', array($this, 'onRenderCollection'));
    }

    public function onRenderEntity($event) {
        $entity = $event->getParam('entity');
        if ($entity->entity instanceof ProjectEntity) {
            $projectEntity = $entity->entity;
            $imageCollection = $this->tempCreateimagesForProject(
                $event, $entity->entity->getId()
            );
            $projectEntity->setImages($imageCollection);
            $event->setParam('entity', $projectEntity);
        }
    }

    public function onRenderCollection($event) {
        $collection = $event->getParam('collection');
        $projectCollection = $collection->getCollection();
        if ($projectCollection instanceof ProjectCollection) {
            foreach ($projectCollection as $key => $projectItem) {
                $tempProject = $projectCollection->getItem($key);
                $tempProject->append(
                    ['images' => $this->tempCreateimagesForProject($tempProject->offsetGet('id'))]
                );
                $projectCollection->getItem($key)->offsetSet($key, $tempProject);
            }
        }
    }

    private function tempCreateimagesForProject(Event $event, $projectId) {
        $imageService = $this->serviceManager->get('Portfolio\V2\Rest\ImageService');
        $imageCollection = $imageService->getImagesForProject($projectId);
        return $imageCollection;
    }

    ...

}

I think using the renderEntity and renderCollection events is not the correct spot to add this kind of resource specific logic. 我认为使用renderEntityrenderCollection事件不是添加这种特定于资源的逻辑的正确位置。 It is more suitable for more general changes or incidental customization. 它更适合更一般的更改或附带的自定义。

You can add this logic to your resource listeners. 您可以将此逻辑添加到资源侦听器。 So in your fetch and fetchAll methods in your MyResource class you can add the custom code you currently added in these onRenderEntity and onRenderCollection methods. 因此,在MyResource类的fetchfetchAll方法中,可以添加当前在这些onRenderEntityonRenderCollection方法中添加的自定义代码。

So something like this: 所以像这样:

class MyResource extends AbstractResourceListener
{
    /**
     * Your image service dependency
     */
    protected $imageService;

    /* ... */

    public function fetch($id)
    {
        $project = $this->projectMapper->fetch($id);

        $imageCollection = $this->imageService->getImagesForProject($project);

        $project->setImages($imageCollection);
        return $project;
    }

    /* ... */

    public function fetchAll($params = array())
    {
        $projects = $this->projectMapper->fetchAll();

        foreach ($projects as $key => $project) {
            $imageCollection = $this->imageService->getImagesForProject($project);
            $project->setImages($imageCollection);
        }

        return $projects;
    }

    /* ... */
}

One possible solution is handling the data in the Hydrator. 一种可能的解决方案是在Hydrator中处理数据。 So we write a custom Hydrator class and enrich the items with nested objects and lists in it. 因此,我们编写了一个自定义的Hydrator类,并在其中添加了嵌套对象和列表来丰富项目。 It can look like this: 它看起来可能像这样:

Portfolio\\V2\\Rest\\Project\\ProjectHydrator

...

class ProjectHydrator extends ClassMethods {

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

    ...

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

}

It's not a nice solution, since then a part of the model / data retrieving logic gets moved to the hydrator. 这不是一个很好的解决方案,因为那之后一部分模型/数据检索逻辑被移到了水化器上。 But it works. 但这有效。 Here is shown an implementation of this approach and here is a discussion to this topic on GitHub. 这里显示这种方法的实施, 这里是GitHub上这个话题的讨论。

If you are using the ClassMethods Hydrator and your Collection extends \\Zend\\Paginator\\Paginator a good solution without losing the Collection's consistency and not changing anybody's code is to overwrite your getCurrentItems() method. 如果您使用的是ClassMethods并且您的Collection扩展了\\Zend\\Paginator\\Paginator一个不错的解决方案是覆盖getCurrentItems()方法,而不会丢失Collection的一致性,并且不更改任何人的代码。

public class MyResourceCollection // most likely extends Paginator
{
    public function getCurrentItems()
    {
        // getting the original items
        $items = parent::getCurrentItems();
        // as we work with objects $item will be an object
        // when working with objects we use references to them not clones of objects
        // so changes to $item are also present in the collection
        foreach ($collection as $item) {
            $oldSomething = $item->getSomething();
            $item->setSomething('['.$oldSomething.']');
        }
        // $items are now changed, return them
        return $items;
    }
}

I have named the key something not to get confused with the getValue method from other places. 我已经为密钥命名了something ,不要与其他地方的getValue方法混淆。

This makes the something value look like [something] . 这使something值看起来像[something]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM