Passed
Push — master ( d47369...3c21e7 )
by Atanas
01:55
created

Application::container()   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
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 0
ccs 2
cts 2
cp 1
crap 1
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 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 2
	protected function verifyBootstrap() {
118 2
		if ( ! $this->isBootstrapped() ) {
119 1
			throw new ConfigurationException( static::class . ' must be bootstrapped first.' );
120
		}
121 1
	}
122
123
	/**
124
	 * Get the IoC container instance.
125
	 *
126
	 * @return Container
127
	 */
128 1
	public function container() {
129 1
		return $this->container;
130
	}
131
132
	/**
133
	 * Bootstrap the application.
134
	 *
135
	 * @param  array   $config
136
	 * @param  boolean $run
137
	 * @return void
138
	 */
139 4
	public function bootstrap( $config = [], $run = true ) {
140 4
		if ( $this->isBootstrapped() ) {
141 1
			throw new ConfigurationException( static::class . ' already bootstrapped.' );
142
		}
143
144 4
		$this->bootstrapped = true;
145
146 4
		$container = $this->container();
147 4
		$this->loadConfig( $container, $config );
148 4
		$this->loadServiceProviders( $container );
149
150
		$this->renderConfigurationExceptions( function () use ( $run ) {
151 4
			$this->loadRoutes();
152
153 4
			if ( $run ) {
154 1
				$kernel = $this->resolve( WPEMERGE_WORDPRESS_HTTP_KERNEL_KEY );
155 1
				$kernel->bootstrap();
156 1
			}
157 4
		} );
158 4
	}
159
160
	/**
161
	 * Load config into the service container.
162
	 *
163
	 * @codeCoverageIgnore
164
	 * @param  Container $container
165
	 * @param  array     $config
166
	 * @return void
167
	 */
168
	protected function loadConfig( Container $container, $config ) {
169
		$container[ WPEMERGE_CONFIG_KEY ] = $config;
170
	}
171
172
	/**
173
	 * Register and bootstrap all service providers.
174
	 *
175
	 * @codeCoverageIgnore
176
	 * @param  Container $container
177
	 * @return void
178
	 */
179
	protected function loadServiceProviders( Container $container ) {
180
		$container[ WPEMERGE_SERVICE_PROVIDERS_KEY ] = array_merge(
181
			$this->service_providers,
182
			Arr::get( $container[ WPEMERGE_CONFIG_KEY ], 'providers', [] )
183
		);
184
185
		$service_providers = array_map( function ( $service_provider ) {
186
			if ( ! is_subclass_of( $service_provider, ServiceProviderInterface::class ) ) {
187
				throw new ConfigurationException(
188
					'The following class does not implement ' .
189
					'ServiceProviderInterface: ' . $service_provider
190
				);
191
			}
192
193
			return new $service_provider();
194
		}, $container[ WPEMERGE_SERVICE_PROVIDERS_KEY ] );
195
196
		$this->registerServiceProviders( $service_providers, $container );
197
		$this->bootstrapServiceProviders( $service_providers, $container );
198
	}
199
200
	/**
201
	 * Register all service providers.
202
	 *
203
	 * @param  array<ServiceProviderInterface> $service_providers
204
	 * @param  Container                                                  $container
205
	 * @return void
206
	 */
207 1
	protected function registerServiceProviders( $service_providers, Container $container ) {
208 1
		foreach ( $service_providers as $provider ) {
209 1
			$provider->register( $container );
210 1
		}
211 1
	}
212
213
	/**
214
	 * Bootstrap all service providers.
215
	 *
216
	 * @param  array<ServiceProviderInterface> $service_providers
217
	 * @param  Container                                                  $container
218
	 * @return void
219
	 */
220 1
	protected function bootstrapServiceProviders( $service_providers, Container $container ) {
221 1
		foreach ( $service_providers as $provider ) {
222 1
			$provider->bootstrap( $container );
223 1
		}
224 1
	}
225
226
	/**
227
	 * Load route definition files depending on the current request.
228
	 *
229
	 * @codeCoverageIgnore
230
	 * @return void
231
	 */
232
	protected function loadRoutes() {
233
		if ( wp_doing_ajax() ) {
234
			$this->loadRoutesGroup( 'ajax' );
235
			return;
236
		}
237
238
		if ( is_admin() ) {
239
			$this->loadRoutesGroup( 'admin' );
240
			return;
241
		}
242
243
		$this->loadRoutesGroup( 'web' );
244
	}
245
246
	/**
247
	 * Load a route group applying default attributes, if any.
248
	 *
249
	 * @codeCoverageIgnore
250
	 * @param  string $group
251
	 * @return void
252
	 */
253
	protected function loadRoutesGroup( $group ) {
254
		$config = $this->resolve( WPEMERGE_CONFIG_KEY );
255
		$file = Arr::get( $config, 'routes.' . $group . '.definitions', '' );
256
		$attributes = Arr::get( $config, 'routes.' . $group . '.attributes', [] );
257
258
		if ( empty( $file ) ) {
259
			return;
260
		}
261
262
		$middleware = Arr::get( $attributes, 'middleware', [] );
263
264
		if ( ! in_array( $group, $middleware, true ) ) {
265
			$middleware = array_merge( [$group], $middleware );
266
		}
267
268
		$attributes['middleware'] = $middleware;
269
270
		$blueprint = $this->resolve( WPEMERGE_ROUTING_ROUTE_BLUEPRINT_KEY );
271
		$blueprint->attributes( $attributes )->group( $file );
272
	}
273
274
	/**
275
	 * Resolve a dependency from the IoC container.
276
	 *
277
	 * @param  string     $key
278
	 * @return mixed|null
279
	 */
280 2
	public function resolve( $key ) {
281 2
		$this->verifyBootstrap();
282
283 2
		if ( ! isset( $this->container()[ $key ] ) ) {
284 1
			return null;
285
		}
286
287 2
		return $this->container()[ $key ];
288
	}
289
290
	/**
291
	 * Catch any configuration exceptions and short-circuit to an error page.
292
	 *
293
	 * @codeCoverageIgnore
294
	 * @param  Closure $action
295
	 * @return void
296
	 */
297
	protected function renderConfigurationExceptions( Closure $action ) {
298
		try {
299
			$action();
300
		} catch ( ConfigurationException $exception ) {
301
			if ( ! $this->render_configuration_exceptions ) {
302
				throw $exception;
303
			}
304
305
			$request = Request::fromGlobals();
306
			$handler = $this->resolve( WPEMERGE_EXCEPTIONS_CONFIGURATION_ERROR_HANDLER_KEY );
307
308
			add_filter( 'wpemerge.pretty_errors.apply_admin_styles', '__return_false' );
309
310
			$response_service = $this->resolve( WPEMERGE_RESPONSE_SERVICE_KEY );
311
			$response_service->respond( $handler->getResponse( $request, $exception ) );
312
313
			wp_die();
314
		}
315
	}
316
}
317