Completed
Push — master ( 9af1fb...3b0552 )
by Tomas
05:09
created

ApiPresenter   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 214
Duplicated Lines 4.67 %

Coupling/Cohesion

Components 2
Dependencies 11

Test Coverage

Coverage 45.54%

Importance

Changes 0
Metric Value
wmc 27
c 0
b 0
f 0
lcom 2
cbo 11
dl 10
loc 214
ccs 46
cts 101
cp 0.4554
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A startup() 0 5 1
A setCorsHeader() 0 4 1
B renderDefault() 0 42 6
A getHandler() 0 9 1
A checkAuth() 5 9 2
A processParams() 5 10 2
A logRequest() 0 23 3
B sendCorsHeaders() 0 22 5
B getRequestDomain() 0 15 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Tomaj\NetteApi\Presenters;
4
5
use Exception;
6
use Nette\Application\Responses\JsonResponse;
7
use Nette\Application\UI\Presenter;
8
use Nette\DI\Container;
9
use Nette\Http\Response;
10
use Tomaj\NetteApi\ApiDecider;
11
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
12
use Tomaj\NetteApi\Handlers\ApiHandlerInterface;
13
use Tomaj\NetteApi\Logger\ApiLoggerInterface;
14
use Tomaj\NetteApi\Params\ParamsProcessor;
15
use Tomaj\NetteApi\Response\JsonApiResponse;
16
use Tracy\Debugger;
17
18
/**
19
 * @property-read Container $context
20
 */
21
class ApiPresenter extends Presenter
22
{
23
    /**
24
     * @var  ApiDecider @inject
25
     */
26
    public $apiDecider;
27
28
    /**
29
     * CORS header settings
30
     *
31
     * Available values:
32
     *   'auto'  - send back header Access-Control-Allow-Origin with domain that made request
33
     *   '*'     - send header with '*' - this will workf fine if you dont need to send cookies via ajax calls to api
34
     *             with jquery $.ajax with xhrFields: { withCredentials: true } settings
35
     *   'off'   - will not send any CORS header
36
     *   other   - any other value will be send in Access-Control-Allow-Origin header
37
     *
38
     * @var string
39
     */
40
    protected $corsHeader = '*';
41
42
    /**
43
     * Presenter startup method
44
     *
45
     * @return void
46
     */
47 9
    public function startup()
48
    {
49 9
        parent::startup();
50 9
        $this->autoCanonicalize = false;
51 9
    }
52
53
    /**
54
     * Set cors header
55
     *
56
     * See description to property $corsHeader for valid inputs
57
     *
58
     * @param string $corsHeader
59
     */
60
    public function setCorsHeader($corsHeader)
61
    {
62
        $this->corsHeader = $corsHeader;
63
    }
64
65
    /**
66
     * Nette render default method
67
     *
68
     * @return void
69
     */
70 9
    public function renderDefault()
71
    {
72 9
        $start = microtime(true);
73
74 9
        $this->sendCorsHeaders();
75
76 9
        $hand = $this->getHandler();
77 9
        $handler = $hand['handler'];
78 9
        $authorization = $hand['authorization'];
79
80 9
        if ($this->checkAuth($authorization) === false) {
81
            return;
82
        }
83
84 6
        $params = $this->processParams($handler);
85 3
        if ($params === false) {
86
            return;
87
        }
88
89
        try {
90 3
            $response = $handler->handle($params);
91 3
            $code = $response->getCode();
92 1
        } catch (Exception $exception) {
93
            if (Debugger::isEnabled()) {
94
                $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error', 'detail' => $exception->getMessage()]);
95
            } else {
96
                $response = new JsonApiResponse(500, ['status' => 'error', 'message' => 'Internal server error']);
97
            }
98
            $code = $response->getCode();
99
            Debugger::log($exception, Debugger::EXCEPTION);
100
        }
101
102 3
        $end = microtime(true);
103
104 3
        if ($this->context->findByType('Tomaj\NetteApi\Logger\ApiLoggerInterface')) {
105
            $this->logRequest($this->context->getByType('Tomaj\NetteApi\Logger\ApiLoggerInterface'), $code, $end - $start);
0 ignored issues
show
Documentation introduced by
$this->context->getByTyp...r\\ApiLoggerInterface') is of type object|null, but the function expects a object<Tomaj\NetteApi\Logger\ApiLoggerInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
106
        }
107
108
        // output to nette
109 3
        $this->getHttpResponse()->setCode($code);
110 3
        $this->sendResponse($response);
111
    }
112
113
    /**
114
     * Get handler information triplet (endpoint, handler, authorization)
115
     *
116
     * @return array
117
     */
118 9
    private function getHandler()
119
    {
120 9
        return $this->apiDecider->getApiHandler(
121 9
            $this->getRequest()->getMethod(),
122 9
            $this->params['version'],
123 9
            $this->params['package'],
124 9
            $this->params['apiAction']
125 3
        );
126
    }
127
128
    /**
129
     * Check authorization
130
     *
131
     * @param ApiAuthorizationInterface  $authorization
132
     *
133
     * @return bool
134
     */
135 9
    private function checkAuth(ApiAuthorizationInterface $authorization)
136
    {
137 9 View Code Duplication
        if (!$authorization->authorized()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138 3
            $this->getHttpResponse()->setCode(Response::S403_FORBIDDEN);
139 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => $authorization->getErrorMessage()]));
140
            return false;
141
        }
142 6
        return true;
143
    }
144
145
    /**
146
     * Process input parameters
147
     *
148
     * @param ApiHandlerInterface   $handler
149
     *
150
     * @return array|bool
151
     */
152 6
    private function processParams(ApiHandlerInterface $handler)
153
    {
154 6
        $paramsProcessor = new ParamsProcessor($handler->params());
155 6 View Code Duplication
        if ($paramsProcessor->isError()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156 3
            $this->getHttpResponse()->setCode(Response::S500_INTERNAL_SERVER_ERROR);
157 3
            $this->sendResponse(new JsonResponse(['status' => 'error', 'message' => 'wrong input']));
158
            return false;
159
        }
160 3
        return $paramsProcessor->getValues();
161
    }
162
163
    /**
164
     * Log request
165
     *
166
     * @param ApiLoggerInterface  $logger
167
     * @param integer             $code
168
     * @param double              $elapsed
169
     *
170
     * @return void
171
     */
172
    private function logRequest(ApiLoggerInterface $logger, $code, $elapsed)
173
    {
174
        $headers = [];
175
        if (function_exists('getallheaders')) {
176
            $headers = getallheaders();
177
        }
178
179
        $requestHeaders = '';
180
        foreach ($headers as $key => $value) {
181
            $requestHeaders .= "$key: $value\n";
182
        }
183
184
        $ipDetector = $this->context->getByType('Tomaj\NetteApi\Misc\IpDetectorInterface');
185
        $logger->log(
186
            $code,
187
            $this->getRequest()->getMethod(),
188
            $requestHeaders,
189
            filter_input(INPUT_SERVER, 'REQUEST_URI'),
190
            $ipDetector->getRequestIp(),
191
            filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
192
            ($elapsed) * 1000
193
        );
194
    }
195
196 9
    protected function sendCorsHeaders()
197
    {
198 9
        $this->getHttpResponse()->addHeader('Access-Control-Allow-Methods', 'POST, DELETE, PUT, GET, OPTIONS');
199
200 9
        if ($this->corsHeader == 'auto') {
201
            $domain = $this->getRequestDomain();
202
            if ($domain !== false) {
203
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $domain);
204
                $this->getHttpResponse()->addHeader('Access-Control-Allow-Credentials', 'true');
205
            }
206
            return;
207
        }
208
209 9
        if ($this->corsHeader == '*') {
210 9
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', '*');
211 9
            return;
212
        }
213
214
        if ($this->corsHeader != 'off') {
215
            $this->getHttpResponse()->addHeader('Access-Control-Allow-Origin', $this->corsHeader);
216
        }
217
    }
218
219 1
    private function getRequestDomain()
220
    {
221 1
        if (!filter_input(INPUT_SERVER, 'HTTP_REFERER')) {
222
            return false;
223
        }
224
        $refererParsedUrl = parse_url(filter_input(INPUT_SERVER, 'HTTP_REFERER'));
225
        if (!(isset($refererParsedUrl['scheme']) && isset($refererParsedUrl['host']))) {
226
            return false;
227
        }
228
        $url = $refererParsedUrl['scheme'] . '://' . $refererParsedUrl['host'];
229
        if (isset($refererParsedUrl['port']) && $refererParsedUrl['port'] !== 80) {
230
            $url .= ':' . $refererParsedUrl['port'];
231
        }
232
        return $url;
233
    }
234
}
235