Passed
Push — master ( fe5855...9d4c4f )
by Atanas
02:45
created

Application::isBootstrapped()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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