RestApiControllerTrait   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 25
c 0
b 0
f 0
lcom 0
cbo 12
dl 0
loc 203
ccs 0
cts 110
cp 0
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
B createJsonResponse() 0 16 5
A createJsonNoContentResponse() 0 7 1
B createJsonBadRequestResponse() 0 23 4
B getRequestData() 0 30 6
A assertSubmitedFormIsValid() 0 14 2
A assertIsGrantedOr403() 0 10 3
B forwardAction() 0 24 4
1
<?php
2
3
namespace Majora\Bundle\FrameworkExtraBundle\Controller;
4
5
use Majora\Framework\Validation\ValidationException;
6
use Symfony\Component\DependencyInjection\ContainerInterface;
7
use Symfony\Component\Form\FormError;
8
use Symfony\Component\Form\FormInterface;
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpFoundation\Response;
11
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
12
use Symfony\Component\HttpKernel\Exception\HttpException;
13
use Symfony\Component\HttpKernel\HttpKernelInterface;
14
15
/**
16
 * Base trait for REST APIs entity controllers traits.
17
 *
18
 * @property ContainerInterface $container
19
 */
20
trait RestApiControllerTrait
21
{
22
    use ControllerTrait;
23
24
    /**
25
     * Create a JsonResponse with given data, if object given, it will be serialize
26
     * with registered serializer.
27
     *
28
     * @param mixed  $data
29
     * @param string $scope
30
     * @param int    $status
31
     *
32
     * @return Response
33
     */
34
    protected function createJsonResponse($data = null, $scope = null, $status = 200)
35
    {
36
        if ($data !== null) {
37
            $data = is_string($data) ?
38
                $data :
39
                $this->container->get('serializer')->serialize(
40
                    $data, 'json', empty($scope) ? array() : array('scope' => $scope)
41
                )
42
            ;
43
        }
44
45
        $response = new Response(null === $data ? '' : $data, $status);
46
        $response->headers->set('Content-Type', 'application/json');
47
48
        return $response;
49
    }
50
51
    /**
52
     * build and return a non content response.
53
     *
54
     * @return JsonResponse
55
     */
56
    protected function createJsonNoContentResponse()
57
    {
58
        $response = new Response(null, 204);
59
        $response->headers->set('Content-Type', null);
60
61
        return $response;
62
    }
63
64
    /**
65
     * create and returns a 400 Bad Request response.
66
     *
67
     * @param array $errors
68
     *
69
     * @return JsonResponse
70
     */
71
    protected function createJsonBadRequestResponse(array $errors = array())
72
    {
73
        // try to extract proper validation errors
74
        foreach ($errors as $key => $error) {
75
            if (!$error instanceof FormError) {
76
                continue;
77
            }
78
            $errors['errors'][$key] = array(
79
                'message'    => $error->getMessage(),
80
                'parameters' => $error->getMessageParameters(),
81
            );
82
            unset($errors[$key]);
83
        }
84
85
        $response = new Response(
86
            is_string($errors) ? $errors : json_encode($errors),
87
            400
88
        );
89
90
        $response->headers->set('Content-Type', 'application/json');
91
92
        return $response;
93
    }
94
95
    /**
96
     * Retrieve given request data depending on its content type.
97
     *
98
     * @param Request $request
99
     * @param string  $inflection optional inflector to tuse on parameter keys
100
     *
101
     * @return array
102
     *
103
     * @throws HttpException if JSON content-type and invalid JSON data
104
     */
105
    protected function getRequestData(Request $request, $inflection = 'camelize')
106
    {
107
        switch ($request->headers->get('content-type')) {
108
109
            case 'application/json':
110
                if (!($data = @json_decode($request->getContent(), true))
111
                    && ($error = json_last_error()) != JSON_ERROR_NONE
112
                ) {
113
                    throw new HttpException(400, sprintf(
114
                        'Invalid submitted json data, error %s : %s',
115
                        $error,
116
                        json_last_error_msg()
117
                    ));
118
                }
119
120
                break;
121
            default:
122
                $data = array_replace_recursive(
123
                    $request->request->all(),
124
                    $request->files->all()
125
                );
126
                break;
127
        }
128
129
        // data camel case normalization
130
        return $inflection && $this->container->has('majora.inflector')?
131
            $this->container->get('majora.inflector')->normalize($data, $inflection) :
132
            $data
133
        ;
134
    }
135
136
    /**
137
     * Custom method for form submission to handle http method bugs, and extra fields
138
     * error options.
139
     *
140
     * @param Request       $request
141
     * @param FormInterface $form
142
     *
143
     * @throws HttpException       if invalid json data
144
     * @throws ValidationException if invalid form
145
     */
146
    protected function assertSubmitedFormIsValid(Request $request, FormInterface $form)
147
    {
148
        $form->submit(
149
            $this->getRequestData($request),
150
            $request->getMethod() !== 'PATCH'
151
        );
152
153
        if (!$valid = $form->isValid()) {
154
            throw new ValidationException(
155
                $form->getData(),
156
                $form->getErrors(true, true) // deep & flattened
157
            );
158
        }
159
    }
160
161
    /**
162
     * verify if intention on given resource (request if undefined) is granted
163
     *
164
     * @param string $intention
165
     * @param mixed  $resource
166
     *
167
     * @throws AccessDeniedHttpException if denied
168
     */
169
    protected function assertIsGrantedOr403($intention, $resource = null)
170
    {
171
        if (!$this->checkSecurity($intention, $resource)) {
172
            throw new AccessDeniedHttpException(sprintf(
173
                'Access denied while trying to "%s" an "%s" object.',
174
                $intention,
175
                is_object($resource) ? get_class($resource) : 'unknown'
176
            ));
177
        }
178
    }
179
180
    /**
181
     * Forwarding action which can proxy other controllers / methods, in cases
182
     * of some protocols isn't supported by clients
183
     *
184
     * Routing has to provide 2 options keys :
185
     * @example
186
     *     route_name:
187
     *         path: .......
188
     *         ....
189
     *         options:
190
     *            forward:
191
     *                controller: MajoraNamespaceBundle:MajoraEntityApi:link
192
     *                method: LINK    # GET by default here
193
     *
194
     * @param Request $request
195
     *
196
     * @return Response
197
     */
198
    public function forwardAction(Request $request)
199
    {
200
        $route = $this->container->get('router')->getRouteCollection()
201
            ->get($request->get('_route'))
202
        ;
203
        if (!$forwardOptions = $route->getOption('forward')) {
204
            throw new \RuntimeException('Forward action has to be called under a route with "forward" option key.');
205
        }
206
        if (empty($forwardOptions['controller'])) {
207
            throw new \InvalidArgumentException('You must provide a "controller" key under "forward" routing option key.');
208
        }
209
210
        $subRequest = $request->duplicate();
211
        $subRequest->attributes->set('_controller', $forwardOptions['controller']);
212
        $subRequest->setMethod(isset($forwardOptions['method']) ?
213
            $forwardOptions['method'] :
214
            'GET'
215
        );
216
217
        return $this->container->get('http_kernel')->handle(
218
            $subRequest,
219
            HttpKernelInterface::SUB_REQUEST
220
        );
221
    }
222
}
223