Search code examples
phpcollectionszend-framework2laminas-api-tools

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.

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

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

    ...

}

Solution

  • One possible solution is handling the data in the Hydrator. So we write a custom Hydrator class and enrich the items with nested objects and lists in it. 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.