From cb2b97995789d1ffc8139894f19af4bdedb91313 Mon Sep 17 00:00:00 2001 From: Romain Date: Mon, 16 Jul 2018 22:15:32 +0200 Subject: [PATCH] Format to JSONAPI --- EventListener/ResponseListener.php | 84 ++++++++++++++++++++++++++++++ README.md | 1 + Resources/config/services.yml | 5 ++ Transformer/EntityTransformer.php | 57 ++++++++++++++++++++ composer.json | 3 +- composer.lock | 66 ++++++++++++++++++++++- 6 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 EventListener/ResponseListener.php create mode 100644 Transformer/EntityTransformer.php diff --git a/EventListener/ResponseListener.php b/EventListener/ResponseListener.php new file mode 100644 index 0000000..efcff58 --- /dev/null +++ b/EventListener/ResponseListener.php @@ -0,0 +1,84 @@ +getResponse()->getStatusCode() + || 'application/vnd.api+json' !== $event->getRequest()->headers->get('Content-Type') + ) { + return; + } + + $content = json_decode($event->getResponse()->getContent(), 1); + + $fractal = new Manager(); + $includes = []; + $inclusion = $event->getRequest()->get('include'); + + if ($inclusion) { + $fractal->parseIncludes($inclusion); + $includes = explode(',', $inclusion); + } + + $fractal->setSerializer(new JsonApiSerializer('')); + $resource = $this->getResource($content, $includes, $this->guessType($event->getRequest())); + $data = $fractal->createData($resource)->toArray(); + + if ($resource instanceof Collection) { + $data['links'] = $content['_links']; + } + + $event->getResponse()->setContent(json_encode($data)); + } + + /** + * @param $content + * @param $includes + * @param $type + * @return Collection|Item + */ + private function getResource($content, $includes, $type) + { + if (isset($content['id'])) { + return new Item($content, new EntityTransformer($includes), $type); + } + + return new Collection($content['_embedded'], new EntityTransformer($includes), $type); + } + + /** + * Guess an entityType from the route path. + * Doesn't seem very reliable... (subresources & stuff like that) + * + * @param Request $request + * @return bool|string + */ + private function guessType(Request $request) + { + $path = $request->getPathInfo(); + + $enPosition = strpos($path,'/', 1); + + return substr($path, 1, $enPosition ? $enPosition - 2 : strlen($path) - 2); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 9d7dc5e..e3e37d7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ new Bigz\HalapiBundle\BigzHalapiBundle(), # Usage It will hook in JmsSerializer and add HATEOAS data before serialization. +*Experimental* If the header Content-Type: application/vnd.api+json is specified, it will transform the output to JSONApi # Further Documentation Have a look at http://github.com/Bigz/Halapi diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 386db1b..1107d2b 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -44,3 +44,8 @@ services: arguments: ["@bigz_halapi.relation_factory"] tags: - { name: jms_serializer.event_subscriber } + + bigz_halapi.jsonapi_transformer: + class: Bigz\HalapiBundle\EventListener\ResponseListener + tags: + - { name: kernel.event_listener, event: kernel.response } diff --git a/Transformer/EntityTransformer.php b/Transformer/EntityTransformer.php new file mode 100644 index 0000000..e33e55d --- /dev/null +++ b/Transformer/EntityTransformer.php @@ -0,0 +1,57 @@ +availableIncludes = $includes; + } + + /** + * Turn this item object into a generic array + * + * @return array + */ + public function transform($toFormat) + { + $data = $toFormat; + + if (isset($data['_links'])) { + $data['links'] = []; + + if (isset($data['_links']['self'])) { + $data['links']['self'] = $data['_links']['self']; + } + if (isset($data['_links']['next'])) { + $data['links']['next'] = $data['_links']['next']; + } + + unset($data['_links']); + } + + unset($data['_embedded']); + return $data; + } + + public function __call($name, $arguments) + { + if (strpos($name, 'include') === 0) { + $shortName = strtolower(substr($name, 7, strlen($name) - 7)); + $relation = $arguments[0]['_embedded'][$shortName]; + if (isset($relation['id'])) { + return $this->item($relation, new EntityTransformer($this->availableIncludes), $shortName); + } + + return $this->collection( + $relation, + new EntityTransformer($this->availableIncludes), + substr($shortName, 0, strlen($shortName) - 1) // singular + ); + + } + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 479a3d5..9414115 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "bigz/halapi": "^0.2", "symfony/psr-http-message-bridge": "^1.0", "pagerfanta/pagerfanta": "^1.0", - "zendframework/zend-diactoros": "^1.8" + "zendframework/zend-diactoros": "^1.8", + "league/fractal": "^0.17.0" }, "require-dev": { "symfony/expression-language": "~2.4 || ~3.0", diff --git a/composer.lock b/composer.lock index 43aaa18..1999d47 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "3b5383f33e61057ef1e4540dc04e3762", + "content-hash": "93f2c739808a25406b2eed59358b67ca", "packages": [ { "name": "bigz/halapi", @@ -923,6 +923,70 @@ ], "time": "2018-06-01T12:10:12+00:00" }, + { + "name": "league/fractal", + "version": "0.17.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/fractal.git", + "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/a0b350824f22fc2fdde2500ce9d6851a3f275b0e", + "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "doctrine/orm": "^2.5", + "illuminate/contracts": "~5.0", + "mockery/mockery": "~0.9", + "pagerfanta/pagerfanta": "~1.0.0", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5", + "zendframework/zend-paginator": "~2.3" + }, + "suggest": { + "illuminate/pagination": "The Illuminate Pagination component.", + "pagerfanta/pagerfanta": "Pagerfanta Paginator", + "zendframework/zend-paginator": "Zend Framework Paginator" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.13-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Fractal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Sturgeon", + "email": "me@philsturgeon.uk", + "homepage": "http://philsturgeon.uk/", + "role": "Developer" + } + ], + "description": "Handle the output of complex data structures ready for API output.", + "homepage": "http://fractal.thephpleague.com/", + "keywords": [ + "api", + "json", + "league", + "rest" + ], + "time": "2017-06-12T11:04:56+00:00" + }, { "name": "pagerfanta/pagerfanta", "version": "v1.1.0",