Passed
Branch facadeless (50a555)
by Atanas
02:42
created

Application::bootstrap()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

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