SandboxResponseManager::existsInQuery()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 2
nc 3
nop 5
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace danrevah\SandboxBundle\Managers;
3
4
use danrevah\SandboxBundle\Enum\ApiSandboxResponseTypeEnum;
5
use Doctrine\Common\Annotations\AnnotationReader;
6
use Doctrine\Common\Collections\ArrayCollection;
7
use ReflectionMethod;
8
use RuntimeException;
9
use Symfony\Component\HttpFoundation\JsonResponse;
10
use Symfony\Component\HttpFoundation\ParameterBag;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpKernel\KernelInterface;
13
14
class SandboxResponseManager {
15
16
    /**
17
     * @var \Symfony\Component\HttpKernel\KernelInterface
18
     */
19
    private $kernel;
20
    /**
21
     * @var \Doctrine\Common\Annotations\AnnotationReader
22
     */
23
    private $annotationsReader;
24
    private $forceMode;
25
26
    /**
27
     * @param KernelInterface $kernel
28
     * @param $forceMode
29
     * @param \Doctrine\Common\Annotations\AnnotationReader $annotationsReader
30
     */
31
    public function __construct($kernel, $forceMode, AnnotationReader $annotationsReader)
32
    {
33
        $this->kernel = $kernel;
34
        $this->annotationsReader = $annotationsReader;
35
        $this->forceMode = $forceMode;
36
    }
37
38
    /**
39
     * Getting a response controller by Annotations
40
     *
41
     * @param $object
42
     * @param String $method
43
     * @param ParameterBag $request
44
     * @param ParameterBag $query
45
     * @param ArrayCollection $rawRequest
46
     * @return callable
47
     * @throws \RuntimeException
48
     * @throws \Exception
49
     */
50
    public function getResponseController(
51
        $object,
52
        $method,
53
        ParameterBag $request,
54
        ParameterBag $query,
55
        ArrayCollection $rawRequest
56
    ) {
57
        $reader = $this->annotationsReader;
58
        $reflectionMethod = new ReflectionMethod($object, $method);
59
60
        // Step [1] - Single Response Annotation
61
        /** @var \StdClass $apiResponseAnnotation */
62
        $apiResponseAnnotation = $reader->getMethodAnnotation(
63
            $reflectionMethod,
64
            'danrevah\SandboxBundle\Annotation\ApiSandboxResponse'
65
        );
66
67
        /** @var \StdClass $apiResponseMultiAnnotation */
68
        $apiResponseMultiAnnotation = $reader->getMethodAnnotation(
69
            $reflectionMethod,
70
            'danrevah\SandboxBundle\Annotation\ApiSandboxMultiResponse'
71
        );
72
73
        if ( ! $apiResponseAnnotation && ! $apiResponseMultiAnnotation) {
74
            // Disabled exception, continue to real controller
75
            $forceMode = $this->forceMode;
76
77
            if ($forceMode) {
78
                throw new \Exception(sprintf(
79
                    'Entity class %s does not have required annotation ApiSandboxResponse or ApiResponseMultiAnnotation',
80
                    get_class($object)
81
                ));
82
            } else {
83
                // Fall back to the REAL Controller
84
                return false;
85
            }
86
        }
87
88
        $parameters = $apiResponseAnnotation ? $apiResponseAnnotation->parameters : $apiResponseMultiAnnotation->parameters;
89
90
        // Validating with Annotation syntax
91
        $this->validateParamsArray($parameters, $rawRequest, $request, $query);
92
93
        // Single response annotation is checked first
94
        if ($apiResponseAnnotation) {
95
            $responsePath = $apiResponseAnnotation->resource;
96
            $type = $apiResponseAnnotation->type;
97
            $statusCode = $apiResponseAnnotation->responseCode;
98
        } else {
99
            // Get response
100
            list($responsePath, $type, $statusCode) = $this->getResource(
101
                $apiResponseMultiAnnotation,
102
                $rawRequest,
103
                $request,
104
                $query
105
            );
106
        }
107
108
        if ($responsePath === null && $apiResponseMultiAnnotation) {
109
            list($type, $statusCode, $responsePath) = $this->extractRealParams($apiResponseMultiAnnotation, $type, $statusCode);
110
        }
111
112
        list($controller, $content) = $this->getControllerResponseByResource($responsePath, $type, $statusCode);
113
114
        return [$controller, $content, $type, $statusCode];
115
    }
116
117
    /**
118
     * @param \StdClass $apiResponseMultiAnnotation
119
     * @param $streamParams
120
     * @param $request
121
     * @param $query
122
     * @throws \RuntimeException
123
     * @return array
124
     */
125
    private function getResource(
126
        \StdClass $apiResponseMultiAnnotation,
127
        ArrayCollection $streamParams,
128
        ParameterBag $request,
129
        ParameterBag $query
130
    ) {
131
        // parent type, and responseCode
132
        $type = $apiResponseMultiAnnotation->type;
133
        $responseCode = $apiResponseMultiAnnotation->responseCode;
134
        $resourcePath = null;
135
136
        foreach ($apiResponseMultiAnnotation->multiResponse as $resource) {
137
138
            if ( ! isset($resource['caseParams']) || ! isset($resource['resource'])) {
139
                throw new RunTimeException('Each multi response must have caseParams and resource property');
140
            }
141
142
            $validateCaseParams = $this->countCaseParamsFromQuery($streamParams, $request, $query, $resource);
143
144
            // Found a match route params
145
            if (count($resource['caseParams']) == $validateCaseParams) {
146
                list($type, $responseCode, $resourcePath) = $this->extractResource($resource, $type, $responseCode);
147
                // If found route break loop
148
                break;
149
            }
150
        }
151
152
        return [$resourcePath, $type, $responseCode];
153
    }
154
155
    /**
156
     * @param $apiDocParams
157
     * @param $rawRequest
158
     * @param $request
159
     * @param $query
160
     * @throws \InvalidArgumentException
161
     */
162
    private function validateParamsArray(
163
        $apiDocParams,
164
        ArrayCollection $rawRequest,
165
        ParameterBag $request,
166
        ParameterBag $query
167
    ) {
168
        // search for missing required parameters and throw exception if there's anything missing
169
        foreach ($apiDocParams as $options) {
170
            // Validating if required parameters are missing
171
            if (array_key_exists('required', $options) && $options['required'] &&
172
                ( ! $request->has($options['name']) && ! $query->has($options['name']) &&
173
                    ! $rawRequest->containsKey($options['name']))
174
            ) {
175
                throw new \InvalidArgumentException('Missing parameters');
176
            }
177
        }
178
    }
179
180
    /**
181
     * @param $responsePath
182
     * @param $type
183
     * @param $statusCode
184
     * @return callable
185
     * @throws \RuntimeException
186
     */
187
    private function getControllerResponseByResource($responsePath, $type, $statusCode)
188
    {
189
        $path = $this->kernel->locateResource($responsePath);
190
        $content = file_get_contents($path);
191
192
        // Override controller with fake response
193
        switch (strtolower($type)) {
194
            // JSON
195
            case ApiSandboxResponseTypeEnum::JSON_RESPONSE:
196
                $content = json_decode($content, 1);
197
198
                $controller = function() use ($content, $statusCode) {
199
                    return new JsonResponse($content, $statusCode);
200
                };
201
                break;
202
203
            // XML
204
            case ApiSandboxResponseTypeEnum::XML_RESPONSE:
205
                $controller = function() use ($content, $statusCode) {
206
                    $response = new Response($content, $statusCode);
207
                    $response->headers->set('Content-Type', 'text/xml');
208
                    return $response;
209
                };
210
                break;
211
212
            // Unknown
213
            default:
214
               throw new RuntimeException('Unknown type of SandboxApiResponse');
215
        }
216
        return [$controller, $content];
217
    }
218
219
    /**
220
     * @param \StdClass $apiResponseMultiAnnotation
221
     * @param $type
222
     * @param $statusCode
223
     * @throws \RuntimeException
224
     * @return array
225
     */
226
    private function extractRealParams(\StdClass $apiResponseMultiAnnotation, $type, $statusCode)
227
    {
228
        // If didn't find route path, fall to responseFallback
229
        if (empty($apiResponseMultiAnnotation->responseFallback) || ! isset($apiResponseMultiAnnotation->responseFallback['resource'])) {
230
            throw new RuntimeException('Missing `responseFallback` is not set properly in the Sandbox annotation');
231
        }
232
233
        return array(
234
            isset($apiResponseMultiAnnotation->responseFallback['type']) ? $apiResponseMultiAnnotation->responseFallback['type'] : $type,
235
            isset($apiResponseMultiAnnotation->responseFallback['responseCode']) ? $apiResponseMultiAnnotation->responseFallback['responseCode'] : $statusCode,
236
            $apiResponseMultiAnnotation->responseFallback['resource']
237
        );
238
    }
239
240
    /**
241
     * @param ArrayCollection $streamParams
242
     * @param ParameterBag $request
243
     * @param ParameterBag $query
244
     * @param $resource
245
     * @return int
246
     */
247
    private function countCaseParamsFromQuery(ArrayCollection $streamParams, ParameterBag $request, ParameterBag $query, $resource)
248
    {
249
        $validateCaseParams = 0;
250
251
        // Validate Params with GET, POST, and RAW
252
        foreach ($resource['caseParams'] as $paramName => $paramValue) {
253
            if ($this->existsInQuery($paramName, $paramValue, $streamParams, $request, $query)) {
254
                $validateCaseParams++;
255
            }
256
        }
257
        return $validateCaseParams;
258
    }
259
260
    /**
261
     * @param $paramName
262
     * @param $paramValue
263
     * @param $streamParams
264
     * @param $request
265
     * @param $query
266
     * @return bool
267
     */
268
    private function existsInQuery($paramName, $paramValue, ArrayCollection $streamParams, ParameterBag $request, ParameterBag $query)
269
    {
270
        return ($query->get($paramName) == $paramValue) || $request->get($paramName) == $paramValue || $streamParams->get($paramName) == $paramValue;
271
    }
272
273
    /**
274
     * @param $resource
275
     * @param $type
276
     * @param $responseCode
277
     * @return array
278
     */
279
    private function extractResource($resource, $type, $responseCode)
280
    {
281
        // Override parent type if has child type
282
        if (isset($resource['type'])) {
283
            $type = $resource['type'];
284
        }
285
        if (isset($resource['responseCode'])) {
286
            $responseCode = $resource['responseCode'];
287
        }
288
        $resourcePath = $resource['resource'];
289
        return array($type, $responseCode, $resourcePath);
290
    }
291
}
292