Passed
Push — master ( eba83d...f4707d )
by Roeland
11:39 queued 14s
created

Dispatcher::dispatch()   B

Complexity

Conditions 7
Paths 46

Size

Total Lines 66
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 39
nc 46
nop 2
dl 0
loc 66
rs 8.3626
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Christoph Wurst <[email protected]>
10
 * @author Julius Härtl <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Thomas Tanghus <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC\AppFramework\Http;
34
35
use OC\AppFramework\Http;
36
use OC\AppFramework\Middleware\MiddlewareDispatcher;
37
use OC\AppFramework\Utility\ControllerMethodReflector;
38
use OC\DB\Connection;
39
use OCP\AppFramework\Controller;
40
use OCP\AppFramework\Http\DataResponse;
41
use OCP\AppFramework\Http\Response;
42
use OCP\IConfig;
43
use OCP\IDBConnection;
44
use OCP\IRequest;
45
use Psr\Log\LoggerInterface;
46
47
/**
48
 * Class to dispatch the request to the middleware dispatcher
49
 */
50
class Dispatcher {
51
52
	/** @var MiddlewareDispatcher */
53
	private $middlewareDispatcher;
54
55
	/** @var Http */
56
	private $protocol;
57
58
	/** @var ControllerMethodReflector */
59
	private $reflector;
60
61
	/** @var IRequest */
62
	private $request;
63
64
	/** @var IConfig */
65
	private $config;
66
67
	/** @var IDBConnection|Connection */
68
	private $connection;
69
70
	/** @var LoggerInterface */
71
	private $logger;
72
73
	/**
74
	 * @param Http $protocol the http protocol with contains all status headers
75
	 * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
76
	 * runs the middleware
77
	 * @param ControllerMethodReflector $reflector the reflector that is used to inject
78
	 * the arguments for the controller
79
	 * @param IRequest $request the incoming request
80
	 * @param IConfig $config
81
	 * @param IDBConnection $connection
82
	 * @param LoggerInterface $logger
83
	 */
84
	public function __construct(Http $protocol,
85
								MiddlewareDispatcher $middlewareDispatcher,
86
								ControllerMethodReflector $reflector,
87
								IRequest $request,
88
								IConfig $config,
89
								IDBConnection $connection,
90
								LoggerInterface $logger) {
91
		$this->protocol = $protocol;
92
		$this->middlewareDispatcher = $middlewareDispatcher;
93
		$this->reflector = $reflector;
94
		$this->request = $request;
95
		$this->config = $config;
96
		$this->connection = $connection;
97
		$this->logger = $logger;
98
	}
99
100
101
	/**
102
	 * Handles a request and calls the dispatcher on the controller
103
	 * @param Controller $controller the controller which will be called
104
	 * @param string $methodName the method name which will be called on
105
	 * the controller
106
	 * @return array $array[0] contains a string with the http main header,
107
	 * $array[1] contains headers in the form: $key => value, $array[2] contains
108
	 * the response output
109
	 * @throws \Exception
110
	 */
111
	public function dispatch(Controller $controller, string $methodName): array {
112
		$out = [null, [], null];
113
114
		try {
115
			// prefill reflector with everything thats needed for the
116
			// middlewares
117
			$this->reflector->reflect($controller, $methodName);
118
119
			$this->middlewareDispatcher->beforeController($controller,
120
				$methodName);
121
122
			$databaseStatsBefore = [];
123
			if ($this->config->getSystemValueBool('debug', false)) {
124
				$databaseStatsBefore = $this->connection->getStats();
0 ignored issues
show
Bug introduced by
The method getStats() does not exist on OCP\IDBConnection. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\IDBConnection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

124
				/** @scrutinizer ignore-call */ 
125
    $databaseStatsBefore = $this->connection->getStats();
Loading history...
125
			}
126
127
			$response = $this->executeController($controller, $methodName);
128
129
			if (!empty($databaseStatsBefore)) {
130
				$databaseStatsAfter = $this->connection->getStats();
131
				$numBuilt = $databaseStatsAfter['built'] - $databaseStatsBefore['built'];
132
				$numExecuted = $databaseStatsAfter['executed'] - $databaseStatsBefore['executed'];
133
134
				if ($numBuilt > 50) {
135
					$this->logger->debug('Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [
136
						'class' => (string) get_class($controller),
137
						'method' => $methodName,
138
						'count' => $numBuilt,
139
					]);
140
				}
141
142
				if ($numExecuted > 100) {
143
					$this->logger->warning('Controller {class}::{method} executed {count} queries.' , [
144
						'class' => (string) get_class($controller),
145
						'method' => $methodName,
146
						'count' => $numExecuted,
147
					]);
148
				}
149
			}
150
151
			// if an exception appears, the middleware checks if it can handle the
152
			// exception and creates a response. If no response is created, it is
153
			// assumed that theres no middleware who can handle it and the error is
154
			// thrown again
155
		} catch (\Exception $exception) {
156
			$response = $this->middlewareDispatcher->afterException(
157
				$controller, $methodName, $exception);
158
		} catch (\Throwable $throwable) {
159
			$exception = new \Exception($throwable->getMessage(), $throwable->getCode(), $throwable);
160
			$response = $this->middlewareDispatcher->afterException(
161
			$controller, $methodName, $exception);
162
		}
163
164
		$response = $this->middlewareDispatcher->afterController(
165
			$controller, $methodName, $response);
166
167
		// depending on the cache object the headers need to be changed
168
		$out[0] = $this->protocol->getStatusHeader($response->getStatus());
169
		$out[1] = array_merge($response->getHeaders());
170
		$out[2] = $response->getCookies();
171
		$out[3] = $this->middlewareDispatcher->beforeOutput(
172
			$controller, $methodName, $response->render()
173
		);
174
		$out[4] = $response;
175
176
		return $out;
177
	}
178
179
180
	/**
181
	 * Uses the reflected parameters, types and request parameters to execute
182
	 * the controller
183
	 * @param Controller $controller the controller to be executed
184
	 * @param string $methodName the method on the controller that should be executed
185
	 * @return Response
186
	 */
187
	private function executeController(Controller $controller, string $methodName): Response {
188
		$arguments = [];
189
190
		// valid types that will be casted
191
		$types = ['int', 'integer', 'bool', 'boolean', 'float'];
192
193
		foreach ($this->reflector->getParameters() as $param => $default) {
194
195
			// try to get the parameter from the request object and cast
196
			// it to the type annotated in the @param annotation
197
			$value = $this->request->getParam($param, $default);
198
			$type = $this->reflector->getType($param);
199
200
			// if this is submitted using GET or a POST form, 'false' should be
201
			// converted to false
202
			if (($type === 'bool' || $type === 'boolean') &&
203
				$value === 'false' &&
204
				(
205
					$this->request->method === 'GET' ||
0 ignored issues
show
Bug introduced by
Accessing method on the interface OCP\IRequest suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
206
					strpos($this->request->getHeader('Content-Type'),
207
						'application/x-www-form-urlencoded') !== false
208
				)
209
			) {
210
				$value = false;
211
			} elseif ($value !== null && \in_array($type, $types, true)) {
212
				settype($value, $type);
213
			}
214
215
			$arguments[] = $value;
216
		}
217
218
		$response = \call_user_func_array([$controller, $methodName], $arguments);
219
220
		// format response
221
		if ($response instanceof DataResponse || !($response instanceof Response)) {
222
223
			// get format from the url format or request format parameter
224
			$format = $this->request->getParam('format');
225
226
			// if none is given try the first Accept header
227
			if ($format === null) {
228
				$headers = $this->request->getHeader('Accept');
229
				$format = $controller->getResponderByHTTPHeader($headers, null);
230
			}
231
232
			if ($format !== null) {
233
				$response = $controller->buildResponse($response, $format);
234
			} else {
235
				$response = $controller->buildResponse($response);
236
			}
237
		}
238
239
		return $response;
240
	}
241
}
242