ApiResponseListener::isApiRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace ApiBundle\EventListener;
4
5
use Psr\Log\LoggerInterface;
6
use Symfony\Component\HttpFoundation\ParameterBag;
7
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
8
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
9
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
10
use Symfony\Component\HttpFoundation\JsonResponse;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\Request;
13
use Symfony\Component\HttpKernel\Exception\HttpException;
14
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
15
use Symfony\Component\Security\Core\Exception\AuthenticationException;
16
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
17
use Symfony\Component\Translation\TranslatorInterface;
18
use JsonSerializable;
19
20
class ApiResponseListener
21
{
22
    private $logger;
23
    private $environment;
24
25
    /**
26
     * @var TranslatorInterface
27
     */
28
    private $translator;
29
30
    public function __construct($environment, TranslatorInterface $translator, LoggerInterface $logger = null)
31
    {
32
        $this->environment = $environment;
33
        $this->logger = $logger;
34
        $this->translator = $translator;
35
    }
36
37
    protected function isApiRequest(Request $request)
38
    {
39
        // @NOTE: you should identify request whether it is api or not
40
        // in this case it has prefix /api
41
        return strpos($request->getRequestUri(), '/api') === 0;
42
    }
43
44
    public function onKernelRequest(GetResponseEvent $event)
45
    {
46
        $request = $event->getRequest();
47
        if (!$this->isApiRequest($request)) {
48
            return;
49
        }
50
51
        // request language
52
        if ($request->headers->has('Language')) {
53
            $this->translator->setLocale($request->headers->get('Language'));
54
            $request->setLocale($request->headers->get('Language'));
55
        }
56
57
        // request content type
58
        if ($type = $request->getContentType()) {
59
            switch ($type) {
60
            case 'json':
61
                $request->setRequestFormat('json');
62
                break;
63
            default:
64
                $mime = $request->headers->get('Content-Type');
65
                throw new HttpException(406, "The content type: \"{$type}\" specified as mime \"{$mime}\" - is not supported.");
66
            }
67
        } else {
68
            // default format is JSON
69
            $request->setRequestFormat('json');
70
        }
71
72
        // request accept content type, currently only JSON
73
        $accepts = $request->getAcceptableContentTypes();
74
        $types = array_filter(array_unique(array_map([$request, 'getFormat'], $accepts)));
75
76
        if ($types && !in_array('json', $types, true)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
77
            $acceptable = implode(',', $accepts);
78
            throw new HttpException(406, "None of acceptable content types: {$acceptable} are supported.");
79
        }
80
81
        // if there is a body, decode it currently as JSON only
82
        if ($content = $request->getContent()) {
83
            $data = @json_decode($content, true);
84
85
            if (null === $data) {
86
                // the error may be important, log it
87
                if (null !== $this->logger) {
88
                    $this->logger->error("Failed to parse json request content, err: " . json_last_error_msg());
89
                }
90
91
                throw new HttpException(400, "The given content is not a valid json.");
92
            }
93
            $request->request = new ParameterBag($data);
94
        }
95
    }
96
97
    public function onKernelView(GetResponseForControllerResultEvent $event)
98
    {
99
        $request = $event->getRequest();
100
        if (!$this->isApiRequest($request)) {
101
            return;
102
        }
103
        // we now only use json, if more formats will be added, then it can check request uri or headers
104
        // and only presenter object allowed as controller result etc
105
        $data = $event->getControllerResult();
106
107
        switch (true) {
108
        case is_array($data):
109
        case $data instanceof JsonSerializable:
110
            $response = new JsonResponse($data);
111
            break;
112
        case is_string($data):
113
            $response = new Response($data);
114
            $response->headers->set('Content-Type', 'application/json');
115
            break;
116
        default:
117
            throw new \UnexpectedValueException("Response type: " . gettype($data) . " from controller was not expected.");
118
        }
119
120
        $response->headers->set('Language', $this->translator->getLocale());
121
        $event->setResponse($response);
122
    }
123
124
    public function onKernelException(GetResponseForExceptionEvent $event)
125
    {
126
        $request = $event->getRequest();
127
        if (!$this->isApiRequest($request)) {
128
            return;
129
        }
130
        // handle only api scope
131
        $error = [
132
            'code' => JsonResponse::HTTP_INTERNAL_SERVER_ERROR,
133
            'message' => "You've come across an application error. Our support team will be receiving this error shortly.",
134
        ];
135
136
        $exception = $event->getException();
137
138
        if ($exception instanceof AccessDeniedHttpException) {
139
            $error['code'] = $exception->getStatusCode();
140
            $error['message'] = "Your account does not have the required roles. To access this resource.";
141
        } elseif ($exception instanceof AuthenticationException) {
142
            $error['code'] = JsonResponse::HTTP_UNAUTHORIZED;
143
            $error['message'] = "Authentication is required.";
144
        } elseif ($exception instanceof HttpException) {
145
            $error['code'] = $exception->getStatusCode();
146
            $error['message'] = $exception->getMessage();
147
        } elseif (in_array($this->environment, ['dev'])) {
148
            $error['message'] = $exception->getMessage();
149
        }
150
151
        if ($error['code'] >= 500) {
152
            $this->logger->error($exception);
153
        } else {
154
            $this->logger->debug($exception);
155
        }
156
157
        $event->setResponse(new JsonResponse(compact('error'), $error['code']));
158
    }
159
}
160