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

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

我正在开发 Apigility driven application based on the Zend Framework 2

目前,我正在将数据库中检索到的数据直接发送给客户端:收到请求后,MyResource#fetch(...)MyResource#fetchAll(...) 被触发并在 [=13= 上调用适当的方法] class,它调用 MyMapper 以使用 findFooByBar(...).

等方法退出数据

现在我想在发送响应之前处理数据。我该怎么做?


Apigility ZF HAL documentation 显示,如何访问它之间的实体数据已被检索并发送到客户端。好吧,我试过了。对于这样的任务,它很丑陋并且有很多代码。而且......它不起作用。但是我想要 post 这里是我的尝试:

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

    ...

}

我认为使用 renderEntityrenderCollection 事件不是添加这种特定于资源的逻辑的正确位置。它更适合更一般的更改或附带的定制。

您可以将此逻辑添加到您的资源侦听器中。因此,在您的 fetchfetchAll 方法中,您可以在 MyResource class 中添加您当前在这些 onRenderEntityonRenderCollection 方法中添加的自定义代码.

所以像这样:

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

    /* ... */
}

一种可能的解决方案是处理 Hydrator 中的数据。因此,我们编写了一个自定义的 Hydrator class 并在其中使用嵌套对象和列表来丰富项目。它可能看起来像这样:

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

}

这不是一个很好的解决方案,因为模型/数据检索逻辑的一部分被移到了水化器中。但它有效。 is shown an implementation of this approach and here 是在 GitHub 上对此主题的讨论。

如果您正在使用 ClassMethods Hydrator 并且您的 Collection 扩展 \Zend\Paginator\Paginator 一个好的解决方案是在不丢失集合的一致性并且不更改任何人的代码的情况下覆盖您的 getCurrentItems() 方法。

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

我将密钥命名为 something,以免与其他地方的 getValue 方法混淆。

这使得 something 值看起来像 [something]