Passed
Push — 0.8.x ( 5c469b...6a817e )
by Alexander
06:08 queued 02:59
created
src/components/Debug/Exceptions/Handlers/PleasingPageHandler.php 1 patch
Indentation   +481 added lines, -481 removed lines patch added patch discarded remove patch
@@ -39,504 +39,504 @@
 block discarded – undo
39 39
  */
40 40
 class PleasingPageHandler extends Handler
41 41
 {
42
-	/**
43
-	 * The brand main of handler.
44
-	 * 
45
-	 * @var string $brand
46
-	 */
47
-	protected $brand = 'Lenevor Debug';
48
-
49
-	/**
50
-	 * A string identifier for a known IDE/text editor, or a closure
51
-	 * that resolves a string that can be used to open a given file
52
-	 * in an editor.
53
-	 * 
54
-	 * @var mixed $editor
55
-	 */
56
-	protected $editor;
57
-
58
-	/**
59
-	 * A list of known editor strings.
60
-	 * 
61
-	 * @var array $editors
62
-	 */
63
-	protected $editors = [
64
-		"vscode"   => "vscode://file/%file:%line",
65
-		"sublime"  => "subl://open?url=file://%file&line=%line",
66
-		"phpstorm" => "phpstorm://open?file://%file&line=%line",
67
-		"textmate" => "txmt://open?url=file://%file&line=%line",
68
-		"atom"     => "atom://core/open/file?filename=%file&line=%line",
69
-	];
42
+    /**
43
+     * The brand main of handler.
44
+     * 
45
+     * @var string $brand
46
+     */
47
+    protected $brand = 'Lenevor Debug';
48
+
49
+    /**
50
+     * A string identifier for a known IDE/text editor, or a closure
51
+     * that resolves a string that can be used to open a given file
52
+     * in an editor.
53
+     * 
54
+     * @var mixed $editor
55
+     */
56
+    protected $editor;
57
+
58
+    /**
59
+     * A list of known editor strings.
60
+     * 
61
+     * @var array $editors
62
+     */
63
+    protected $editors = [
64
+        "vscode"   => "vscode://file/%file:%line",
65
+        "sublime"  => "subl://open?url=file://%file&line=%line",
66
+        "phpstorm" => "phpstorm://open?file://%file&line=%line",
67
+        "textmate" => "txmt://open?url=file://%file&line=%line",
68
+        "atom"     => "atom://core/open/file?filename=%file&line=%line",
69
+    ];
70 70
 	
71
-	/**
72
-	 * The page title main of handler.
73
-	 * 
74
-	 * @var string $pageTitle
75
-	 */
76
-	protected $pageTitle = 'Lenevor Debug! There was an error';
71
+    /**
72
+     * The page title main of handler.
73
+     * 
74
+     * @var string $pageTitle
75
+     */
76
+    protected $pageTitle = 'Lenevor Debug! There was an error';
77 77
 	
78
-	/**
79
-	 * Fast lookup cache for known resource locations.
80
-	 * 
81
-	 * @var array $resourceCache
82
-	 */
83
-	protected $resourceCache = [];
78
+    /**
79
+     * Fast lookup cache for known resource locations.
80
+     * 
81
+     * @var array $resourceCache
82
+     */
83
+    protected $resourceCache = [];
84 84
 	
85
-	/**
86
-	 * The path to the directory containing the html error template directories.
87
-	 * 
88
-	 * @var array $searchPaths
89
-	 */
90
-	protected $searchPaths = [];
91
-
92
-	/**
93
-	 * Gets the table of data.
94
-	 * 
95
-	 * @var array $tables
96
-	 */
97
-	protected $tables = [];
85
+    /**
86
+     * The path to the directory containing the html error template directories.
87
+     * 
88
+     * @var array $searchPaths
89
+     */
90
+    protected $searchPaths = [];
91
+
92
+    /**
93
+     * Gets the table of data.
94
+     * 
95
+     * @var array $tables
96
+     */
97
+    protected $tables = [];
98 98
 	
99
-	/**
100
-	 * The template handler system.
101
-	 * 
102
-	 * @var string|object $template
103
-	 */
104
-	protected $template;	
99
+    /**
100
+     * The template handler system.
101
+     * 
102
+     * @var string|object $template
103
+     */
104
+    protected $template;	
105 105
 	
106
-	/**
107
-	 * Constructor. The PleasingPageHandler class.
108
-	 * 
109
-	 * @return void
110
-	 */
111
-	public function __construct()
112
-	{
113
-		$this->template      = new TemplateHandler;
114
-		$this->searchPaths[] = dirname(__DIR__).DIRECTORY_SEPARATOR.'Resources';
115
-	}
116
-
117
-	/**
118
-	 * Adds an editor resolver, identified by a string name, and that may be a 
119
-	 * string path, or a callable resolver.
120
-	 * 
121
-	 * @param  string            $identifier
122
-	 * @param  string|\Callable  $resolver
123
-	 * 
124
-	 * @return void
125
-	 */
126
-	public function addEditor($identifier, $resolver): void
127
-	{
128
-		$this->editors[$identifier] = $resolver;
129
-	}
130
-
131
-	/**
132
-	 * Adds an entry to the list of tables displayed in the template.
133
-	 * The expected data is a simple associative array. Any nested arrays
134
-	 * will be flattened with print_r.
135
-	 * 
136
-	 * @param  \Syscodes\Components\Contracts\Debug\Table  $table
137
-	 * 
138
-	 * @return void
139
-	 */
140
-	public function addTables(Table $table): void
141
-	{
142
-		$this->tables[] = $table;
143
-	}
106
+    /**
107
+     * Constructor. The PleasingPageHandler class.
108
+     * 
109
+     * @return void
110
+     */
111
+    public function __construct()
112
+    {
113
+        $this->template      = new TemplateHandler;
114
+        $this->searchPaths[] = dirname(__DIR__).DIRECTORY_SEPARATOR.'Resources';
115
+    }
116
+
117
+    /**
118
+     * Adds an editor resolver, identified by a string name, and that may be a 
119
+     * string path, or a callable resolver.
120
+     * 
121
+     * @param  string            $identifier
122
+     * @param  string|\Callable  $resolver
123
+     * 
124
+     * @return void
125
+     */
126
+    public function addEditor($identifier, $resolver): void
127
+    {
128
+        $this->editors[$identifier] = $resolver;
129
+    }
130
+
131
+    /**
132
+     * Adds an entry to the list of tables displayed in the template.
133
+     * The expected data is a simple associative array. Any nested arrays
134
+     * will be flattened with print_r.
135
+     * 
136
+     * @param  \Syscodes\Components\Contracts\Debug\Table  $table
137
+     * 
138
+     * @return void
139
+     */
140
+    public function addTables(Table $table): void
141
+    {
142
+        $this->tables[] = $table;
143
+    }
144 144
 	
145
-	/**
146
-	 * Gathers the variables that will be made available to the view.
147
-	 * 
148
-	 * @return  array
149
-	 */
150
-	protected function collectionVars(): array
151
-	{
152
-		$supervisor = $this->getSupervisor();
153
-		$style      = file_get_contents($this->getResource('css/debug.base.css'));
154
-		$jscript    = file_get_contents($this->getResource('js/debug.base.js'));
155
-		$servers    = array_merge($this->getDefaultServers(), $this->tables);
156
-		$routing    = array_merge($this->getDefaultRouting(), $this->tables);
157
-		$databases  = array_merge($this->getDefaultDatabase(), $this->tables);
158
-		$context    = array_merge($this->getDefaultContext(), $this->tables);
145
+    /**
146
+     * Gathers the variables that will be made available to the view.
147
+     * 
148
+     * @return  array
149
+     */
150
+    protected function collectionVars(): array
151
+    {
152
+        $supervisor = $this->getSupervisor();
153
+        $style      = file_get_contents($this->getResource('css/debug.base.css'));
154
+        $jscript    = file_get_contents($this->getResource('js/debug.base.js'));
155
+        $servers    = array_merge($this->getDefaultServers(), $this->tables);
156
+        $routing    = array_merge($this->getDefaultRouting(), $this->tables);
157
+        $databases  = array_merge($this->getDefaultDatabase(), $this->tables);
158
+        $context    = array_merge($this->getDefaultContext(), $this->tables);
159 159
 		
160
-		return [ 
161
-			'class' => explode('\\', $supervisor->getExceptionName()),
162
-			'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
163
-			'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
164
-			'header' => $this->getResource('views/partials/updown/header.php'),
165
-			'footer' => $this->getResource('views/partials/updown/footer.php'),
166
-			'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
167
-			'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
168
-			'section_frame' => $this->getResource('views/partials/section_frame.php'),
169
-			'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
170
-			'frame_list' => $this->getResource('views/partials/frames/frame_list.php'),
171
-			'section_code' => $this->getResource('views/partials/section_code.php'),
172
-			'code_source' => $this->getResource('views/partials/codes/code_source.php'),
173
-			'request_info' => $this->getResource('views/partials/request_info.php'),
174
-			'navigation' => $this->getResource('views/components/navBar.php'),
175
-			'settings' => $this->getResource('views/components/settingsDropdown.php'),
176
-			'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
177
-			'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
178
-			'handler' => $this,
179
-			'handlers' => $this->getDebug()->getHandlers(),
180
-			'debug' => $this->getDebug(),
181
-			'code' => $this->getExceptionCode(),
182
-			'message' => $supervisor->getExceptionMessage(),
183
-			'frames' => $this->getExceptionFrames(),
184
-			'servers' => $this->getProcessTables($servers),
185
-			'routes' => $this->getProcessTables($routing),
186
-			'databases' => $this->getProcessTables($databases),
187
-			'contexts' => $this->getProcessTables($context),
188
-		];
189
-	}
160
+        return [ 
161
+            'class' => explode('\\', $supervisor->getExceptionName()),
162
+            'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
163
+            'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
164
+            'header' => $this->getResource('views/partials/updown/header.php'),
165
+            'footer' => $this->getResource('views/partials/updown/footer.php'),
166
+            'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
167
+            'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
168
+            'section_frame' => $this->getResource('views/partials/section_frame.php'),
169
+            'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
170
+            'frame_list' => $this->getResource('views/partials/frames/frame_list.php'),
171
+            'section_code' => $this->getResource('views/partials/section_code.php'),
172
+            'code_source' => $this->getResource('views/partials/codes/code_source.php'),
173
+            'request_info' => $this->getResource('views/partials/request_info.php'),
174
+            'navigation' => $this->getResource('views/components/navBar.php'),
175
+            'settings' => $this->getResource('views/components/settingsDropdown.php'),
176
+            'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
177
+            'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
178
+            'handler' => $this,
179
+            'handlers' => $this->getDebug()->getHandlers(),
180
+            'debug' => $this->getDebug(),
181
+            'code' => $this->getExceptionCode(),
182
+            'message' => $supervisor->getExceptionMessage(),
183
+            'frames' => $this->getExceptionFrames(),
184
+            'servers' => $this->getProcessTables($servers),
185
+            'routes' => $this->getProcessTables($routing),
186
+            'databases' => $this->getProcessTables($databases),
187
+            'contexts' => $this->getProcessTables($context),
188
+        ];
189
+    }
190 190
 	
191
-	/**
192
-	 * The way in which the data sender (usually the server) can tell the recipient
193
-	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
194
-	 * 
195
-	 * @return string
196
-	 */
197
-	public function contentType(): string
198
-	{
199
-		return 'text/html;charset=UTF-8';
200
-	}
201
-
202
-	/**
203
-	 * Gets the brand of project.
204
-	 * 
205
-	 * @return string
206
-	 */
207
-	public function getBrand(): string
208
-	{
209
-		return $this->brand;
210
-	}
211
-
212
-	/**
213
-	 * Returns the default servers.
214
-	 * 
215
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
216
-	 */
217
-	protected function getDefaultServers()
218
-	{
219
-		$server = [
220
-			'host' => $_SERVER['HTTP_HOST'], 
221
-			'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
222
-			'accept' => $_SERVER['HTTP_ACCEPT'], 
223
-			'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
224
-			'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
225
-			'connection' => $_SERVER['HTTP_CONNECTION'],
226
-			'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
227
-			'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
228
-			'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
229
-			'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
230
-			'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
231
-		];
232
-
233
-		return [new ArrayTable($server)];
234
-	}
235
-
236
-	/**
237
-	 * Returns the default routing.
238
-	 * 
239
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
240
-	 */
241
-	protected function getDefaultRouting()
242
-	{
243
-		$action = app('request')->route()->isControllerAction() 
244
-		          ? app('request')->route()->parseControllerCallback()[0] 
245
-		          : 'Closure';
246
-
247
-		$index = match (true) {
248
-			array_key_exists('web', app('router')->getMiddlewareGroups()) => 0,
249
-			array_key_exists('api', app('router')->getMiddlewareGroups()) => 1,
250
-		};
251
-
252
-		$routing = [
253
-			'Controller' => $action,
254
-			'Middleware' => array_keys(app('router')->getMiddlewareGroups())[$index],
255
-		];
256
-
257
-		return [new ArrayTable($routing)];
258
-	}
259
-
260
-	/**
261
-	 * Returns the default database.
262
-	 * 
263
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
264
-	 */
265
-	protected function getDefaultDatabase()
266
-	{
267
-		$query = [
268
-			'Sql' => null,
269
-			'Time' => null,
270
-			'Connection name' => null,
271
-		];
272
-
273
-		return [new ArrayTable($query)];
274
-	}
275
-
276
-	/**
277
-	 * Returns the default context data.
278
-	 * 
279
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
280
-	 */
281
-	protected function getDefaultContext()
282
-	{
283
-		$context = [
284
-			'Php Version' => PHP_VERSION,
285
-			'Lenevor Version' => app()->version(),
286
-			'Lenevor Locale' => config('app.locale'),
287
-			'App Debug' => (1 == env('APP_DEBUG') ? 'true' : 'false'),
288
-			'App Env' => env('APP_ENV'),
289
-		];
290
-
291
-		return [new ArrayTable($context)];
292
-	}
293
-
294
-	/**
295
-	 * Get the code of the exception that is currently being handled.
296
-	 * 
297
-	 * @return string
298
-	 */
299
-	protected function getExceptionCode()
300
-	{
301
-		$exception = $this->getException();
302
-		$code      = $exception->getCode();
303
-
304
-		if ($exception instanceof ErrorException) {
305
-			$code = Misc::translateErrorCode($exception->getSeverity());
306
-		}
307
-
308
-		return (string) $code;
309
-	}
310
-
311
-	/**
312
-	 * Get the stack trace frames of the exception that is currently being handled.
313
-	 * 
314
-	 * @return \Syscodes\Components\Debug\Engine\Supervisor;
315
-	 */
316
-	protected function getExceptionFrames()
317
-	{
318
-		$frames = $this->getSupervisor()->getFrames();
191
+    /**
192
+     * The way in which the data sender (usually the server) can tell the recipient
193
+     * (the browser, in general) what type of data is being sent in this case, html format tagged.
194
+     * 
195
+     * @return string
196
+     */
197
+    public function contentType(): string
198
+    {
199
+        return 'text/html;charset=UTF-8';
200
+    }
201
+
202
+    /**
203
+     * Gets the brand of project.
204
+     * 
205
+     * @return string
206
+     */
207
+    public function getBrand(): string
208
+    {
209
+        return $this->brand;
210
+    }
211
+
212
+    /**
213
+     * Returns the default servers.
214
+     * 
215
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
216
+     */
217
+    protected function getDefaultServers()
218
+    {
219
+        $server = [
220
+            'host' => $_SERVER['HTTP_HOST'], 
221
+            'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
222
+            'accept' => $_SERVER['HTTP_ACCEPT'], 
223
+            'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
224
+            'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
225
+            'connection' => $_SERVER['HTTP_CONNECTION'],
226
+            'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
227
+            'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
228
+            'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
229
+            'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
230
+            'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
231
+        ];
232
+
233
+        return [new ArrayTable($server)];
234
+    }
235
+
236
+    /**
237
+     * Returns the default routing.
238
+     * 
239
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
240
+     */
241
+    protected function getDefaultRouting()
242
+    {
243
+        $action = app('request')->route()->isControllerAction() 
244
+                  ? app('request')->route()->parseControllerCallback()[0] 
245
+                  : 'Closure';
246
+
247
+        $index = match (true) {
248
+            array_key_exists('web', app('router')->getMiddlewareGroups()) => 0,
249
+            array_key_exists('api', app('router')->getMiddlewareGroups()) => 1,
250
+        };
251
+
252
+        $routing = [
253
+            'Controller' => $action,
254
+            'Middleware' => array_keys(app('router')->getMiddlewareGroups())[$index],
255
+        ];
256
+
257
+        return [new ArrayTable($routing)];
258
+    }
259
+
260
+    /**
261
+     * Returns the default database.
262
+     * 
263
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
264
+     */
265
+    protected function getDefaultDatabase()
266
+    {
267
+        $query = [
268
+            'Sql' => null,
269
+            'Time' => null,
270
+            'Connection name' => null,
271
+        ];
272
+
273
+        return [new ArrayTable($query)];
274
+    }
275
+
276
+    /**
277
+     * Returns the default context data.
278
+     * 
279
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
280
+     */
281
+    protected function getDefaultContext()
282
+    {
283
+        $context = [
284
+            'Php Version' => PHP_VERSION,
285
+            'Lenevor Version' => app()->version(),
286
+            'Lenevor Locale' => config('app.locale'),
287
+            'App Debug' => (1 == env('APP_DEBUG') ? 'true' : 'false'),
288
+            'App Env' => env('APP_ENV'),
289
+        ];
290
+
291
+        return [new ArrayTable($context)];
292
+    }
293
+
294
+    /**
295
+     * Get the code of the exception that is currently being handled.
296
+     * 
297
+     * @return string
298
+     */
299
+    protected function getExceptionCode()
300
+    {
301
+        $exception = $this->getException();
302
+        $code      = $exception->getCode();
303
+
304
+        if ($exception instanceof ErrorException) {
305
+            $code = Misc::translateErrorCode($exception->getSeverity());
306
+        }
307
+
308
+        return (string) $code;
309
+    }
310
+
311
+    /**
312
+     * Get the stack trace frames of the exception that is currently being handled.
313
+     * 
314
+     * @return \Syscodes\Components\Debug\Engine\Supervisor;
315
+     */
316
+    protected function getExceptionFrames()
317
+    {
318
+        $frames = $this->getSupervisor()->getFrames();
319 319
 		
320
-		return $frames;
321
-	}
320
+        return $frames;
321
+    }
322 322
 	
323
-	/**
324
-	 * Gets the page title web.
325
-	 * 
326
-	 * @return string
327
-	 */
328
-	public function getPageTitle(): string
329
-	{
330
-		return $this->pageTitle;
331
-	}
332
-
333
-	/**
334
-	 * Processes an array of tables making sure everything is all right.
335
-	 * 
336
-	 * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
337
-	 * 
338
-	 * @return array
339
-	 */
340
-	protected function getProcessTables(array $tables): array
341
-	{
342
-		$processTables = [];
343
-
344
-		foreach ($tables as $table) {
345
-			if ( ! $table instanceof Table) {
346
-				continue;
347
-			}
323
+    /**
324
+     * Gets the page title web.
325
+     * 
326
+     * @return string
327
+     */
328
+    public function getPageTitle(): string
329
+    {
330
+        return $this->pageTitle;
331
+    }
332
+
333
+    /**
334
+     * Processes an array of tables making sure everything is all right.
335
+     * 
336
+     * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
337
+     * 
338
+     * @return array
339
+     */
340
+    protected function getProcessTables(array $tables): array
341
+    {
342
+        $processTables = [];
343
+
344
+        foreach ($tables as $table) {
345
+            if ( ! $table instanceof Table) {
346
+                continue;
347
+            }
348 348
 			
349
-			$label = $table->getLabel();
350
-
351
-			try {
352
-				$data = (array) $table->getData();
353
-
354
-				if ( ! (is_array($data) || $data instanceof Traversable)) {
355
-					$data = [];
356
-				}
357
-			} catch (Exception $e) {
358
-				$data = [];
359
-			}
360
-
361
-			$processTables[$label] = $data;
362
-		}
363
-
364
-		return $processTables;
365
-	}
366
-
367
-	/**
368
-	 * Finds a resource, by its relative path, in all available search paths.
369
-	 *
370
-	 * @param  string  $resource
371
-	 * 
372
-	 * @return string
373
-	 * 
374
-	 * @throws \RuntimeException
375
-	 */
376
-	protected function getResource($resource)
377
-	{
378
-		if (isset($this->resourceCache[$resource])) {
379
-			return $this->resourceCache[$resource];
380
-		}
381
-
382
-		foreach ($this->searchPaths as $path) {
383
-			$fullPath = $path.DIRECTORY_SEPARATOR.$resource;
384
-
385
-			if (is_file($fullPath)) {
386
-				// Cache:
387
-				$this->resourceCache[$resource] = $fullPath;
388
-
389
-				return $fullPath;
390
-			}
391
-		}
392
-
393
-		throw new RuntimeException( 
394
-				"Could not find resource '{$resource}' in any resource paths.". 
395
-				"(searched: ".join(", ", $this->searchPaths).")");
396
-	}
349
+            $label = $table->getLabel();
350
+
351
+            try {
352
+                $data = (array) $table->getData();
353
+
354
+                if ( ! (is_array($data) || $data instanceof Traversable)) {
355
+                    $data = [];
356
+                }
357
+            } catch (Exception $e) {
358
+                $data = [];
359
+            }
360
+
361
+            $processTables[$label] = $data;
362
+        }
363
+
364
+        return $processTables;
365
+    }
366
+
367
+    /**
368
+     * Finds a resource, by its relative path, in all available search paths.
369
+     *
370
+     * @param  string  $resource
371
+     * 
372
+     * @return string
373
+     * 
374
+     * @throws \RuntimeException
375
+     */
376
+    protected function getResource($resource)
377
+    {
378
+        if (isset($this->resourceCache[$resource])) {
379
+            return $this->resourceCache[$resource];
380
+        }
381
+
382
+        foreach ($this->searchPaths as $path) {
383
+            $fullPath = $path.DIRECTORY_SEPARATOR.$resource;
384
+
385
+            if (is_file($fullPath)) {
386
+                // Cache:
387
+                $this->resourceCache[$resource] = $fullPath;
388
+
389
+                return $fullPath;
390
+            }
391
+        }
392
+
393
+        throw new RuntimeException( 
394
+                "Could not find resource '{$resource}' in any resource paths.". 
395
+                "(searched: ".join(", ", $this->searchPaths).")");
396
+    }
397 397
 	
398
-	/**
399
-	 * Given an exception and status code will display the error to the client.
400
-	 * 
401
-	 * @return int|null
402
-	 */
403
-	public function handle()
404
-	{	
405
-		$templatePath = $this->getResource('views/debug.layout.php');
406
-
407
-		$vars = $this->collectionVars();
398
+    /**
399
+     * Given an exception and status code will display the error to the client.
400
+     * 
401
+     * @return int|null
402
+     */
403
+    public function handle()
404
+    {	
405
+        $templatePath = $this->getResource('views/debug.layout.php');
406
+
407
+        $vars = $this->collectionVars();
408 408
 		
409
-		if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
409
+        if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
410 410
 		
411
-		$this->template->setVariables($vars);
412
-		$this->template->render($templatePath);
411
+        $this->template->setVariables($vars);
412
+        $this->template->render($templatePath);
413 413
 		
414
-		return Handler::QUIT;
415
-	}
416
-
417
-	/**
418
-	 * Set the editor to use to open referenced files, by a string identifier or callable
419
-	 * that will be executed for every file reference. Should return a string.
420
-	 * 
421
-	 * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
422
-	 * @example  $debug->setEditor('vscode');
423
-	 * 
424
-	 * @param  string  $editor
425
-	 * 
426
-	 * @return void
427
-	 * 
428
-	 * @throws \InvalidArgumentException
429
-	 */
430
-	public function setEditor($editor)
431
-	{
432
-		if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
433
-			throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
434
-				implode(', ', array_keys($this->editors))
435
-			);
436
-		}
437
-
438
-		$this->editor = $editor;
439
-	}
440
-
441
-	/**
442
-	 * Given a string file path, and an integer file line,
443
-	 * executes the editor resolver and returns.
444
-	 * 
445
-	 * @param  string  $file
446
-	 * @param  int	   $line
447
-	 * 
448
-	 * @return string|bool
449
-	 * 
450
-	 * @throws \UnexpectedValueException
451
-	 */
452
-	public function getEditorAtHref($file, $line)
453
-	{
454
-		$editor = $this->getEditor($file, $line);
455
-
456
-		if (empty($editor))	{
457
-			return false;
458
-		}
459
-
460
-		if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
461
-			throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
462
-		}
463
-
464
-		$editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
465
-		$editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
466
-
467
-		return $editor['url'];
468
-	}
469
-
470
-	/**
471
-	 * The editor must be a valid callable function/closure.
472
-	 * 
473
-	 * @param  string  $file
474
-	 * @param  int	   $line
475
-	 * 
476
-	 * @return array
477
-	 */
478
-	protected function getEditor($file, $line): array
479
-	{
480
-		if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
481
-			return [];
482
-		}
483
-
484
-		if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
485
-			return ['url' => $this->editors[$this->editor]];
486
-		}
487
-
488
-		if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
489
-			if (is_callable($this->editor)) {
490
-				$callback = call_user_func($this->editor, $file, $line);
491
-			} else {
492
-				$callback = call_user_func($this->editors[$this->editor], $file, $line);
493
-			}
494
-
495
-			if (empty($callback)) {
496
-				return [];
497
-			}
498
-
499
-			if (is_string($callback)) {
500
-				return ['url' => $callback];
501
-			}
414
+        return Handler::QUIT;
415
+    }
416
+
417
+    /**
418
+     * Set the editor to use to open referenced files, by a string identifier or callable
419
+     * that will be executed for every file reference. Should return a string.
420
+     * 
421
+     * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
422
+     * @example  $debug->setEditor('vscode');
423
+     * 
424
+     * @param  string  $editor
425
+     * 
426
+     * @return void
427
+     * 
428
+     * @throws \InvalidArgumentException
429
+     */
430
+    public function setEditor($editor)
431
+    {
432
+        if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
433
+            throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
434
+                implode(', ', array_keys($this->editors))
435
+            );
436
+        }
437
+
438
+        $this->editor = $editor;
439
+    }
440
+
441
+    /**
442
+     * Given a string file path, and an integer file line,
443
+     * executes the editor resolver and returns.
444
+     * 
445
+     * @param  string  $file
446
+     * @param  int	   $line
447
+     * 
448
+     * @return string|bool
449
+     * 
450
+     * @throws \UnexpectedValueException
451
+     */
452
+    public function getEditorAtHref($file, $line)
453
+    {
454
+        $editor = $this->getEditor($file, $line);
455
+
456
+        if (empty($editor))	{
457
+            return false;
458
+        }
459
+
460
+        if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
461
+            throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
462
+        }
463
+
464
+        $editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
465
+        $editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
466
+
467
+        return $editor['url'];
468
+    }
469
+
470
+    /**
471
+     * The editor must be a valid callable function/closure.
472
+     * 
473
+     * @param  string  $file
474
+     * @param  int	   $line
475
+     * 
476
+     * @return array
477
+     */
478
+    protected function getEditor($file, $line): array
479
+    {
480
+        if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
481
+            return [];
482
+        }
483
+
484
+        if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
485
+            return ['url' => $this->editors[$this->editor]];
486
+        }
487
+
488
+        if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
489
+            if (is_callable($this->editor)) {
490
+                $callback = call_user_func($this->editor, $file, $line);
491
+            } else {
492
+                $callback = call_user_func($this->editors[$this->editor], $file, $line);
493
+            }
494
+
495
+            if (empty($callback)) {
496
+                return [];
497
+            }
498
+
499
+            if (is_string($callback)) {
500
+                return ['url' => $callback];
501
+            }
502 502
 			
503
-			return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
504
-		}
503
+            return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
504
+        }
505 505
 		
506
-		return [];
507
-	}
508
-
509
-	/**
510
-	 * Registered the editor.
511
-	 * 
512
-	 * @return string
513
-	 */
514
-	public function getEditorcode(): string
515
-	{
516
-		return $this->editor;
517
-	}
506
+        return [];
507
+    }
508
+
509
+    /**
510
+     * Registered the editor.
511
+     * 
512
+     * @return string
513
+     */
514
+    public function getEditorcode(): string
515
+    {
516
+        return $this->editor;
517
+    }
518 518
 	
519
-	/**
520
-	 * Sets the brand of project.
521
-	 * 
522
-	 * @param  string  $brand
523
-	 * 
524
-	 * @return void
525
-	 */
526
-	public function setBrand($brand): void
527
-	{
528
-		$this->brand = (string) $brand;
529
-	}
519
+    /**
520
+     * Sets the brand of project.
521
+     * 
522
+     * @param  string  $brand
523
+     * 
524
+     * @return void
525
+     */
526
+    public function setBrand($brand): void
527
+    {
528
+        $this->brand = (string) $brand;
529
+    }
530 530
 	
531
-	/**
532
-	 * Sets the page title web.
533
-	 * 
534
-	 * @param  string  $title
535
-	 * 
536
-	 * @return void
537
-	 */
538
-	public function setPageTitle($title): void
539
-	{
540
-		$this->pageTitle = (string) $title;
541
-	}
531
+    /**
532
+     * Sets the page title web.
533
+     * 
534
+     * @param  string  $title
535
+     * 
536
+     * @return void
537
+     */
538
+    public function setPageTitle($title): void
539
+    {
540
+        $this->pageTitle = (string) $title;
541
+    }
542 542
 }
543 543
\ No newline at end of file
Please login to merge, or discard this patch.