Passed
Push — master ( 534e7b...378b8e )
by Atanas
02:01
created

Application::loadRoutes()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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