PleasingPageHandler::getEditor()   C
last analyzed

Complexity

Conditions 14
Paths 11

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 15
c 1
b 0
f 0
nc 11
nop 2
dl 0
loc 29
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php 
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2021 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Debug\Handlers;
24
25
use Throwable;
26
use Traversable;
27
use ErrorException;
28
use RuntimeException;
29
use InvalidArgumentException;
30
use Syscodes\Debug\Util\Misc;
31
use UnexpectedValueException;
32
use Syscodes\Debug\Util\ArrayTable;
33
use Syscodes\Contracts\Debug\Table;
34
use Syscodes\Debug\Util\TemplateHandler;
35
use Syscodes\Debug\FrameHandler\Formatter;  
36
37
/**
38
 * Generates exceptions in mode of graphic interface.
39
 * 
40
 * @author Alexander Campo <[email protected]>
41
 */
42
class PleasingPageHandler extends MainHandler
43
{
44
	/**
45
	 * The brand main of handler.
46
	 * 
47
	 * @var string $brand
48
	 */
49
	protected $brand = 'Lenevor Debug';
50
51
	/**
52
	 * A string identifier for a known IDE/text editor, or a closure
53
	 * that resolves a string that can be used to open a given file
54
	 * in an editor.
55
	 * 
56
	 * @var mixed $editor
57
	 */
58
	protected $editor;
59
60
	/**
61
	 * A list of known editor strings.
62
	 * 
63
	 * @var array $editors
64
	 */
65
	protected $editors = [
66
		"vscode"   => "vscode://file/%file:%line",
67
		"sublime"  => "subl://open?url=file://%file&line=%line",
68
		"phpstorm" => "phpstorm://open?file://%file&line=%line",
69
		"textmate" => "txmt://open?url=file://%file&line=%line",
70
		"atom"     => "atom://core/open/file?filename=%file&line=%line",
71
	];
72
	
73
	/**
74
	 * The page title main of handler.
75
	 * 
76
	 * @var string $pageTitle
77
	 */
78
	protected $pageTitle = 'Lenevor Debug! There was an error.';
79
	
80
	/**
81
	 * Fast lookup cache for known resource locations.
82
	 * 
83
	 * @var array $resourceCache
84
	 */
85
	protected $resourceCache = [];
86
	
87
	/**
88
	 * The path to the directory containing the html error template directories.
89
	 * 
90
	 * @var array $searchPaths
91
	 */
92
	protected $searchPaths = [];
93
94
	/**
95
	 * Gets the table of data.
96
	 * 
97
	 * @var array $tables
98
	 */
99
	protected $tables = [];
100
	
101
	/**
102
	 * The template handler system.
103
	 * 
104
	 * @var string $template
105
	 */
106
	protected $template;	
107
	
108
	/**
109
	 * Constructor. The PleasingPageHandler class.
110
	 * 
111
	 * @return void
112
	 */
113
	public function __construct()
114
	{
115
		$this->template      = new TemplateHandler;
0 ignored issues
show
Documentation Bug introduced by
It seems like new Syscodes\Debug\Util\TemplateHandler() of type Syscodes\Debug\Util\TemplateHandler is incompatible with the declared type string of property $template.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
116
		$this->searchPaths[] = dirname(__DIR__).DIRECTORY_SEPARATOR.'Resources';
117
	}
118
119
	/**
120
	 * Adds an editor resolver, identified by a string name, and that may be a 
121
	 * string path, or a callable resolver.
122
	 * 
123
	 * @param  string            $identifier
124
	 * @param  string|\Callable  $resolver
0 ignored issues
show
Bug introduced by
The type Callable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
125
	 * 
126
	 * @return void
127
	 */
128
	public function addEditor($identifier, $resolver)
129
	{
130
		$this->editors[$identifier] = $resolver;
131
	}
132
133
	/**
134
	 * Adds an entry to the list of tables displayed in the template.
135
	 * The expected data is a simple associative array. Any nested arrays
136
	 * will be flattened with print_r.
137
	 * 
138
	 * @param  \Syscodes\Contracts\Debug\Table  $table
139
	 * 
140
	 * @return array
141
	 */
142
	public function addTables(Table $table)
143
	{
144
		$this->tables[] = $table;
145
	}
146
	
147
	/**
148
	 * Gathers the variables that will be made available to the view.
149
	 * 
150
	 * @return  array
151
	 */
152
	protected function collectionVars()
153
	{
154
		$supervisor = $this->getSupervisor();
155
		$style      = file_get_contents($this->getResource('css/debug.base.css'));
156
		$jscript    = file_get_contents($this->getResource('js/debug.base.js'));
157
		$tables     = array_merge($this->getDefaultTables(), $this->tables);
158
		
159
		return [ 
160
			'class'             => explode('\\', $supervisor->getExceptionName()),
161
			'stylesheet'        => preg_replace('#[\r\n\t ]+#', ' ', $style),
162
			'javascript'        => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
163
			'header'            => $this->getResource('views/header.php'),
164
			'sidebar'           => $this->getResource('views/sidebar.php'),
165
			'frame_description' => $this->getResource('views/frame_description.php'),
166
			'frame_list'        => $this->getResource('views/frame_list.php'),
167
			'details_panel'     => $this->getResource('views/details_panel.php'),
168
			'code_source'       => $this->getResource('views/code_source.php'),
169
			'details_content'   => $this->getResource('views/details_content.php'),
170
			'footer'            => $this->getResource('views/footer.php'),
171
			'plain_exception'   => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
172
			'handler'           => $this,
173
			'handlers'          => $this->getDebug()->getHandlers(),
0 ignored issues
show
Bug introduced by
The method getHandlers() does not exist on Syscodes\Contracts\Debug\Handler. Since it exists in all sub-types, consider adding an abstract or default implementation to Syscodes\Contracts\Debug\Handler. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

173
			'handlers'          => $this->getDebug()->/** @scrutinizer ignore-call */ getHandlers(),
Loading history...
174
			'debug'             => $this->getDebug(),
175
			'code'              => $this->getExceptionCode(),
176
			'message'           => $supervisor->getExceptionMessage(),
177
			'frames'            => $this->getExceptionFrames(),
178
			'tables'            => $this->getProcessTables($tables),
179
		];
180
	}
181
	
182
	/**
183
	 * The way in which the data sender (usually the server) can tell the recipient
184
	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
185
	 * 
186
	 * @return string
187
	 */
188
	public function contentType()
189
	{
190
		return 'text/html;charset=UTF-8';
191
	}
192
193
	/**
194
	 * Gets the brand of project.
195
	 * 
196
	 * @return string
197
	 */
198
	public function getBrand()
199
	{
200
		return $this->brand;
201
	}
202
203
	/**
204
	 * Returns the default tables.
205
	 * 
206
	 * @return \Syscodes\Contracts\Debug\Table[]
207
	 */
208
	protected function getDefaultTables()
209
	{
210
		return [
211
			new ArrayTable('GET Data', $_GET),
212
			new ArrayTable('POST Data', $_POST),
213
			new ArrayTable('Files', $_FILES),
214
			new ArrayTable('Cookie', $_COOKIE),
215
			new ArrayTable('Session', isset($_SESSION) ? $_SESSION : []),
216
			new ArrayTable('Server/Request Data', $_SERVER),
217
			new ArrayTable(__('exception.environmentVars'), $_ENV),
218
		];
219
	}
220
221
	/**
222
	 * Get the code of the exception that is currently being handled.
223
	 * 
224
	 * @return string
225
	 */
226
	protected function getExceptionCode()
227
	{
228
		$exception = $this->getException();
229
		$code      = $exception->getCode();
230
231
		if ($exception instanceof ErrorException) {
232
			$code = Misc::translateErrorCode($exception->getSeverity());
233
		}
234
235
		return (string) $code;
236
	}
237
238
	/**
239
	 * Get the stack trace frames of the exception that is currently being handled.
240
	 * 
241
	 * @return \Syscodes\Debug\Engine\Supervisor;
0 ignored issues
show
Bug introduced by
The type Syscodes\Debug\Engine\Supervisor was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
242
	 */
243
	protected function getExceptionFrames()
244
	{
245
		$frames = $this->getSupervisor()->getFrames();
246
		
247
		return $frames;
248
	}
249
	
250
	/**
251
	 * Gets the page title web.
252
	 * 
253
	 * @return string
254
	 */
255
	public function getPageTitle()
256
	{
257
		return $this->pageTitle;
258
	}
259
260
	/**
261
	 * Processes an array of tables making sure everything is allright.
262
	 * 
263
	 * @param  \Syscodes\Contracts\Debug\Table[]  $tables
264
	 * 
265
	 * @return array
266
	 */
267
	protected function getProcessTables(array $tables)
268
	{
269
		$processTables = [];
270
271
		foreach ($tables as $table) {
272
			if ( ! $table instanceof Table) {
273
				continue;
274
			}
275
276
			$label = $table->getLabel();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $label is correct as $table->getLabel() targeting Syscodes\Contracts\Debug\Table::getLabel() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
277
278
			try {
279
				$data = $table->getData();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $data is correct as $table->getData() targeting Syscodes\Contracts\Debug\Table::getData() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
280
281
				if ( ! (is_array($data) || $data instanceof Traversable)) {
282
					$data = [];
283
				}
284
			} catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The type Syscodes\Debug\Handlers\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
285
				$data = [];
286
			}
287
288
			$processTables[$label] = $data;
289
		}
290
291
		return $processTables;
292
	}
293
294
	/**
295
	 * Finds a resource, by its relative path, in all available search paths.
296
	 *
297
	 * @param  string  $resource
298
	 * 
299
	 * @return string
300
	 * 
301
	 * @throws \RuntimeException
302
	 */
303
	protected function getResource($resource)
304
	{
305
		if (isset($this->resourceCache[$resource])) {
306
			return $this->resourceCache[$resource];
307
		}
308
309
		foreach ($this->searchPaths as $path) {
310
			$fullPath = $path.DIRECTORY_SEPARATOR.$resource;
311
312
			if (is_file($fullPath)) {
313
				// Cache:
314
				$this->resourceCache[$resource] = $fullPath;
315
316
				return $fullPath;
317
			}
318
		}
319
320
		throw new RuntimeException( 
321
				"Could not find resource '{$resource}' in any resource paths.". 
322
				"(searched: ".join(", ", $this->searchPaths).")");
323
	}
324
	
325
	/**
326
	 * Given an exception and status code will display the error to the client.
327
	 * 
328
	 * @return int|null
329
	 */
330
	public function handle()
331
	{	
332
		$templatePath = $this->getResource('debug.layout.php');
333
334
		$vars = $this->collectionVars();
335
		
336
		if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
337
		
338
		$this->template->setVariables($vars);
339
		$this->template->render($templatePath);
340
		
341
		return MainHandler::QUIT;
342
	}
343
344
	/**
345
	 * Set the editor to use to open referenced files, by a string identifier or callable
346
	 * that will be executed for every file reference. Should return a string.
347
	 * 
348
	 * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
349
	 * @example  $debug->setEditor('vscode');
350
	 * 
351
	 * @param  string  $editor
352
	 * 
353
	 * @return void
354
	 * 
355
	 * @throws \InvalidArgumentException
356
	 */
357
	public function setEditor($editor)
358
	{
359
		if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
360
			throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
361
				implode(', ', array_keys($this->editors))
362
			);
363
		}
364
365
		$this->editor = $editor;
366
	}
367
368
	/**
369
	 * Given a string file path, and an integer file line,
370
	 * executes the editor resolver and returns.
371
	 * 
372
	 * @param  string  $file
373
	 * @param  int	   $line
374
	 * 
375
	 * @return string|bool
376
	 * 
377
	 * @throws \UnexpectedValueException
378
	 */
379
	public function getEditorAtHref($file, $line)
380
	{
381
		$editor = $this->getEditor($file, $line);
382
383
		if (empty($editor))	{
384
			return false;
385
		}
386
387
		if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
388
			throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
389
		}
390
391
		$editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
392
		$editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
393
394
		return $editor['url'];
395
	}
396
397
	/**
398
	 * The editor must be a valid callable function/closure.
399
	 * 
400
	 * @param  string  $file
401
	 * @param  int	   $line
402
	 * 
403
	 * @return array
404
	 */
405
	protected function getEditor($file, $line)
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

405
	protected function getEditor(/** @scrutinizer ignore-unused */ $file, $line)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
406
	{
407
		if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
408
			return [];
409
		}
410
411
		if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
412
			return ['url' => $this->editors[$this->editor]];
413
		}
414
415
		if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
416
			if (is_callable($this->editor)) {
417
				$callback = call_user_func($this->editor, $filePath, $line);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filePath seems to be never defined.
Loading history...
418
			} else {
419
				$callback = call_user_func($this->editors[$this->editor], $filePath, $line);
420
			}
421
422
			if (empty($callback)) {
423
				return [];
424
			}
425
426
			if (is_string($callback)) {
427
				return ['url' => $callback];
428
			}
429
			
430
			return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
431
		}
432
		
433
		return [];
434
	}
435
436
	/**
437
	 * Registered the editor.
438
	 * 
439
	 * @return string
440
	 */
441
	public function getEditorcode()
442
	{
443
		return $this->editor;
444
	}
445
	
446
	/**
447
	 * Sets the brand of project.
448
	 * 
449
	 * @param  string  $brand
450
	 * 
451
	 * @return void
452
	 */
453
	public function setBrand($brand)
454
	{
455
		$this->brand = (string) $brand;
456
	}
457
	
458
	/**
459
	 * Sets the page title web.
460
	 * 
461
	 * @param  string  $title
462
	 * 
463
	 * @return void
464
	 */
465
	public function setPageTitle($title)
466
	{
467
		$this->pageTitle = (string) $title;
468
	}
469
}