DebugBar::initDebugBar()   F
last analyzed

Complexity

Conditions 22
Paths 3457

Size

Total Lines 110
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
cc 22
eloc 61
nc 3457
nop 0
dl 0
loc 110
rs 0
c 7
b 1
f 0

How to fix   Long Method    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
namespace LeKoala\DebugBar;
4
5
use Exception;
6
use Monolog\Logger;
7
use ReflectionObject;
8
use SilverStripe\ORM\DB;
9
use Psr\Log\LoggerInterface;
10
use SilverStripe\Core\Kernel;
11
use DebugBar\JavascriptRenderer;
12
use DebugBar\Storage\FileStorage;
13
use SilverStripe\Control\Director;
14
use SilverStripe\Core\Environment;
15
use SilverStripe\Admin\LeftAndMain;
16
use SilverStripe\View\Requirements;
17
use SilverStripe\Control\Controller;
18
use DebugBar\Bridge\MonologCollector;
19
use SilverStripe\Control\HTTPRequest;
20
use DebugBar\DebugBar as BaseDebugBar;
21
use SilverStripe\Control\Email\Mailer;
22
use SilverStripe\Core\Injector\Injector;
23
use SilverStripe\Core\Config\ConfigLoader;
24
use SilverStripe\Core\Config\Configurable;
25
use SilverStripe\Core\Injector\Injectable;
26
use SilverStripe\ORM\Connect\PDOConnector;
27
use DebugBar\DataCollector\MemoryCollector;
28
use LeKoala\DebugBar\Messages\LogFormatter;
29
use SilverStripe\Admin\AdminRootController;
30
use SilverStripe\Control\Email\SwiftMailer;
31
use DebugBar\DataCollector\PDO\PDOCollector;
32
use DebugBar\DataCollector\PDO\TraceablePDO;
33
use SilverStripe\Core\Manifest\ModuleLoader;
34
use DebugBar\DataCollector\MessagesCollector;
35
use SilverStripe\Core\Manifest\ModuleResource;
36
use LeKoala\DebugBar\Collector\ConfigCollector;
37
use LeKoala\DebugBar\Proxy\ConfigManifestProxy;
38
use LeKoala\DebugBar\Collector\PhpInfoCollector;
39
use LeKoala\DebugBar\Extension\ProxyDBExtension;
40
use LeKoala\DebugBar\Collector\DatabaseCollector;
41
use LeKoala\DebugBar\Collector\TimeDataCollector;
42
use DebugBar\Bridge\SwiftMailer\SwiftLogCollector;
43
use DebugBar\Bridge\SwiftMailer\SwiftMailCollector;
44
use LeKoala\DebugBar\Proxy\DeltaConfigManifestProxy;
45
use LeKoala\DebugBar\Collector\PartialCacheCollector;
46
use LeKoala\DebugBar\Collector\SilverStripeCollector;
47
use SilverStripe\Config\Collections\DeltaConfigCollection;
48
use SilverStripe\Config\Collections\CachedConfigCollection;
49
50
/**
51
 * A simple helper
52
 */
53
class DebugBar
54
{
55
    use Configurable;
56
    use Injectable;
57
58
    /**
59
     * @var BaseDebugBar
60
     */
61
    protected static $debugbar;
62
63
    /**
64
     * @var bool
65
     */
66
    public static $bufferingEnabled = false;
67
68
    /**
69
     * @var JavascriptRenderer
70
     */
71
    protected static $renderer;
72
73
    /**
74
     * @var bool
75
     */
76
    protected static $showQueries = false;
77
78
    /**
79
     * @var HTTPRequest
80
     */
81
    protected static $request;
82
83
    /**
84
     * @var array
85
     */
86
    protected static $extraTimes = [];
87
88
    /**
89
     * Get the Debug Bar instance
90
     * @throws Exception
91
     * @global array $databaseConfig
92
     * @return BaseDebugBar
93
     */
94
    public static function getDebugBar()
95
    {
96
        if (self::$debugbar !== null) {
97
            return self::$debugbar;
98
        }
99
100
        $reasons = self::disabledCriteria();
101
        if (!empty($reasons)) {
102
            self::$debugbar = false; // no need to check again
0 ignored issues
show
Documentation Bug introduced by Thomas
It seems like false of type false is incompatible with the declared type DebugBar\DebugBar of property $debugbar.

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...
103
            return;
104
        }
105
106
        self::initDebugBar();
107
108
        if (!self::$debugbar) {
109
            throw new Exception("Failed to initialize the DebugBar");
110
        }
111
112
        return self::$debugbar;
0 ignored issues
show
Bug Best Practice introduced by Thomas
The expression return self::debugbar returns the type void which is incompatible with the documented return type DebugBar\DebugBar.
Loading history...
113
    }
114
115
    /**
116
     * Init the debugbar instance
117
     *
118
     * @global array $databaseConfig
119
     * @return BaseDebugBar|null
120
     */
121
    public static function initDebugBar()
122
    {
123
        // Prevent multiple inits
124
        if (self::$debugbar) {
125
            return self::$debugbar;
126
        }
127
128
        self::$debugbar = $debugbar = new BaseDebugBar();
129
130
        if (isset($_REQUEST['showqueries']) && Director::isDev()) {
131
            self::setShowQueries(true);
132
            unset($_REQUEST['showqueries']);
133
        }
134
135
        $debugbar->addCollector(new PhpInfoCollector());
136
        $debugbar->addCollector(new TimeDataCollector());
137
        self::measureExtraTime();
138
        $debugbar->addCollector(new MemoryCollector());
139
140
        // Add config proxy replacing the core config manifest
141
        if (self::config()->config_collector) {
142
            /** @var ConfigLoader $configLoader */
143
            $configLoader = Injector::inst()->get(Kernel::class)->getConfigLoader();
144
            // There is no getManifests method on ConfigLoader
145
            $manifests = self::getProtectedValue($configLoader, 'manifests');
146
            foreach ($manifests as $manifestIdx => $manifest) {
147
                if ($manifest instanceof CachedConfigCollection) {
148
                    $manifest = new ConfigManifestProxy($manifest);
149
                    $manifests[$manifestIdx] = $manifest;
150
                }
151
                if ($manifest instanceof DeltaConfigCollection) {
152
                    $manifest = DeltaConfigManifestProxy::createFromOriginal($manifest);
153
                    $manifests[$manifestIdx] = $manifest;
154
                }
155
            }
156
            // Don't push as it may change stack order
157
            self::setProtectedValue($configLoader, 'manifests', $manifests);
158
        }
159
160
        if (self::config()->db_collector) {
161
            $connector = DB::get_connector();
162
            if (!self::config()->get('force_proxy') && $connector instanceof PDOConnector) {
163
                // Use a little bit of magic to replace the pdo instance
164
                $refObject = new ReflectionObject($connector);
165
                $refProperty = $refObject->getProperty('pdoConnection');
166
                $refProperty->setAccessible(true);
167
                $traceablePdo = new TraceablePDO($refProperty->getValue($connector));
168
                $refProperty->setValue($connector, $traceablePdo);
169
170
                $debugbar->addCollector(new PDOCollector($traceablePdo));
171
            } else {
172
                $debugbar->addCollector(new DatabaseCollector);
173
            }
174
        }
175
176
        // Add message collector last so other collectors can send messages to the console using it
177
        $debugbar->addCollector(new MessagesCollector());
178
179
        // Aggregate monolog into messages
180
        $logger = Injector::inst()->get(LoggerInterface::class);
181
        if ($logger instanceof Logger) {
182
            $logCollector = new MonologCollector($logger);
183
            $logCollector->setFormatter(new LogFormatter);
184
            $debugbar['messages']->aggregate($logCollector);
0 ignored issues
show
Bug introduced by Thomas
The method aggregate() does not exist on DebugBar\DataCollector\DataCollectorInterface. It seems like you code against a sub-type of DebugBar\DataCollector\DataCollectorInterface such as DebugBar\DataCollector\MessagesCollector. ( Ignorable by Annotation )

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

184
            $debugbar['messages']->/** @scrutinizer ignore-call */ 
185
                                   aggregate($logCollector);
Loading history...
185
        }
186
187
        // Add some SilverStripe specific infos
188
        $debugbar->addCollector(new SilverStripeCollector);
189
190
        if (self::config()->get('enable_storage')) {
191
            $debugBarTempFolder = TEMP_FOLDER . '/debugbar';
192
            $debugbar->setStorage($fileStorage = new FileStorage($debugBarTempFolder));
193
            if (isset($_GET['flush']) && is_dir($debugBarTempFolder)) {
194
                // FileStorage::clear() is implemented with \DirectoryIterator which throws UnexpectedValueException if dir can not be opened
195
                $fileStorage->clear();
196
            }
197
        }
198
199
        if (self::config()->config_collector) {
200
            // Add the config collector
201
            $debugbar->addCollector(new ConfigCollector);
202
        }
203
204
        // Partial cache
205
        if (self::config()->partial_cache_collector) {
206
            $debugbar->addCollector(new PartialCacheCollector);
207
        }
208
209
        // Email logging
210
        if (self::config()->email_collector) {
211
            $mailer = Injector::inst()->get(Mailer::class);
212
            if ($mailer instanceof SwiftMailer) {
213
                $swiftInst = $mailer->getSwiftMailer();
214
                $debugbar['messages']->aggregate(new SwiftLogCollector($swiftInst));
215
                $debugbar->addCollector(new SwiftMailCollector($swiftInst));
216
            }
217
        }
218
219
        // Since we buffer everything, why not enable all dev options ?
220
        if (self::config()->get('auto_debug')) {
221
            $_REQUEST['debug'] = true;
222
            $_REQUEST['debug_request'] = true;
223
        }
224
225
        if (isset($_REQUEST['debug']) || isset($_REQUEST['debug_request'])) {
226
            self::$bufferingEnabled = true;
227
            ob_start(); // We buffer everything until we have called an action
228
        }
229
230
        return $debugbar;
231
    }
232
233
    /**
234
     * Access a protected property when the api does not allow access
235
     *
236
     * @param object $object
237
     * @param string $property
238
     * @return mixed
239
     */
240
    protected static function getProtectedValue($object, $property)
241
    {
242
        $refObject = new ReflectionObject($object);
243
        $refProperty = $refObject->getProperty($property);
244
        $refProperty->setAccessible(true);
245
        return $refProperty->getValue($object);
246
    }
247
248
    /**
249
     * Set a protected property when the api does not allow access
250
     *
251
     * @param object $object
252
     * @param string $property
253
     * @param mixed $newValue
254
     * @return void
255
     */
256
    protected static function setProtectedValue($object, $property, $newValue)
257
    {
258
        $refObject = new ReflectionObject($object);
259
        $refProperty = $refObject->getProperty($property);
260
        $refProperty->setAccessible(true);
261
        return $refProperty->setValue($object, $newValue);
0 ignored issues
show
Bug introduced by Thomas
Are you sure the usage of $refProperty->setValue($object, $newValue) targeting ReflectionProperty::setValue() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
262
    }
263
264
    /**
265
     * Clear the current instance of DebugBar
266
     *
267
     * @return void
268
     */
269
    public static function clearDebugBar()
270
    {
271
        self::$debugbar = null;
272
        self::$bufferingEnabled = false;
273
        self::$renderer = null;
274
        self::$showQueries = false;
275
        self::$request = null;
276
        self::$extraTimes = [];
277
        ProxyDBExtension::resetQueries();
278
    }
279
280
    /**
281
     * @return boolean
282
     */
283
    public static function getShowQueries()
284
    {
285
        return self::$showQueries;
286
    }
287
288
    /**
289
     * Override default showQueries mode
290
     *
291
     * @param boolean $showQueries
292
     * @return void
293
     */
294
    public static function setShowQueries($showQueries)
295
    {
296
        self::$showQueries = $showQueries;
297
    }
298
299
    /**
300
     * Helper to access this module resources
301
     *
302
     * @param string $path
303
     * @return ModuleResource
304
     */
305
    public static function moduleResource($path)
306
    {
307
        return ModuleLoader::getModule('lekoala/silverstripe-debugbar')->getResource($path);
308
    }
309
310
    /**
311
     * Include DebugBar assets using Requirements API
312
     *
313
     * @return void
314
     */
315
    public static function includeRequirements()
316
    {
317
        $debugbar = self::getDebugBar();
318
319
        if (!$debugbar) {
0 ignored issues
show
introduced by Thomas
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
320
            return;
321
        }
322
323
        // Already called
324
        if (self::$renderer) {
325
            return;
326
        }
327
328
        $renderer = $debugbar->getJavascriptRenderer();
329
330
        // We don't need the true path since we are going to use Requirements API that appends the BASE_PATH
331
        $assetsResource = self::moduleResource('assets');
332
        $renderer->setBasePath($assetsResource->getRelativePath());
333
        $renderer->setBaseUrl(Director::makeRelative($assetsResource->getURL()));
334
335
        $includeJquery = self::config()->get('include_jquery');
336
        // In CMS, jQuery is already included
337
        if (self::isAdminController()) {
338
            $includeJquery = false;
339
        }
340
        // If jQuery is already included, set to false
341
        $js = Requirements::backend()->getJavascript();
342
        foreach ($js as $url => $args) {
343
            $name = basename($url);
344
            if ($name == 'jquery.js' || $name == 'jquery.min.js') {
345
                $includeJquery = false;
346
                break;
347
            }
348
        }
349
350
        if ($includeJquery) {
351
            $renderer->setEnableJqueryNoConflict(true);
352
        } else {
353
            $renderer->disableVendor('jquery');
354
            $renderer->setEnableJqueryNoConflict(false);
355
        }
356
357
        if (DebugBar::config()->get('enable_storage')) {
358
            $renderer->setOpenHandlerUrl('__debugbar');
359
        }
360
361
        foreach ($renderer->getAssets('css') as $cssFile) {
362
            Requirements::css(Director::makeRelative(ltrim($cssFile, '/')));
363
        }
364
365
        foreach ($renderer->getAssets('js') as $jsFile) {
366
            Requirements::javascript(Director::makeRelative(ltrim($jsFile, '/')));
367
        }
368
369
        self::$renderer = $renderer;
370
    }
371
372
    /**
373
     * Returns the script to display the DebugBar
374
     *
375
     * @return string
376
     */
377
    public static function renderDebugBar()
378
    {
379
        if (!self::$renderer) {
380
            return;
381
        }
382
383
        // If we have any extra time pending, add it
384
        if (!empty(self::$extraTimes)) {
385
            foreach (self::$extraTimes as $extraTime => $extraTimeData) {
386
                self::trackTime($extraTime);
387
            }
388
        }
389
390
        // Requirements may have been cleared (CMS iframes...) or not set (Security...)
391
        $js = Requirements::backend()->getJavascript();
392
        $debugBarResource = self::moduleResource('assets/debugbar.js');
393
        $path = $debugBarResource->getRelativePath();
394
395
        // Url in getJavascript has a / slash, so fix if necessary
396
        $path = str_replace("assets\\debugbar.js", "assets/debugbar.js", $path);
397
        if (!array_key_exists($path, $js)) {
398
            return;
399
        }
400
        $initialize = true;
401
        if (Director::is_ajax()) {
402
            $initialize = false;
403
        }
404
405
        $script = self::$renderer->render($initialize);
406
        return $script;
407
    }
408
409
    /**
410
     * Get all criteria why the DebugBar could be disabled
411
     *
412
     * @return array
413
     */
414
    public static function disabledCriteria()
415
    {
416
        $reasons = array();
417
        if (!Director::isDev() && !self::allowAllEnvironments()) {
418
            $reasons[] = 'Not in dev mode';
419
        }
420
        if (self::isDisabled()) {
421
            $reasons[] = 'Disabled by a constant or configuration';
422
        }
423
        if (self::vendorNotInstalled()) {
424
            $reasons[] = 'DebugBar is not installed in vendors';
425
        }
426
        if (self::notLocalIp()) {
427
            $reasons[] = 'Not a local ip';
428
        }
429
        if (Director::is_cli()) {
430
            $reasons[] = 'In CLI mode';
431
        }
432
        if (self::isDevUrl()) {
433
            $reasons[] = 'Dev tools';
434
        }
435
        if (self::isAdminUrl() && !self::config()->get('enabled_in_admin')) {
436
            $reasons[] = 'In admin';
437
        }
438
        if (isset($_GET['CMSPreview'])) {
439
            $reasons[] = 'CMS Preview';
440
        }
441
        return $reasons;
442
    }
443
444
    /**
445
     * Determine why DebugBar is disabled
446
     *
447
     * Deprecated in favor of disabledCriteria
448
     *
449
     * @return string
450
     */
451
    public static function whyDisabled()
452
    {
453
        $reasons = self::disabledCriteria();
454
        if (!empty($reasons)) {
455
            return $reasons[0];
456
        }
457
        return "I don't know why";
458
    }
459
460
    public static function vendorNotInstalled()
461
    {
462
        return !class_exists('DebugBar\\StandardDebugBar');
463
    }
464
465
    public static function notLocalIp()
466
    {
467
        if (!self::config()->get('check_local_ip')) {
468
            return false;
469
        }
470
        if (isset($_SERVER['REMOTE_ADDR'])) {
471
            return !in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', '1'));
472
        }
473
        return false;
474
    }
475
476
    public static function allowAllEnvironments()
477
    {
478
        // You will also need to add a debugbar-live config
479
        if (Environment::getEnv('DEBUGBAR_ALLOW_ALL_ENV')) {
480
            return true;
481
        }
482
        return false;
483
    }
484
485
    public static function isDisabled()
486
    {
487
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
488
            return true;
489
        }
490
        return false;
491
    }
492
493
    public static function isDevUrl()
494
    {
495
        return strpos(self::getRequestUrl(), '/dev/') === 0;
496
    }
497
498
    public static function isAdminUrl()
499
    {
500
        $baseUrl = rtrim(BASE_URL, '/');
501
        if (class_exists(AdminRootController::class)) {
502
            $adminUrl = AdminRootController::config()->get('url_base');
503
        } else {
504
            $adminUrl = 'admin';
505
        }
506
507
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
508
    }
509
510
    public static function isAdminController()
511
    {
512
        if (Controller::curr()) {
513
            return Controller::curr() instanceof LeftAndMain;
514
        }
515
        return self::isAdminUrl();
516
    }
517
518
    /**
519
     * Avoid triggering data collection for open handler
520
     *
521
     * @return boolean
522
     */
523
    public static function isDebugBarRequest()
524
    {
525
        if ($url = self::getRequestUrl()) {
526
            return strpos($url, '/__debugbar') === 0;
527
        }
528
        return true;
529
    }
530
531
    /**
532
     * Get request url
533
     *
534
     * @return string
535
     */
536
    public static function getRequestUrl()
537
    {
538
        if (isset($_REQUEST['url'])) {
539
            return $_REQUEST['url'];
540
        }
541
        if (isset($_SERVER['REQUEST_URI'])) {
542
            return $_SERVER['REQUEST_URI'];
543
        }
544
        return '';
545
    }
546
547
    /**
548
     * Helper to make code cleaner
549
     *
550
     * @param callable $callback
551
     */
552
    public static function withDebugBar($callback)
553
    {
554
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
555
            $callback(self::getDebugBar());
556
        }
557
    }
558
559
    /**
560
     * Set the current request. Is provided by the DebugBarMiddleware.
561
     *
562
     * @param HTTPRequest $request
563
     */
564
    public static function setRequest(HTTPRequest $request)
565
    {
566
        self::$request = $request;
567
    }
568
569
    /**
570
     * Get the current request
571
     *
572
     * @return HTTPRequest
573
     */
574
    public static function getRequest()
575
    {
576
        if (self::$request) {
577
            return self::$request;
578
        }
579
        // Fall back to trying from the global state
580
        if (Controller::has_curr()) {
581
            return Controller::curr()->getRequest();
582
        }
583
    }
584
585
    /**
586
     * @return TimeDataCollector|false
587
     */
588
    public static function getTimeCollector()
589
    {
590
        $debugbar = self::getDebugBar();
591
        if (!$debugbar) {
0 ignored issues
show
introduced by Thomas
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
592
            return false;
593
        }
594
        return $debugbar->getCollector('time');
595
    }
596
597
    /**
598
     * @return MessagesCollector|false
599
     */
600
    public static function getMessageCollector()
601
    {
602
        $debugbar = self::getDebugBar();
603
        if (!$debugbar) {
0 ignored issues
show
introduced by Thomas
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
604
            return false;
605
        }
606
        return  $debugbar->getCollector('messages');
607
    }
608
609
    /**
610
     * Start/stop time tracking (also before init)
611
     *
612
     * @param string $label
613
     * @return void
614
     */
615
    public static function trackTime($label)
616
    {
617
        if (!isset(self::$extraTimes[$label])) {
618
            self::$extraTimes[$label] = [microtime(true)];
619
        } else {
620
            self::$extraTimes[$label][] = microtime(true);
621
622
            // If we have the debugbar instance, add the measure
623
            if (self::$debugbar) {
624
                $timeData = self::getTimeCollector();
625
                if (!$timeData) {
0 ignored issues
show
introduced by Thomas
$timeData is of type LeKoala\DebugBar\Collector\TimeDataCollector, thus it always evaluated to true.
Loading history...
626
                    return;
627
                }
628
                $values = self::$extraTimes[$label];
629
                $timeData->addMeasure(
630
                    $label,
631
                    $values[0],
632
                    $values[1]
633
                );
634
                unset(self::$extraTimes[$label]);
635
            }
636
        }
637
    }
638
639
    /**
640
     * Close any open extra time record
641
     *
642
     * @return void
643
     */
644
    public static function closeExtraTime()
645
    {
646
        foreach (self::$extraTimes as $label => $values) {
647
            if (!isset($values[1])) {
648
                self::$extraTimes[$label][] = microtime(true);
649
            }
650
        }
651
    }
652
653
    /**
654
     * Add extra time to time collector
655
     */
656
    public static function measureExtraTime()
657
    {
658
        $timeData = self::getTimeCollector();
659
        if (!$timeData) {
0 ignored issues
show
introduced by Thomas
$timeData is of type LeKoala\DebugBar\Collector\TimeDataCollector, thus it always evaluated to true.
Loading history...
660
            return;
661
        }
662
        foreach (self::$extraTimes as $label => $values) {
663
            if (!isset($values[1])) {
664
                continue; // unfinished measure
665
            }
666
            $timeData->addMeasure(
667
                $label,
668
                $values[0],
669
                $values[1]
670
            );
671
            unset(self::$extraTimes[$label]);
672
        }
673
    }
674
}
675