Passed
Push — master ( 576bdc...3d3460 )
by Atanas
02:09
created

Application::resolve()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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