Completed
Push — master ( abafe4...c1f952 )
by Thomas
187:31 queued 126:26
created

DebugBar::closeExtraTime()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 5
rs 10
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
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
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
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
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
$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()) {
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 isDisabled()
477
    {
478
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
479
            return true;
480
        }
481
        return false;
482
    }
483
484
    public static function isDevUrl()
485
    {
486
        return strpos(self::getRequestUrl(), '/dev/') === 0;
487
    }
488
489
    public static function isAdminUrl()
490
    {
491
        $baseUrl = rtrim(BASE_URL, '/');
492
        if (class_exists(AdminRootController::class)) {
493
            $adminUrl = AdminRootController::config()->get('url_base');
494
        } else {
495
            $adminUrl = 'admin';
496
        }
497
498
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
499
    }
500
501
    public static function isAdminController()
502
    {
503
        if (Controller::curr()) {
504
            return Controller::curr() instanceof LeftAndMain;
505
        }
506
        return self::isAdminUrl();
507
    }
508
509
    /**
510
     * Avoid triggering data collection for open handler
511
     *
512
     * @return boolean
513
     */
514
    public static function isDebugBarRequest()
515
    {
516
        if ($url = self::getRequestUrl()) {
517
            return strpos($url, '/__debugbar') === 0;
518
        }
519
        return true;
520
    }
521
522
    /**
523
     * Get request url
524
     *
525
     * @return string
526
     */
527
    public static function getRequestUrl()
528
    {
529
        if (isset($_REQUEST['url'])) {
530
            return $_REQUEST['url'];
531
        }
532
        if (isset($_SERVER['REQUEST_URI'])) {
533
            return $_SERVER['REQUEST_URI'];
534
        }
535
        return '';
536
    }
537
538
    /**
539
     * Helper to make code cleaner
540
     *
541
     * @param callable $callback
542
     */
543
    public static function withDebugBar($callback)
544
    {
545
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
546
            $callback(self::getDebugBar());
547
        }
548
    }
549
550
    /**
551
     * Set the current request. Is provided by the DebugBarMiddleware.
552
     *
553
     * @param HTTPRequest $request
554
     */
555
    public static function setRequest(HTTPRequest $request)
556
    {
557
        self::$request = $request;
558
    }
559
560
    /**
561
     * Get the current request
562
     *
563
     * @return HTTPRequest
564
     */
565
    public static function getRequest()
566
    {
567
        if (self::$request) {
568
            return self::$request;
569
        }
570
        // Fall back to trying from the global state
571
        if (Controller::has_curr()) {
572
            return Controller::curr()->getRequest();
573
        }
574
    }
575
576
    /**
577
     * @return TimeDataCollector|false
578
     */
579
    public static function getTimeCollector()
580
    {
581
        $debugbar = self::getDebugBar();
582
        if (!$debugbar) {
0 ignored issues
show
introduced by
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
583
            return false;
584
        }
585
        return $debugbar->getCollector('time');
586
    }
587
588
    /**
589
     * @return MessagesCollector|false
590
     */
591
    public static function getMessageCollector()
592
    {
593
        $debugbar = self::getDebugBar();
594
        if (!$debugbar) {
0 ignored issues
show
introduced by
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
595
            return false;
596
        }
597
        return  $debugbar->getCollector('messages');
598
    }
599
600
    /**
601
     * Start/stop time tracking (also before init)
602
     *
603
     * @param string $label
604
     * @return void
605
     */
606
    public static function trackTime($label)
607
    {
608
        if (!isset(self::$extraTimes[$label])) {
609
            self::$extraTimes[$label] = [microtime(true)];
610
        } else {
611
            self::$extraTimes[$label][] = microtime(true);
612
613
            // If we have the debugbar instance, add the measure
614
            if (self::$debugbar) {
615
                $timeData = self::getTimeCollector();
616
                if (!$timeData) {
0 ignored issues
show
introduced by
$timeData is of type LeKoala\DebugBar\Collector\TimeDataCollector, thus it always evaluated to true.
Loading history...
617
                    return;
618
                }
619
                $values = self::$extraTimes[$label];
620
                $timeData->addMeasure(
621
                    $label,
622
                    $values[0],
623
                    $values[1]
624
                );
625
                unset(self::$extraTimes[$label]);
626
            }
627
        }
628
    }
629
630
    /**
631
     * Close any open extra time record
632
     *
633
     * @return void
634
     */
635
    public static function closeExtraTime()
636
    {
637
        foreach (self::$extraTimes as $label => $values) {
638
            if (!isset($values[1])) {
639
                self::$extraTimes[$label][] = microtime(true);
640
            }
641
        }
642
    }
643
644
    /**
645
     * Add extra time to time collector
646
     */
647
    public static function measureExtraTime()
648
    {
649
        $timeData = self::getTimeCollector();
650
        if (!$timeData) {
0 ignored issues
show
introduced by
$timeData is of type LeKoala\DebugBar\Collector\TimeDataCollector, thus it always evaluated to true.
Loading history...
651
            return;
652
        }
653
        foreach (self::$extraTimes as $label => $values) {
654
            if (!isset($values[1])) {
655
                continue; // unfinished measure
656
            }
657
            $timeData->addMeasure(
658
                $label,
659
                $values[0],
660
                $values[1]
661
            );
662
            unset(self::$extraTimes[$label]);
663
        }
664
    }
665
}
666