Passed
Push — master ( c40970...cb03ce )
by Atanas
03:02
created

Application::instantiate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 14
ccs 8
cts 8
cp 1
crap 3
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
		ApplicationServiceProvider::class,
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
	 * Make a new application instance.
77
	 *
78
	 * @codeCoverageIgnore
79
	 * @return self
80
	 */
81
	public static function make() {
82
		$container = new Container();
83
		$container[ WPEMERGE_APPLICATION_KEY ] = function ( $container ) {
84
			return new self( $container );
85
		};
86
87
		Facade::setFacadeApplication( $container );
88
		AliasLoader::getInstance()->register();
89
90
		/** @var $app self */
91
		$app = $container[ WPEMERGE_APPLICATION_KEY ];
92
93
		return $app;
94
	}
95
96
	/**
97
	 * Constructor.
98
	 *
99
	 * @param Container $container
100
	 * @param boolean   $render_configuration_exceptions
101
	 */
102 1
	public function __construct( Container $container, $render_configuration_exceptions = true ) {
103 1
		$this->container = $container;
104 1
		$this->render_configuration_exceptions = $render_configuration_exceptions;
105 1
	}
106
107
	/**
108
	 * Get whether WordPress is in debug mode.
109
	 *
110
	 * @return boolean
111
	 */
112 1
	public function debugging() {
113 1
		$debugging = ( defined( 'WP_DEBUG' ) && WP_DEBUG );
114 1
		$debugging = apply_filters( 'wpemerge.debug', $debugging );
115 1
		return $debugging;
116
	}
117
118
	/**
119
	 * Get whether the application has been bootstrapped.
120
	 *
121
	 * @return boolean
122
	 */
123 1
	public function isBootstrapped() {
124 1
		return $this->bootstrapped;
125
	}
126
127
	/**
128
	 * Throw an exception if the application has not been bootstrapped.
129
	 *
130
	 * @return void
131
	 */
132 2
	protected function verifyBootstrap() {
133 2
		if ( ! $this->isBootstrapped() ) {
134 1
			throw new ConfigurationException( static::class . ' must be bootstrapped first.' );
135
		}
136 1
	}
137
138
	/**
139
	 * Get the IoC container instance.
140
	 *
141
	 * @return Container
142
	 */
143 1
	public function getContainer() {
144 1
		return $this->container;
145
	}
146
147
	/**
148
	 * Bootstrap the application.
149
	 * WordPress' 'after_setup_theme' action is a good place to call this.
150
	 *
151
	 * @param  array   $config
152
	 * @param  boolean $run
153
	 * @return void
154
	 */
155 4
	public function bootstrap( $config = [], $run = true ) {
156 4
		if ( $this->isBootstrapped() ) {
157 1
			throw new ConfigurationException( static::class . ' already bootstrapped.' );
158
		}
159
160 4
		$this->bootstrapped = true;
161
162 4
		$container = $this->getContainer();
163 4
		$this->loadConfig( $container, $config );
164 4
		$this->loadServiceProviders( $container );
165
166 4
		$this->renderConfigurationExceptions( function () use ( $run ) {
167 4
			$this->loadRoutes();
168
169 4
			if ( $run ) {
170 1
				$kernel = $this->resolve( WPEMERGE_WORDPRESS_HTTP_KERNEL_KEY );
171 1
				$kernel->bootstrap();
172
			}
173 4
		} );
174 4
	}
175
176
	/**
177
	 * Load config into the service container.
178
	 *
179
	 * @codeCoverageIgnore
180
	 * @param  Container $container
181
	 * @param  array     $config
182
	 * @return void
183
	 */
184
	protected function loadConfig( Container $container, $config ) {
185
		$container[ WPEMERGE_CONFIG_KEY ] = $config;
186
	}
187
188
	/**
189
	 * Register and bootstrap all service providers.
190
	 *
191
	 * @codeCoverageIgnore
192
	 * @param  Container $container
193
	 * @return void
194
	 */
195
	protected function loadServiceProviders( Container $container ) {
196
		$container[ WPEMERGE_SERVICE_PROVIDERS_KEY ] = array_merge(
197
			$this->service_providers,
198
			Arr::get( $container[ WPEMERGE_CONFIG_KEY ], 'providers', [] )
199
		);
200
201
		$service_providers = array_map( function ( $service_provider ) {
202
			if ( ! is_subclass_of( $service_provider, ServiceProviderInterface::class ) ) {
203
				throw new ConfigurationException(
204
					'The following class does not implement ' .
205
					'ServiceProviderInterface: ' . $service_provider
206
				);
207
			}
208
209
			return new $service_provider();
210
		}, $container[ WPEMERGE_SERVICE_PROVIDERS_KEY ] );
211
212
		$this->registerServiceProviders( $service_providers, $container );
213
		$this->bootstrapServiceProviders( $service_providers, $container );
214
	}
215
216
	/**
217
	 * Register all service providers.
218
	 *
219
	 * @param  array<ServiceProviderInterface> $service_providers
220
	 * @param  Container                                                  $container
221
	 * @return void
222
	 */
223 1
	protected function registerServiceProviders( $service_providers, Container $container ) {
224 1
		foreach ( $service_providers as $provider ) {
225 1
			$provider->register( $container );
226
		}
227 1
	}
228
229
	/**
230
	 * Bootstrap all service providers.
231
	 *
232
	 * @param  array<ServiceProviderInterface> $service_providers
233
	 * @param  Container                                                  $container
234
	 * @return void
235
	 */
236 1
	protected function bootstrapServiceProviders( $service_providers, Container $container ) {
237 1
		foreach ( $service_providers as $provider ) {
238 1
			$provider->bootstrap( $container );
239
		}
240 1
	}
241
242
	/**
243
	 * Load route definition files depending on the current request.
244
	 *
245
	 * @codeCoverageIgnore
246
	 * @return void
247
	 */
248
	protected function loadRoutes() {
249
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
250
			$this->loadRoutesGroup( 'ajax' );
251
			return;
252
		}
253
254
		if ( is_admin() ) {
255
			$this->loadRoutesGroup( 'admin' );
256
			return;
257
		}
258
259
		$this->loadRoutesGroup( 'web' );
260
	}
261
262
	/**
263
	 * Load a route group applying default attributes, if any.
264
	 *
265
	 * @codeCoverageIgnore
266
	 * @param  string $group
267
	 * @return void
268
	 */
269
	protected function loadRoutesGroup( $group ) {
270
		$config = $this->resolve( WPEMERGE_CONFIG_KEY );
271
		$file = Arr::get( $config, 'routes.' . $group . '.definitions', '' );
272
		$attributes = Arr::get( $config, 'routes.' . $group . '.attributes', [] );
273
274
		if ( empty( $file ) ) {
275
			return;
276
		}
277
278
		$middleware = Arr::get( $attributes, 'middleware', [] );
279
280
		if ( ! in_array( $group, $middleware, true ) ) {
281
			$middleware = array_merge( [$group], $middleware );
282
		}
283
284
		$attributes['middleware'] = $middleware;
285
286
		$blueprint = $this->resolve( WPEMERGE_ROUTING_ROUTE_BLUEPRINT_KEY );
287
		$blueprint->attributes( $attributes )->group( $file );
288
	}
289
290
	/**
291
	 * Register a facade class.
292
	 *
293
	 * @param  string $alias
294
	 * @param  string $facade_class
295
	 * @return void
296
	 */
297 1
	public function alias( $alias, $facade_class ) {
298 1
		AliasLoader::getInstance()->alias( $alias, $facade_class );
299 1
	}
300
301
	/**
302
	 * Resolve a dependency from the IoC container.
303
	 *
304
	 * @param  string     $key
305
	 * @return mixed|null
306
	 */
307 2
	public function resolve( $key ) {
308 2
		$this->verifyBootstrap();
309
310 2
		if ( ! isset( $this->getContainer()[ $key ] ) ) {
311 1
			return null;
312
		}
313
314 2
		return $this->getContainer()[ $key ];
315
	}
316
317
	/**
318
	 * Catch any configuration exceptions and short-circuit to an error page.
319
	 *
320
	 * @codeCoverageIgnore
321
	 * @param  Closure $action
322
	 * @return void
323
	 */
324
	protected function renderConfigurationExceptions( Closure $action ) {
325
		try {
326
			$action();
327
		} catch ( ConfigurationException $exception ) {
328
			if ( ! $this->render_configuration_exceptions ) {
329
				throw $exception;
330
			}
331
332
			$request = Request::fromGlobals();
333
			$handler = $this->resolve( WPEMERGE_EXCEPTIONS_CONFIGURATION_ERROR_HANDLER_KEY );
334
335
			add_filter( 'wpemerge.pretty_errors.apply_admin_styles', '__return_false' );
336
337
			$response_service = $this->resolve( WPEMERGE_RESPONSE_SERVICE_KEY );
338
			$response_service->respond( $handler->getResponse( $request, $exception ) );
339
340
			wp_die();
341
		}
342
	}
343
}
344