automatix automatix - 4 years ago 129
PHP Question

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

...

}

Answer Source

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download