Passed
Branch try/acceptance-tests (720b0a)
by Atanas
02:12
created

HttpKernel   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 446
Duplicated Lines 0 %

Test Coverage

Coverage 59.09%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 41
eloc 137
dl 0
loc 446
ccs 78
cts 132
cp 0.5909
rs 9.1199
c 3
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A executeHandler() 0 12 2
A handle() 0 27 2
A respond() 0 8 2
A __construct() 0 18 1
A getResponseService() 0 2 1
A getResponse() 0 2 2
A makeMiddleware() 0 2 1
A compose() 0 4 1
A bootstrap() 0 12 1
A run() 0 21 3
A actionAjax() 0 10 2
A registerAdminAction() 0 6 1
A actionAdminLoad() 0 9 3
A getAdminHook() 0 16 4
A filterTemplateInclude() 0 27 4
A filterRequest() 0 20 4
A actionAdmin() 0 8 2
A getAdminPageHook() 0 16 3
A registerAjaxAction() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like HttpKernel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HttpKernel, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package   WPEmerge
4
 * @author    Atanas Angelov <[email protected]>
5
 * @copyright 2017-2019 Atanas Angelov
6
 * @license   https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0
7
 * @link      https://wpemerge.com/
8
 */
9
10
namespace WPEmerge\Kernels;
11
12
use Exception;
13
use Pimple\Container;
14
use Psr\Http\Message\ResponseInterface;
15
use WP_Query;
16
use WPEmerge\Application\GenericFactory;
17
use WPEmerge\Exceptions\ConfigurationException;
18
use WPEmerge\Exceptions\ErrorHandlerInterface;
19
use WPEmerge\Helpers\Handler;
20
use WPEmerge\Helpers\HandlerFactory;
21
use WPEmerge\Middleware\ExecutesMiddlewareTrait;
22
use WPEmerge\Middleware\HasMiddlewareDefinitionsTrait;
23
use WPEmerge\Middleware\ReadsHandlerMiddlewareTrait;
24
use WPEmerge\Requests\RequestInterface;
25
use WPEmerge\Responses\ConvertsToResponseTrait;
26
use WPEmerge\Responses\ResponseService;
27
use WPEmerge\Routing\HasQueryFilterInterface;
28
use WPEmerge\Routing\Router;
29
use WPEmerge\Routing\SortsMiddlewareTrait;
30
use WPEmerge\View\ViewService;
31
32
/**
33
 * Describes how a request is handled.
34
 */
35
class HttpKernel implements HttpKernelInterface {
36
	use HasMiddlewareDefinitionsTrait;
37
	use SortsMiddlewareTrait;
38
	use ConvertsToResponseTrait;
39
	use ReadsHandlerMiddlewareTrait;
40
	use ExecutesMiddlewareTrait;
41
42
	/**
43
	 * Container.
44
	 *
45
	 * @var Container
46
	 */
47
	protected $container = null;
48
49
	/**
50
	 * Injection factory.
51
	 *
52
	 * @var GenericFactory
53
	 */
54
	protected $factory = null;
55
56
	/**
57
	 * Handler factory.
58
	 *
59
	 * @var HandlerFactory
60
	 */
61
	protected $handler_factory = null;
62
63
	/**
64
	 * Response service.
65
	 *
66
	 * @var ResponseService
67
	 */
68
	protected $response_service = null;
69
70
	/**
71
	 * Request.
72
	 *
73
	 * @var RequestInterface
74
	 */
75
	protected $request = null;
76
77
	/**
78
	 * Router.
79
	 *
80
	 * @var Router
81
	 */
82
	protected $router = null;
83
84
	/**
85
	 * View Service.
86
	 *
87
	 * @var ViewService
88
	 */
89
	protected $view_service = null;
90
91
	/**
92
	 * Error handler.
93
	 *
94
	 * @var ErrorHandlerInterface
95
	 */
96
	protected $error_handler = null;
97
98
	/**
99
	 * Template WordPress attempted to load.
100
	 *
101
	 * @var string
102
	 */
103
	protected $template = '';
104
105
	/**
106
	 * Constructor.
107
	 *
108
	 * @codeCoverageIgnore
109
	 * @param Container             $container
110
	 * @param GenericFactory        $factory
111
	 * @param HandlerFactory        $handler_factory
112
	 * @param ResponseService       $response_service
113
	 * @param RequestInterface      $request
114
	 * @param Router                $router
115
	 * @param ViewService           $view_service
116
	 * @param ErrorHandlerInterface $error_handler
117
	 */
118
	public function __construct(
119
		Container $container,
120
		GenericFactory $factory,
121
		HandlerFactory $handler_factory,
122
		ResponseService $response_service,
123
		RequestInterface $request,
124
		Router $router,
125
		ViewService $view_service,
126
		ErrorHandlerInterface $error_handler
127
	) {
128
		$this->container = $container;
129
		$this->factory = $factory;
130
		$this->handler_factory = $handler_factory;
131
		$this->response_service = $response_service;
132
		$this->request = $request;
133
		$this->router = $router;
134
		$this->view_service = $view_service;
135
		$this->error_handler = $error_handler;
136
	}
137
138
	/**
139
	 * Get the current response.
140
	 *
141
	 * @codeCoverageIgnore
142
	 * @return ResponseInterface|null
143
	 */
144
	protected function getResponse() {
145
		return isset( $this->container[ WPEMERGE_RESPONSE_KEY ] ) ? $this->container[ WPEMERGE_RESPONSE_KEY ] : null;
146
	}
147
148
	/**
149
	 * Get a Response Service instance.
150
	 *
151
	 * @codeCoverageIgnore
152
	 * @return ResponseService
153
	 */
154
	protected function getResponseService() {
155
		return $this->response_service;
156
	}
157
158
	/**
159
	 * Make a middleware class instance.
160
	 *
161
	 * @codeCoverageIgnore
162
	 * @param  string $class
163
	 * @return object
164
	 */
165
	protected function makeMiddleware( $class ) {
166
		return $this->factory->make( $class );
167
	}
168
169
	/**
170
	 * Execute a handler.
171
	 *
172
	 * @param  Handler           $handler
173
	 * @param  array             $arguments
174
	 * @return ResponseInterface
175
	 */
176 2
	protected function executeHandler( Handler $handler, $arguments = [] ) {
177 2
		$response = call_user_func_array( [$handler, 'execute'], $arguments );
178 2
		$response = $this->toResponse( $response );
179
180 2
		if ( ! $response instanceof ResponseInterface ) {
181 1
			throw new ConfigurationException(
182
				'Response returned by controller is not valid ' .
183 1
				'(expected ' . ResponseInterface::class . '; received ' . gettype( $response ) . ').'
184 1
			);
185
		}
186
187 1
		return $response;
188
	}
189
190
	/**
191
	 * {@inheritDoc}
192
	 */
193 2
	public function run( RequestInterface $request, $middleware, $handler, $arguments = [] ) {
194 2
		$this->error_handler->register();
195
196
		try {
197 2
			$handler = $handler instanceof Handler ? $handler : $this->handler_factory->make( $handler );
198
199 2
			$middleware = array_merge( $middleware, $this->getHandlerMiddleware( $handler ) );
200 2
			$middleware = $this->expandMiddleware( $middleware );
201 2
			$middleware = $this->uniqueMiddleware( $middleware );
202 2
			$middleware = $this->sortMiddleware( $middleware );
203
204
			$response = $this->executeMiddleware( $middleware, $request, function () use ( $handler, $arguments ) {
205 2
				return $this->executeHandler( $handler, $arguments );
206 2
			} );
207 2
		} catch ( Exception $exception ) {
208 1
			$response = $this->error_handler->getResponse( $request, $exception );
209
		}
210
211 1
		$this->error_handler->unregister();
212
213 1
		return $response;
214
	}
215
216
	/**
217
	 * {@inheritDoc}
218
	 */
219 2
	public function handle( RequestInterface $request, $arguments = [] ) {
220 2
		$route = $this->router->execute( $request );
221
222 2
		if ( $route === null ) {
223 1
			return null;
224
		}
225
226 1
		$route_arguments = $route->getArguments( $request );
227
228
		$request = $request
229 1
			->withAttribute( 'route', $route )
230 1
			->withAttribute( 'route_arguments', $route_arguments );
231
232 1
		$response = $this->run(
233 1
			$request,
234 1
			$route->getAttribute( 'middleware', [] ),
235 1
			$route->getAttribute( 'handler' ),
236 1
			array_merge(
237 1
				[$request],
238 1
				$arguments,
239
				$route_arguments
240 1
			)
241 1
		);
242
243 1
		$this->container[ WPEMERGE_RESPONSE_KEY ] = $response;
244
245 1
		return $response;
246
	}
247
248
	/**
249
	 * Respond with the current response.
250
	 *
251
	 * @return void
252
	 */
253 2
	public function respond() {
254 2
		$response = $this->getResponse();
255
256 2
		if ( ! $response instanceof ResponseInterface ) {
257 1
			return;
258
		}
259
260 1
		$this->response_service->respond( $response );
261 1
	}
262
263
	/**
264
	 * Output the current view outside of the routing flow.
265
	 *
266
	 * @return void
267
	 */
268 1
	public function compose() {
269 1
		$view = $this->view_service->make( $this->template );
270
271 1
		echo $view->toString();
272 1
	}
273
274
	/**
275
	 * {@inheritDoc}
276
	 * @codeCoverageIgnore
277
	 */
278
	public function bootstrap() {
279
		// Web. Use 3100 so it's high enough and has uncommonly used numbers
280
		// before and after. For example, 1000 is too common and it would have 999 before it
281
		// which is too common as well.).
282
		add_action( 'request', [$this, 'filterRequest'], 3100 );
283
		add_action( 'template_include', [$this, 'filterTemplateInclude'], 3100 );
284
285
		// Ajax.
286
		add_action( 'admin_init', [$this, 'registerAjaxAction'] );
287
288
		// Admin.
289
		add_action( 'admin_init', [$this, 'registerAdminAction'] );
290
	}
291
292
	/**
293
	 * Filter the main query vars.
294
	 *
295
	 * @param  array $query_vars
296
	 * @return array
297
	 */
298 2
	public function filterRequest( $query_vars ) {
299 2
		$routes = $this->router->getRoutes();
300
301 2
		foreach ( $routes as $route ) {
302 2
			if ( ! $route instanceof HasQueryFilterInterface ) {
303 2
				continue;
304
			}
305
306 2
			if ( ! $route->isSatisfied( $this->request ) ) {
307 1
				continue;
308
			}
309
310 2
			$this->container[ WPEMERGE_APPLICATION_KEY ]
311 2
				->renderConfigExceptions( function () use ( $route, &$query_vars ) {
312 2
					$query_vars = $route->applyQueryFilter( $this->request, $query_vars );
313 2
				} );
314 2
			break;
315 2
		}
316
317 2
		return $query_vars;
318
	}
319
320
	/**
321
	 * Filter the main template file.
322
	 *
323
	 * @param  string $template
324
	 * @return string
325
	 */
326 4
	public function filterTemplateInclude( $template ) {
327
		/** @var WP_Query $wp_query */
328 4
		global $wp_query;
329
330 4
		$this->template = $template;
331
332 4
		$response = $this->handle( $this->request, [$template] );
333
334 4
		if ( $response instanceof ResponseInterface ) {
335 2
			if ( $response->getStatusCode() === 404 ) {
336 1
				$wp_query->set_404();
337 1
			}
338
339 2
			add_action( 'wpemerge.kernels.http_kernel.respond', [$this, 'respond'] );
340
341 2
			return WPEMERGE_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'view.php';
342
		}
343
344 2
		$composers = $this->view_service->getComposersForView( $template );
345
346 2
		if ( ! empty( $composers ) ) {
347 1
			add_action( 'wpemerge.kernels.http_kernel.respond', [$this, 'compose'] );
348
349 1
			return WPEMERGE_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'view.php';
350
		}
351
352 1
		return $template;
353
	}
354
355
	/**
356
	 * Register ajax action to hook into current one.
357
	 *
358
	 * @return void
359
	 */
360
	public function registerAjaxAction() {
361
		if ( ! wp_doing_ajax() ) {
362
			return;
363
		}
364
365
		$action = $this->request->body( 'action', $this->request->query( 'action' ) );
366
		$action = sanitize_text_field( $action );
367
368
		add_action( "wp_ajax_{$action}", [$this, 'actionAjax'] );
369
		add_action( "wp_ajax_nopriv_{$action}", [$this, 'actionAjax'] );
370
	}
371
372
	/**
373
	 * Act on ajax action.
374
	 *
375
	 * @return void
376
	 */
377
	public function actionAjax() {
378
		$response = $this->handle( $this->request, [''] );
379
380
		if ( ! $response instanceof ResponseInterface ) {
381
			return;
382
		}
383
384
		$this->response_service->respond( $response );
385
386
		wp_die( '', '', ['response' => null] );
387
	}
388
389
	/**
390
	 * Get page hook.
391
	 * Slightly modified version of code from wp-admin/admin.php.
392
	 *
393
	 * @return string
394
	 */
395
	protected function getAdminPageHook() {
396
		global $pagenow, $typenow, $plugin_page;
397
398
		$page_hook = '';
399
400
		if ( isset( $plugin_page ) ) {
401
			$the_parent = $pagenow;
402
403
			if ( ! empty( $typenow ) ) {
404
				$the_parent = $pagenow . '?post_type=' . $typenow;
405
			}
406
407
			$page_hook = get_plugin_page_hook( $plugin_page, $the_parent );
408
		}
409
410
		return $page_hook;
411
	}
412
413
	/**
414
	 * Get admin page hook.
415
	 * Slightly modified version of code from wp-admin/admin.php.
416
	 *
417
	 * @param  string $page_hook
418
	 * @return string
419
	 */
420
	protected function getAdminHook( $page_hook ) {
421
		global $pagenow, $plugin_page;
422
423
		if ( ! empty( $page_hook ) ) {
424
			return $page_hook;
425
		}
426
427
		if ( isset( $plugin_page ) ) {
428
			return $plugin_page;
429
		}
430
431
		if ( isset( $pagenow ) ) {
432
			return $pagenow;
433
		}
434
435
		return '';
436
	}
437
438
	/**
439
	 * Register admin action to hook into current one.
440
	 *
441
	 * @return void
442
	 */
443
	public function registerAdminAction() {
444
		$page_hook = $this->getAdminPageHook();
445
		$hook_suffix = $this->getAdminHook( $page_hook );
446
447
		add_action( "load-{$hook_suffix}", [$this, 'actionAdminLoad'] );
448
		add_action( $hook_suffix, [$this, 'actionAdmin'] );
449
	}
450
451
	/**
452
	 * Act on admin action load.
453
	 *
454
	 * @return void
455
	 */
456
	public function actionAdminLoad() {
457
		$response = $this->handle( $this->request, [''] );
458
459
		if ( ! $response instanceof ResponseInterface ) {
460
			return;
461
		}
462
463
		if ( ! headers_sent() ) {
464
			$this->response_service->sendHeaders( $response );
465
		}
466
	}
467
468
	/**
469
	 * Act on admin action.
470
	 *
471
	 * @return void
472
	 */
473
	public function actionAdmin() {
474
		$response = $this->getResponse();
475
476
		if ( ! $response instanceof ResponseInterface ) {
477
			return;
478
		}
479
480
		$this->response_service->sendBody( $response );
481
	}
482
}
483