Passed
Push — master ( 041130...0cf240 )
by Michael
02:37
created

JsonApiViewListener::handleDocumentCallback()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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