Passed
Push — master ( fb4950...253eda )
by Michael
02:31
created

JsonApiViewListener::handleDocumentLinks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 14
cts 14
cp 1
rs 9.2
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 2
crap 2
1
<?php
2
declare(strict_types = 1);
3
4
namespace Mikemirten\Bundle\JsonApiBundle\EventListener;
5
6
use Mikemirten\Bundle\JsonApiBundle\ObjectHandler\ObjectHandlerInterface;
7
use Mikemirten\Bundle\JsonApiBundle\Response\AbstractJsonApiView;
8
use Mikemirten\Bundle\JsonApiBundle\Response\Behaviour\HttpAttributesAwareInterface;
9
use Mikemirten\Bundle\JsonApiBundle\Response\Behaviour\IncludedObjectsAwareInterface;
10
use Mikemirten\Bundle\JsonApiBundle\Response\JsonApiDocumentView;
11
use Mikemirten\Bundle\JsonApiBundle\Response\JsonApiIteratorView;
12
use Mikemirten\Bundle\JsonApiBundle\Response\JsonApiObjectView;
13
use Mikemirten\Component\JsonApi\Document\AbstractDocument;
14
use Mikemirten\Component\JsonApi\Document\ErrorObject;
15
use Mikemirten\Component\JsonApi\Document\JsonApiObject;
16
use Mikemirten\Component\JsonApi\Document\LinkObject;
17
use Mikemirten\Component\JsonApi\Document\NoDataDocument;
18
use Mikemirten\Component\JsonApi\Document\ResourceCollectionDocument;
19
use Mikemirten\Component\JsonApi\Document\ResourceObject;
20
use Mikemirten\Component\JsonApi\Document\SingleResourceDocument;
21
use Mikemirten\Component\JsonApi\Mapper\Handler\LinkRepository\RepositoryProvider;
22
use Symfony\Component\HttpFoundation\Response;
23
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
24
25
/**
26
 * JsonApi view listener
27
 * Handles a JsonApi-document or its part responded by controller
28
 *
29
 * @package Mikemirten\Bundle\JsonApiBundle\EventListener
30
 */
31
class JsonApiViewListener
32
{
33
    /**
34
     * Link-repositories provider
35
     *
36
     * @var RepositoryProvider
37
     */
38
    protected $linkRepositoryProvider;
39
40
    /**
41
     * Object-handlers
42
     *
43
     * @var ObjectHandlerInterface[]
44
     */
45
    protected $objectHandlers = [];
46
47
    /**
48
     * Object-handlers resolved by supported class
49
     *
50
     * @var ObjectHandlerInterface[]
51
     */
52
    protected $resolvedObjectHandlers = [];
53
54
    /**
55
     * JsonApiViewListener constructor.
56
     *
57
     * @param RepositoryProvider $linkRepositoryProvider
58
     */
59 14
    public function __construct(RepositoryProvider $linkRepositoryProvider)
60
    {
61 14
        $this->linkRepositoryProvider = $linkRepositoryProvider;
62 14
    }
63
64
    /**
65
     * Add object-handler
66
     *
67
     * @param ObjectHandlerInterface $handler
68
     */
69 3
    public function addObjectHandler(ObjectHandlerInterface $handler)
70
    {
71 3
        $this->objectHandlers[] = $handler;
72 3
    }
73
74
    /**
75
     * On Kernel View event handler
76
     *
77
     * @param GetResponseForControllerResultEvent $event
78
     */
79 13
    public function onKernelView(GetResponseForControllerResultEvent $event)
80
    {
81 13
        $result = $event->getControllerResult();
82
83 13
        if (! is_object($result)) {
84 1
            return;
85
        }
86
87 12
        $response = $this->handleResult($result);
88
89 11
        if ($response !== null) {
90 11
            $event->setResponse($response);
91
        }
92 11
    }
93
94
    /**
95
     * Handle result
96
     *
97
     * @param  mixed $result
98
     * @return Response | null
99
     */
100 12
    protected function handleResult($result)
101
    {
102
        // Json API document
103 12
        if ($result instanceof AbstractDocument) {
104 1
            return $this->createResponse($result);
105
        }
106
107
        // Json API document wrapped into view
108 11
        if ($result instanceof JsonApiDocumentView) {
109 1
            return $this->handleDocumentView($result);
110
        }
111
112
        // An object for serialization wrapped into view
113 10
        if ($result instanceof JsonApiObjectView) {
114 5
            return $this->handleObjectView($result);
115
        }
116
117
        // An iterator of objects for serialization wrapped into view
118 5
        if ($result instanceof JsonApiIteratorView) {
119 3
            return $this->handleIteratorView($result);
120
        }
121
122
        // A resource-object of Json API document
123 2
        if ($result instanceof ResourceObject) {
124 1
            return $this->handleResource($result);
125
        }
126
127
        // An error-object of Json API document
128 1
        if ($result instanceof ErrorObject) {
129 1
            return $this->handleError($result);
130
        }
131
    }
132
133
    /**
134
     * Handle document Json API view
135
     *
136
     * @param  JsonApiDocumentView $view
137
     * @return Response
138
     */
139 1
    protected function handleDocumentView(JsonApiDocumentView $view)
140
    {
141 1
        $document = $view->getDocument();
142 1
        $response = $this->createResponse($document);
143
144 1
        $this->handleHttpAttributes($response, $view);
145 1
        return $response;
146
    }
147
148
    /**
149
     * Handle single object Json API view
150
     *
151
     * @param  JsonApiObjectView $view
152
     * @return Response
153
     */
154 5
    protected function handleObjectView(JsonApiObjectView $view): Response
155
    {
156 5
        $resource = $this->handleObject($view->getObject());
157 4
        $this->handleResourceCallback($view, $resource);
158
159 4
        $document = new SingleResourceDocument($resource);
160 4
        $document->setJsonApi(new JsonApiObject());
161
162 4
        $this->handleIncludedResources($document, $view);
163 4
        $this->handleDocumentLinks($view, $document);
164 4
        $this->handleDocumentCallback($view, $document);
165
166 4
        $response = $this->createResponse($document);
167
168 4
        $this->handleHttpAttributes($response, $view);
169 4
        return $response;
170
    }
171
172
    /**
173
     * Handle a callback after a resource-object has created.
174
     *
175
     * @param AbstractJsonApiView $view
176
     * @param ResourceObject      $resource
177
     */
178 7
    protected function handleResourceCallback(AbstractJsonApiView $view, ResourceObject $resource)
179
    {
180 7
        if ($view->hasResourceCallback()) {
181 2
            $callback = $view->getResourceCallback();
182 2
            $callback($resource);
183
        }
184 7
    }
185
186
    /**
187
     * Handle links of document
188
     *
189
     * @param AbstractJsonApiView $view
190
     * @param AbstractDocument    $document
191
     */
192 7
    protected function handleDocumentLinks(AbstractJsonApiView $view, AbstractDocument $document)
193
    {
194 7
        foreach ($view->getDocumentLinks() as $definition)
195
        {
196 1
            $repositoryName = $definition->getRepositoryName();
197 1
            $linkName       = $definition->getLinkName();
198 1
            $parameters     = $definition->getParameters();
199
200 1
            $linkData = $this->linkRepositoryProvider
201 1
                ->getRepository($repositoryName)
202 1
                ->getLink($linkName, $parameters);
203
204 1
            $metadata = array_replace(
205 1
                $linkData->getMetadata(),
206 1
                $definition->getMetadata()
207
            );
208
209 1
            $link = new LinkObject($linkData->getReference(), $metadata);
210
211 1
            $document->setLink($definition->getName(), $link);
212
        }
213 7
    }
214
215
    /**
216
     * Handle a callback after a document has created.
217
     *
218
     * @param AbstractJsonApiView $view
219
     * @param AbstractDocument    $document
220
     */
221 7
    protected function handleDocumentCallback(AbstractJsonApiView $view, AbstractDocument $document)
222
    {
223 7
        if ($view->hasDocumentCallback()) {
224 2
            $callback = $view->getDocumentCallback();
225 2
            $callback($document);
226
        }
227 7
    }
228
229
    /**
230
     * Handle supposed to be included to document resources
231
     *
232
     * @param AbstractDocument              $document
233
     * @param IncludedObjectsAwareInterface $view
234
     */
235 7
    protected function handleIncludedResources(AbstractDocument $document, IncludedObjectsAwareInterface $view)
236
    {
237 7
        foreach ($view->getIncludedObjects() as $object)
238
        {
239 2
            $resource = $this->handleObject($object);
240 2
            $document->addIncludedResource($resource);
241
        }
242 7
    }
243
244
    /**
245
     * Handle object-iterator
246
     *
247
     * @param  JsonApiIteratorView $view
248
     * @return Response
249
     */
250 3
    protected function handleIteratorView(JsonApiIteratorView $view): Response
251
    {
252 3
        $document = new ResourceCollectionDocument();
253 3
        $document->setJsonApi(new JsonApiObject());
254
255 3
        foreach ($view as $object)
256
        {
257 3
            $resource = $this->handleObject($object);
258 3
            $this->handleResourceCallback($view, $resource);
259
260 3
            $document->addResource($resource);
261
        }
262
263 3
        $this->handleIncludedResources($document, $view);
264 3
        $this->handleDocumentLinks($view, $document);
265 3
        $this->handleDocumentCallback($view, $document);
266
267 3
        $response = $this->createResponse($document);
268
269 3
        $this->handleHttpAttributes($response, $view);
270 3
        return $response;
271
    }
272
273
    /**
274
     * Handle response data besides of the document itself
275
     * 
276
     * @param Response                     $response
277
     * @param HttpAttributesAwareInterface $view
278
     */
279 8
    protected function handleHttpAttributes(Response $response, HttpAttributesAwareInterface $view)
280
    {
281 8
        $response->setStatusCode($view->getStatusCode());
282 8
        $response->headers->add($view->getHeaders());
283 8
    }
284
285
    /**
286
     * Handle object
287
     *
288
     * @param  $object
289
     * @return ResourceObject
290
     */
291 8
    protected function handleObject($object): ResourceObject
292
    {
293 8
        if ($object instanceof ResourceObject) {
294 5
            return $object;
295
        }
296
297 3
        $class = get_class($object);
298
299 3
        return $this->getHandler($class)->handle($object);
300
    }
301
302
    /**
303
     * Get handler supports given class
304
     *
305
     * @param  string $class
306
     * @return ObjectHandlerInterface
307
     * @throws \LogicException
308
     */
309 3
    protected function getHandler(string $class): ObjectHandlerInterface
310
    {
311 3
        if (isset($this->resolvedObjectHandlers[$class])) {
312 2
            return $this->resolvedObjectHandlers[$class];
313
        }
314
315 3
        foreach ($this->objectHandlers as $handler) {
316 2
            if ($handler->supports($class)) {
317 2
                $this->resolvedObjectHandlers[$class] = $handler;
318 2
                return $handler;
319
            }
320
        }
321
322 1
        throw new \LogicException(sprintf('Class "%s" is not supported by known handles.', $class));
323
    }
324
325
    /**
326
     * Handle single resource object
327
     *
328
     * @param  ResourceObject $resource
329
     * @return Response
330
     */
331 1
    protected function handleResource(ResourceObject $resource): Response
332
    {
333 1
        $document = new SingleResourceDocument($resource);
334 1
        $document->setJsonApi(new JsonApiObject());
335
336 1
        return $this->createResponse($document);
337
    }
338
339
    /**
340
     * Handle error
341
     *
342
     * @param  ErrorObject $error
343
     * @return Response
344
     */
345 1
    protected function handleError(ErrorObject $error): Response
346
    {
347 1
        $document = new NoDataDocument();
348 1
        $document->setJsonApi(new JsonApiObject());
349 1
        $document->addError($error);
350
351 1
        return $this->createResponse($document);
352
    }
353
354
    /**
355
     * Create response
356
     *
357
     * @param  AbstractDocument $document
358
     * @return Response
359
     */
360 11
    protected function createResponse(AbstractDocument $document): Response
361
    {
362 11
        $encoded  = $this->encode($document->toArray());
363 11
        $response = new Response($encoded);
364
365 11
        $response->headers->set('Content-Type', 'application/vnd.api+json');
366
367 11
        return $response;
368
    }
369
370
    /**
371
     * Encode object into a json-string
372
     *
373
     * @param  mixed $object
374
     * @return string
375
     * @throws \LogicException
376
     */
377 11
    protected function encode($object): string
378
    {
379 11
        $encoded = json_encode($object);
380
381 11
        if (json_last_error() === JSON_ERROR_NONE) {
382 11
            return $encoded;
383
        }
384
385
        throw new \LogicException('Encoding error: ' . json_last_error_msg());
386
    }
387
}