Passed
Push — master ( e9200e...e307b8 )
by Atanas
02:40
created

Application::renderConfigurationExceptions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 12
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   WPEmerge
4
 * @author    Atanas Angelov <[email protected]>
5
 * @copyright 2018 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\Application;
11
12
use Closure;
13
use Pimple\Container;
14
use WPEmerge\Controllers\ControllersServiceProvider;
15
use WPEmerge\Csrf\CsrfServiceProvider;
16
use WPEmerge\Exceptions\ConfigurationException;
17
use WPEmerge\Exceptions\ExceptionsServiceProvider;
18
use WPEmerge\Facades\Response;
19
use WPEmerge\Facades\Route;
20
use WPEmerge\Flash\FlashServiceProvider;
21
use WPEmerge\Input\OldInputServiceProvider;
22
use WPEmerge\Kernels\KernelsServiceProvider;
23
use WPEmerge\Requests\Request;
24
use WPEmerge\Requests\RequestsServiceProvider;
25
use WPEmerge\Responses\ResponsesServiceProvider;
26
use WPEmerge\Routing\RoutingServiceProvider;
27
use WPEmerge\ServiceProviders\ServiceProviderInterface;
28
use WPEmerge\Support\AliasLoader;
29
use WPEmerge\Support\Arr;
30
use WPEmerge\View\ViewServiceProvider;
31
32
/**
33
 * Main communication channel with the application.
34
 */
35
class Application {
36
	/**
37
	 * IoC container.
38
	 *
39
	 * @var Container
40
	 */
41
	protected $container = null;
42
43
	/**
44
	 * Flag whether to intercept and render configuration exceptions.
45
	 *
46
	 * @var boolean
47
	 */
48
	protected $render_configuration_exceptions = true;
49
50
	/**
51
	 * Flag whether the application has been bootstrapped.
52
	 *
53
	 * @var boolean
54
	 */
55
	protected $bootstrapped = false;
56
57
	/**
58
	 * Array of application service providers.
59
	 *
60
	 * @var string[]
61
	 */
62
	protected $service_providers = [
63
		KernelsServiceProvider::class,
64
		ExceptionsServiceProvider::class,
65
		RequestsServiceProvider::class,
66
		ResponsesServiceProvider::class,
67
		RoutingServiceProvider::class,
68
		ViewServiceProvider::class,
69
		ControllersServiceProvider::class,
70
		CsrfServiceProvider::class,
71
		FlashServiceProvider::class,
72
		OldInputServiceProvider::class,
73
	];
74
75
	/**
76
	 * Constructor.
77
	 *
78
	 * @param Container $container
79
	 * @param boolean   $render_configuration_exceptions
80
	 */
81 1
	public function __construct( Container $container, $render_configuration_exceptions = true ) {
82 1
		$this->container = $container;
83 1
		$this->render_configuration_exceptions = $render_configuration_exceptions;
84
85 1
		$config = isset( $container[ WPEMERGE_CONFIG_KEY ] ) ? $container[ WPEMERGE_CONFIG_KEY ] : [];
86 1
		$config = array_merge( [
87 1
			'providers' => [],
88 1
		], $config );
89 1
		$container[ WPEMERGE_CONFIG_KEY ] = $config;
90 1
	}
91
92
	/**
93
	 * Get whether WordPress is in debug mode.
94
	 *
95
	 * @return boolean
96
	 */
97 1
	public function debugging() {
98 1
		$debugging = ( defined( 'WP_DEBUG' ) && WP_DEBUG );
99 1
		$debugging = apply_filters( 'wpemerge.debug', $debugging );
100 1
		return $debugging;
101
	}
102
103
	/**
104
	 * Get whether the application has been bootstrapped.
105
	 *
106
	 * @return boolean
107
	 */
108 1
	public function isBootstrapped() {
109 1
		return $this->bootstrapped;
110
	}
111
112
	/**
113
	 * Throw an exception if the application has not been bootstrapped.
114
	 *
115
	 * @return void
116
	 */
117 5
	protected function verifyBootstrap() {
118 5
		if ( ! $this->isBootstrapped() ) {
119 1
			throw new ConfigurationException( static::class . ' must be bootstrapped first.' );
120
		}
121 4
	}
122
123
	/**
124
	 * Get the IoC container instance.
125
	 *
126
	 * @return Container
127
	 */
128 1
	public function getContainer() {
129 1
		return $this->container;
130
	}
131
132
	/**
133
	 * Bootstrap the application.
134
	 * WordPress' 'after_setup_theme' action is a good place to call this.
135
	 *
136
	 * @param  array   $config
137
	 * @param  boolean $run
138
	 * @return void
139
	 */
140 4
	public function bootstrap( $config = [], $run = true ) {
141 4
		if ( $this->isBootstrapped() ) {
142 1
			throw new ConfigurationException( static::class . ' already bootstrapped.' );
143
		}
144
145 4
		$this->bootstrapped = true;
146
147 4
		$container = $this->getContainer();
148 4
		$this->loadConfig( $container, $config );
149 4
		$this->loadServiceProviders( $container );
150
151
		$this->renderConfigurationExceptions( function () use ( $run ) {
152 4
			$this->loadRoutes();
153
154 4
			if ( $run ) {
155 1
				$kernel = $this->resolve( WPEMERGE_WORDPRESS_HTTP_KERNEL_KEY );
156 1
				$kernel->bootstrap();
157 1
			}
158 4
		} );
159 4
	}
160
161
	/**
162
	 * Load config into the service container.
163
	 *
164
	 * @codeCoverageIgnore
165
	 * @param  Container $container
166
	 * @param  array     $config
167
	 * @return void
168
	 */
169
	protected function loadConfig( Container $container, $config ) {
170
		$container[ WPEMERGE_CONFIG_KEY ] = array_merge(
171
			$container[ WPEMERGE_CONFIG_KEY ],
172
			$config
173
		);
174
	}
175
176
	/**
177
	 * Register and bootstrap all service providers.
178
	 *
179
	 * @codeCoverageIgnore
180
	 * @param  Container $container
181
	 * @return void
182
	 */
183
	protected function loadServiceProviders( Container $container ) {
184
		$container[ WPEMERGE_SERVICE_PROVIDERS_KEY ] = array_merge(
185
			$this->service_providers,
186
			$container[ WPEMERGE_CONFIG_KEY ]['providers']
187
		);
188
189
		$service_providers = array_map( function ( $service_provider ) {
190
			if ( ! is_subclass_of( $service_provider, ServiceProviderInterface::class ) ) {
191
				throw new ConfigurationException(
192
					'The following class does not implement ' .
193
					'ServiceProviderInterface: ' . $service_provider
194
				);
195
			}
196
197
			return new $service_provider();
198
		}, $container[ WPEMERGE_SERVICE_PROVIDERS_KEY ] );
199
200
		$this->registerServiceProviders( $service_providers, $container );
201
		$this->bootstrapServiceProviders( $service_providers, $container );
202
	}
203
204
	/**
205
	 * Register all service providers.
206
	 *
207
	 * @param  array<\WPEmerge\ServiceProviders\ServiceProviderInterface> $service_providers
208
	 * @param  Container                                                  $container
209
	 * @return void
210
	 */
211 1
	protected function registerServiceProviders( $service_providers, Container $container ) {
212 1
		foreach ( $service_providers as $provider ) {
213 1
			$provider->register( $container );
214 1
		}
215 1
	}
216
217
	/**
218
	 * Bootstrap all service providers.
219
	 *
220
	 * @param  array<\WPEmerge\ServiceProviders\ServiceProviderInterface> $service_providers
221
	 * @param  Container                                                  $container
222
	 * @return void
223
	 */
224 1
	protected function bootstrapServiceProviders( $service_providers, Container $container ) {
225 1
		foreach ( $service_providers as $provider ) {
226 1
			$provider->bootstrap( $container );
227 1
		}
228 1
	}
229
230
	/**
231
	 * Load route definition files depending on the current request.
232
	 *
233
	 * @codeCoverageIgnore
234
	 * @return void
235
	 */
236
	protected function loadRoutes() {
237
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
238
			$this->loadRoutesGroup( 'ajax' );
239
			return;
240
		}
241
242
		if ( is_admin() ) {
243
			$this->loadRoutesGroup( 'admin' );
244
			return;
245
		}
246
247
		$this->loadRoutesGroup( 'web' );
248
	}
249
250
	/**
251
	 * Load a route group applying default attributes, if any.
252
	 *
253
	 * @codeCoverageIgnore
254
	 * @param  string $group
255
	 * @return void
256
	 */
257
	protected function loadRoutesGroup( $group ) {
258
		$config = $this->resolve( WPEMERGE_CONFIG_KEY );
259
		$file = Arr::get( $config, 'routes.' . $group . '.definitions', '' );
260
		$attributes = Arr::get( $config, 'routes.' . $group . '.attributes', [] );
261
262
		if ( empty( $file ) ) {
263
			return;
264
		}
265
266
		$middleware = Arr::get( $attributes, 'middleware', [] );
267
268
		if ( ! in_array( $group, $middleware, true ) ) {
269
			$middleware = array_merge( [$group], $middleware );
270
		}
271
272
		$attributes['middleware'] = $middleware;
273
274
		Route::attributes( $attributes )->group( $file );
275
	}
276
277
	/**
278
	 * Register a facade class.
279
	 *
280
	 * @param  string $alias
281
	 * @param  string $facade_class
282
	 * @return void
283
	 */
284 1
	public function alias( $alias, $facade_class ) {
285 1
		AliasLoader::getInstance()->alias( $alias, $facade_class );
286 1
	}
287
288
	/**
289
	 * Resolve a dependency from the IoC container.
290
	 *
291
	 * @param  string     $key
292
	 * @return mixed|null
293
	 */
294 2
	public function resolve( $key ) {
295 2
		$this->verifyBootstrap();
296
297 2
		if ( ! isset( $this->getContainer()[ $key ] ) ) {
298 1
			return null;
299
		}
300
301 2
		return $this->getContainer()[ $key ];
302
	}
303
304
	/**
305
	 * Create and return a class instance.
306
	 *
307
	 * @throws ClassNotFoundException
308
	 * @param  string $class
309
	 * @return object
310
	 */
311 3
	public function instantiate( $class ) {
312 3
		$this->verifyBootstrap();
313
314 3
		$instance = $this->resolve( $class );
315
316 3
		if ( $instance === null ) {
317 2
			if ( ! class_exists( $class ) ) {
318 1
				throw new ClassNotFoundException( 'Class not found: ' . $class );
319
			}
320
321 1
			$instance = new $class();
322 1
		}
323
324 2
		return $instance;
325
	}
326
327
	/**
328
	 * Catch any configuration exceptions and short-circuit to an error page.
329
	 *
330
	 * @codeCoverageIgnore
331
	 * @param  Closure $action
332
	 * @return void
333
	 */
334
	protected function renderConfigurationExceptions( Closure $action ) {
335
		try {
336
			$action();
337
		} catch ( ConfigurationException $exception ) {
338
			if ( ! $this->render_configuration_exceptions ) {
339
				throw $exception;
340
			}
341
342
			$request = Request::fromGlobals();
343
			$handler = $this->resolve( WPEMERGE_EXCEPTIONS_CONFIGURATION_ERROR_HANDLER_KEY );
344
345
			add_filter( 'wpemerge.pretty_errors.apply_admin_styles', '__return_false' );
346
			Response::respond( $handler->getResponse( $request, $exception ) );
347
			wp_die();
348
		}
349
	}
350
}
351