Issues (63)

code/DebugBar.php (12 issues)

1
<?php
2
3
namespace LeKoala\DebugBar;
4
5
use Exception;
6
use Monolog\Logger;
7
use ReflectionObject;
8
use Psr\Log\LoggerInterface;
9
use SilverStripe\Core\Kernel;
10
use DebugBar\JavascriptRenderer;
11
use DebugBar\Storage\FileStorage;
12
use SilverStripe\Dev\Deprecation;
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 Symfony\Component\Mailer\Mailer;
19
use SilverStripe\Control\HTTPRequest;
20
use DebugBar\DebugBar as BaseDebugBar;
21
use SilverStripe\Core\Injector\Injector;
22
use SilverStripe\Core\Config\ConfigLoader;
23
use SilverStripe\Core\Config\Configurable;
24
use SilverStripe\Core\Injector\Injectable;
25
use DebugBar\DataCollector\MemoryCollector;
26
use LeKoala\DebugBar\Messages\LogFormatter;
27
use SilverStripe\Admin\AdminRootController;
28
use SilverStripe\Core\Manifest\ModuleLoader;
29
use DebugBar\DataCollector\MessagesCollector;
30
use LeKoala\DebugBar\Bridge\MonologCollector;
31
use Symfony\Component\Mailer\MailerInterface;
32
use LeKoala\DebugBar\Collector\CacheCollector;
33
use SilverStripe\Core\Manifest\ModuleResource;
34
use LeKoala\DebugBar\Collector\ConfigCollector;
35
use LeKoala\DebugBar\Proxy\ConfigManifestProxy;
36
use LeKoala\DebugBar\Collector\PhpInfoCollector;
37
use LeKoala\DebugBar\Extension\ProxyDBExtension;
38
use LeKoala\DebugBar\Collector\DatabaseCollector;
39
use LeKoala\DebugBar\Collector\TimeDataCollector;
40
use LeKoala\DebugBar\Proxy\DeltaConfigManifestProxy;
41
use LeKoala\DebugBar\Collector\PartialCacheCollector;
42
use LeKoala\DebugBar\Collector\SilverStripeCollector;
43
use SilverStripe\Config\Collections\DeltaConfigCollection;
44
use SilverStripe\Config\Collections\CachedConfigCollection;
45
use LeKoala\DebugBar\Bridge\SymfonyMailer\SymfonyMailerCollector;
46
use SilverStripe\Security\Permission;
47
48
/**
49
 * A simple helper
50
 */
51
class DebugBar
52
{
53
    use Configurable;
54
    use Injectable;
55
56
    /**
57
     * @var BaseDebugBar|false|null
58
     */
59
    protected static $debugbar;
60
61
    /**
62
     * @var bool
63
     */
64
    public static $bufferingEnabled = false;
65
66
    /**
67
     * @var bool
68
     */
69
    public static $suppressJquery = false;
70
71
    /**
72
     * @var JavascriptRenderer|null
73
     */
74
    protected static $renderer;
75
76
    /**
77
     * @var bool
78
     */
79
    protected static $showQueries = false;
80
81
    /**
82
     * @var HTTPRequest|null
83
     */
84
    protected static $request;
85
86
    /**
87
     * @var array<string,array<float>>
88
     */
89
    protected static $extraTimes = [];
90
91
    /**
92
     * Get the Debug Bar instance
93
     * @throws Exception
94
     * @global array $databaseConfig
95
     * @return BaseDebugBar|false
96
     */
97
    public static function getDebugBar()
98
    {
99
        if (self::$debugbar !== null) {
100
            return self::$debugbar;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::debugbar also could return the type boolean which is incompatible with the documented return type DebugBar\DebugBar|false.
Loading history...
101
        }
102
103
        $reasons = self::disabledCriteria();
104
        if (!empty($reasons)) {
105
            self::$debugbar = false; // no need to check again
106
            return false;
107
        }
108
109
        self::initDebugBar();
110
111
        if (!self::$debugbar) {
112
            throw new Exception("Failed to initialize the DebugBar");
113
        }
114
115
        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|false.
Loading history...
116
    }
117
118
    /**
119
     * Init the debugbar instance
120
     *
121
     * @global array $databaseConfig
122
     * @return BaseDebugBar|null
123
     */
124
    public static function initDebugBar()
125
    {
126
        // Prevent multiple inits
127
        if (self::$debugbar) {
128
            return self::$debugbar;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::debugbar also could return the type true which is incompatible with the documented return type DebugBar\DebugBar|null.
Loading history...
129
        }
130
131
        self::$debugbar = $debugbar = new BaseDebugBar();
132
133
        if (isset($_REQUEST['showqueries']) && Director::isDev()) {
134
            self::setShowQueries(true);
135
            unset($_REQUEST['showqueries']);
136
        }
137
138
        $debugbar->addCollector(new PhpInfoCollector());
139
        $debugbar->addCollector(new TimeDataCollector());
140
        self::measureExtraTime();
141
        $debugbar->addCollector(new MemoryCollector());
142
143
        // Add config proxy replacing the core config manifest
144
        if (self::config()->config_collector) {
145
            /** @var ConfigLoader $configLoader */
146
            $configLoader = Injector::inst()->get(Kernel::class)->getConfigLoader();
147
            // There is no getManifests method on ConfigLoader
148
            $manifests = self::getProtectedValue($configLoader, 'manifests');
149
            foreach ($manifests as $manifestIdx => $manifest) {
150
                if ($manifest instanceof CachedConfigCollection) {
151
                    $manifest = new ConfigManifestProxy($manifest);
152
                    $manifests[$manifestIdx] = $manifest;
153
                }
154
                if ($manifest instanceof DeltaConfigCollection) {
155
                    $manifest = DeltaConfigManifestProxy::createFromOriginal($manifest);
156
                    $manifests[$manifestIdx] = $manifest;
157
                }
158
            }
159
            // Don't push as it may change stack order
160
            self::setProtectedValue($configLoader, 'manifests', $manifests);
161
        }
162
163
        // If enabled and available
164
        if (self::config()->db_collector && class_exists(\TractorCow\SilverStripeProxyDB\ProxyDBFactory::class)) {
165
            $debugbar->addCollector(new DatabaseCollector);
166
        }
167
168
        // Add message collector last so other collectors can send messages to the console using it
169
        $debugbar->addCollector(new MessagesCollector());
170
171
        // Aggregate monolog into messages
172
        $logger = Injector::inst()->get(LoggerInterface::class);
173
        if ($logger instanceof Logger) {
174
            $logCollector = new MonologCollector($logger);
175
            $logCollector->setFormatter(new LogFormatter);
176
            $debugbar['messages']->aggregate($logCollector);
0 ignored issues
show
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

176
            $debugbar['messages']->/** @scrutinizer ignore-call */ 
177
                                   aggregate($logCollector);
Loading history...
177
        }
178
179
        // Add some SilverStripe specific infos
180
        $debugbar->addCollector(new SilverStripeCollector);
181
182
        if (self::config()->get('enable_storage')) {
183
            $debugBarTempFolder = \TEMP_PATH . '/debugbar';
184
            $debugbar->setStorage($fileStorage = new FileStorage($debugBarTempFolder));
185
            if (isset($_GET['flush']) && is_dir($debugBarTempFolder)) {
186
                // FileStorage::clear() is implemented with \DirectoryIterator which throws UnexpectedValueException if dir can not be opened
187
                $fileStorage->clear();
188
            }
189
        }
190
191
        if (self::config()->config_collector) {
192
            // Add the config collector
193
            $debugbar->addCollector(new ConfigCollector);
194
        }
195
196
        // Cache
197
        if (self::config()->cache_collector) {
198
            $debugbar->addCollector($cacheCollector = new CacheCollector);
199
            $cacheCollector->setShowGet(self::config()->cache_collector_show_get);
200
        }
201
202
        // Partial cache
203
        if (self::config()->partial_cache_collector) {
204
            $debugbar->addCollector(new PartialCacheCollector);
205
        }
206
207
        // Email logging
208
        if (self::config()->email_collector) {
209
            $mailer = Injector::inst()->get(MailerInterface::class);
210
            if ($mailer instanceof Mailer) {
211
                $debugbar->addCollector(new SymfonyMailerCollector);
212
            }
213
        }
214
215
        // Since we buffer everything, why not enable all dev options ?
216
        if (self::config()->get('auto_debug')) {
217
            $_REQUEST['debug'] = true;
218
            $_REQUEST['debug_request'] = true;
219
        }
220
221
        if (isset($_REQUEST['debug']) || isset($_REQUEST['debug_request'])) {
222
            self::$bufferingEnabled = true;
223
            ob_start(); // We buffer everything until we have called an action
224
        }
225
226
        return $debugbar;
227
    }
228
229
    /**
230
     * Access a protected property when the api does not allow access
231
     *
232
     * @param object $object
233
     * @param string $property
234
     * @return mixed
235
     */
236
    protected static function getProtectedValue($object, $property)
237
    {
238
        $refObject = new ReflectionObject($object);
239
        $refProperty = $refObject->getProperty($property);
240
        $refProperty->setAccessible(true);
241
        return $refProperty->getValue($object);
242
    }
243
244
    /**
245
     * Set a protected property when the api does not allow access
246
     *
247
     * @param object $object
248
     * @param string $property
249
     * @param mixed $newValue
250
     * @return void
251
     */
252
    protected static function setProtectedValue($object, $property, $newValue)
253
    {
254
        $refObject = new ReflectionObject($object);
255
        $refProperty = $refObject->getProperty($property);
256
        $refProperty->setAccessible(true);
257
        $refProperty->setValue($object, $newValue);
258
    }
259
260
    /**
261
     * Clear the current instance of DebugBar
262
     *
263
     * @return void
264
     */
265
    public static function clearDebugBar()
266
    {
267
        self::$debugbar = null;
268
        self::$bufferingEnabled = false;
269
        self::$renderer = null;
270
        self::$showQueries = false;
271
        self::$request = null;
272
        self::$extraTimes = [];
273
        ProxyDBExtension::resetQueries();
274
    }
275
276
    /**
277
     * @return boolean
278
     */
279
    public static function getShowQueries()
280
    {
281
        return self::$showQueries;
282
    }
283
284
    /**
285
     * Override default showQueries mode
286
     *
287
     * @param boolean $showQueries
288
     * @return void
289
     */
290
    public static function setShowQueries($showQueries)
291
    {
292
        self::$showQueries = $showQueries;
293
    }
294
295
    /**
296
     * Helper to access this module resources
297
     *
298
     * @param string $path
299
     * @return ModuleResource
300
     */
301
    public static function moduleResource($path)
302
    {
303
        return ModuleLoader::getModule('lekoala/silverstripe-debugbar')->getResource($path);
304
    }
305
306
    /**
307
     * @param bool $flag
308
     * @return void
309
     */
310
    public static function suppressJquery($flag = true)
311
    {
312
        $file = "debugbar/assets/vendor/jquery/dist/jquery.min.js";
313
        if ($flag) {
314
            Requirements::block($file);
315
        } else {
316
            Requirements::unblock($file);
317
        }
318
319
        self::$suppressJquery = $flag;
320
    }
321
322
    /**
323
     * Include DebugBar assets using Requirements API
324
     * This needs to be called before the template is rendered otherwise the calls to the Requirements API are ignored
325
     *
326
     * @return bool
327
     */
328
    public static function includeRequirements()
329
    {
330
        // Already called
331
        if (self::$renderer) {
332
            return false;
333
        }
334
335
        $renderer = self::getConfiguredRenderer();
336
        if (!$renderer) {
0 ignored issues
show
$renderer is of type DebugBar\JavascriptRenderer, thus it always evaluated to true.
Loading history...
337
            return false;
338
        }
339
340
        foreach ($renderer->getAssets('css') as $cssFile) {
341
            Requirements::css(self::replaceAssetPath($cssFile));
342
        }
343
344
        foreach ($renderer->getAssets('js') as $jsFile) {
345
            Requirements::javascript(self::replaceAssetPath($jsFile), [
346
                'type' => 'application/javascript'
347
            ]);
348
        }
349
350
        // Store instance to avoid calling this multiple times
351
        self::$renderer = $renderer;
352
353
        return true;
354
    }
355
356
    /**
357
     * @return void
358
     */
359
    public static function excludeRequirements()
360
    {
361
        $renderer = self::getConfiguredRenderer();
362
        if (!$renderer) {
0 ignored issues
show
$renderer is of type DebugBar\JavascriptRenderer, thus it always evaluated to true.
Loading history...
363
            return;
364
        }
365
        foreach ($renderer->getAssets('css') as $cssFile) {
366
            Requirements::block(self::replaceAssetPath($cssFile));
367
        }
368
369
        foreach ($renderer->getAssets('js') as $jsFile) {
370
            Requirements::block(self::replaceAssetPath($jsFile), [
0 ignored issues
show
The call to SilverStripe\View\Requirements::block() has too many arguments starting with array('type' => 'application/javascript'). ( Ignorable by Annotation )

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

370
            Requirements::/** @scrutinizer ignore-call */ 
371
                          block(self::replaceAssetPath($jsFile), [

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
371
                'type' => 'application/javascript'
372
            ]);
373
        }
374
    }
375
376
    /**
377
     * @return \DebugBar\JavascriptRenderer|null
378
     */
379
    protected static function getConfiguredRenderer()
380
    {
381
        $debugbar = self::getDebugBar();
382
        if (!$debugbar) {
0 ignored issues
show
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
383
            return null;
384
        }
385
        $renderer = $debugbar->getJavascriptRenderer();
386
387
        // We don't need the true path since we are going to use Requirements API that appends the BASE_PATH
388
        $assetsResource = self::moduleResource('assets');
389
        $renderer->setBasePath($assetsResource->getRelativePath());
390
        $renderer->setBaseUrl(Director::makeRelative($assetsResource->getURL()));
391
392
        $includeJquery = self::shouldIncludeJQuery();
393
394
        if ($includeJquery) {
395
            $renderer->setEnableJqueryNoConflict(true);
396
        } else {
397
            $renderer->disableVendor('jquery');
398
            $renderer->setEnableJqueryNoConflict(false);
399
        }
400
401
        if (DebugBar::config()->get('enable_storage')) {
402
            $renderer->setOpenHandlerUrl('__debugbar');
403
        }
404
        return $renderer;
405
    }
406
407
    /**
408
     * @return bool
409
     */
410
    protected static function shouldIncludeJQuery()
411
    {
412
        $includeJquery = self::config()->get('include_jquery') ?? true;
413
        // In CMS, jQuery is already included
414
        if (self::isAdminController()) {
415
            $includeJquery = false;
416
        }
417
        // If jQuery is already included, set to false
418
        $js = Requirements::backend()->getJavascript();
419
        foreach ($js as $url => $args) {
420
            $name = basename($url);
421
            if ($name == 'jquery.js' || $name == 'jquery.min.js') {
422
                $includeJquery = false;
423
                break;
424
            }
425
        }
426
        return $includeJquery;
427
    }
428
429
    /**
430
     * @param string $file
431
     * @return string
432
     */
433
    protected static function replaceAssetPath($file)
434
    {
435
        return Director::makeRelative(str_replace('\\', '/', ltrim($file, '/')));
436
    }
437
438
    /**
439
     * Returns the script to display the DebugBar
440
     *
441
     * @return string
442
     */
443
    public static function renderDebugBar()
444
    {
445
        if (!self::$renderer) {
446
            return '';
447
        }
448
449
        // If we have any extra time pending, add it
450
        if (!empty(self::$extraTimes)) {
451
            foreach (self::$extraTimes as $extraTime => $extraTimeData) {
452
                self::trackTime($extraTime);
453
            }
454
        }
455
456
        // Requirements may have been cleared (CMS iframes...) or not set
457
        $js = Requirements::backend()->getJavascript();
458
        $debugBarResource = self::moduleResource('assets/debugbar.js');
459
        $path = $debugBarResource->getRelativePath();
460
461
        // Url in getJavascript has a / slash, so fix if necessary
462
        $path = str_replace("\\", "/", $path);
463
        if (!array_key_exists($path, $js)) {
464
            return '';
465
        }
466
        $initialize = true;
467
        if (Director::is_ajax()) {
468
            $initialize = false;
469
        }
470
471
        // Normally deprecation notices are output in a shutdown function, which runs well after debugbar has rendered.
472
        // This ensures the deprecation notices which have been noted up to this point are logged out and collected by
473
        // the MonologCollector.
474
        Deprecation::outputNotices();
475
476
        $script = self::$renderer->render($initialize);
477
478
        return $script;
479
    }
480
481
    /**
482
     * Get all criteria why the DebugBar could be disabled
483
     *
484
     * @return array<string>
485
     */
486
    public static function disabledCriteria()
487
    {
488
        $reasons = [];
489
        if (!Director::isDev() && !self::allowAllEnvironments()) {
490
            $reasons[] = 'Not in dev mode';
491
        }
492
        if (self::isDisabled()) {
493
            $reasons[] = 'Disabled by a constant or configuration';
494
        }
495
        if (self::vendorNotInstalled()) {
496
            $reasons[] = 'DebugBar is not installed in vendors';
497
        }
498
        if (self::notLocalIp()) {
499
            $reasons[] = 'Not a local ip';
500
        }
501
        if (Director::is_cli()) {
502
            $reasons[] = 'In CLI mode';
503
        }
504
        if (self::isDevUrl()) {
505
            $reasons[] = 'Dev tools';
506
        }
507
        if (self::isAdminUrl() && !self::config()->get('enabled_in_admin')) {
508
            $reasons[] = 'In admin';
509
        }
510
        if (isset($_GET['CMSPreview'])) {
511
            $reasons[] = 'CMS Preview';
512
        }
513
        if (self::isExcludedRoute()) {
514
            $reasons[] = 'Route excluded';
515
        }
516
        if (!self::hasRequiredPermissions()) {
517
            $reasons[] = 'Not allowed';
518
        }
519
        return $reasons;
520
    }
521
522
    /**
523
     * Determine why DebugBar is disabled
524
     *
525
     * Deprecated in favor of disabledCriteria
526
     *
527
     * @return string
528
     */
529
    public static function whyDisabled()
530
    {
531
        $reasons = self::disabledCriteria();
532
        if (!empty($reasons)) {
533
            return $reasons[0];
534
        }
535
        return "I don't know why";
536
    }
537
538
    /**
539
     * @return bool
540
     */
541
    public static function vendorNotInstalled()
542
    {
543
        return !class_exists('DebugBar\\StandardDebugBar');
544
    }
545
546
    /**
547
     * @return bool
548
     */
549
    public static function notLocalIp()
550
    {
551
        if (!self::config()->get('check_local_ip')) {
552
            return false;
553
        }
554
        if (isset($_SERVER['REMOTE_ADDR'])) {
555
            return !in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1', '1']);
556
        }
557
        return false;
558
    }
559
560
    /**
561
     * @return bool
562
     */
563
    public static function allowAllEnvironments()
564
    {
565
        // You will also need to add a debugbar-live config
566
        if (Environment::getEnv('DEBUGBAR_ALLOW_ALL_ENV')) {
567
            return true;
568
        }
569
        return false;
570
    }
571
572
    /**
573
     * @return bool
574
     */
575
    public static function isDisabled()
576
    {
577
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
578
            return true;
579
        }
580
        return false;
581
    }
582
583
    /**
584
     * @return bool
585
     */
586
    public static function hasRequiredPermissions()
587
    {
588
        if (static::config()->get('user_admin_only')) {
589
            return Permission::check('ADMIN');
590
        }
591
        return true;
592
    }
593
594
    /**
595
     * @return bool
596
     */
597
    public static function isDevUrl()
598
    {
599
        return strpos(self::getRequestUrl(), '/dev/') === 0;
600
    }
601
602
    /**
603
     * @return bool
604
     */
605
    public static function isAdminUrl()
606
    {
607
        $baseUrl = rtrim(BASE_URL, '/');
608
        if (class_exists(AdminRootController::class)) {
609
            $adminUrl = AdminRootController::config()->get('url_base');
610
        } else {
611
            $adminUrl = 'admin';
612
        }
613
614
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
615
    }
616
617
    /**
618
     * @return bool
619
     */
620
    public static function isExcludedRoute()
621
    {
622
        $isExcluded = false;
623
        $excludedRoutes = self::config()->get('excluded_routes');
624
        if (!empty($excludedRoutes)) {
625
            $url = self::getRequestUrl();
626
            foreach ($excludedRoutes as $excludedRoute) {
627
                if (strpos($url, (string) $excludedRoute) === 0) {
628
                    $isExcluded = true;
629
                    break;
630
                }
631
            }
632
        }
633
        return $isExcluded;
634
    }
635
636
    /**
637
     * @return bool
638
     */
639
    public static function isAdminController()
640
    {
641
        if (Controller::curr()) {
642
            return Controller::curr() instanceof LeftAndMain;
643
        }
644
        return self::isAdminUrl();
645
    }
646
647
    /**
648
     * Avoid triggering data collection for open handler
649
     *
650
     * @return boolean
651
     */
652
    public static function isDebugBarRequest()
653
    {
654
        if ($url = self::getRequestUrl()) {
655
            return strpos($url, '/__debugbar') === 0;
656
        }
657
        return true;
658
    }
659
660
    /**
661
     * Get request url
662
     *
663
     * @return string
664
     */
665
    public static function getRequestUrl()
666
    {
667
        if (isset($_REQUEST['url'])) {
668
            return $_REQUEST['url'];
669
        }
670
        if (isset($_SERVER['REQUEST_URI'])) {
671
            return $_SERVER['REQUEST_URI'];
672
        }
673
        return '';
674
    }
675
676
    /**
677
     * Helper to make code cleaner
678
     *
679
     * @param callable $callback
680
     * @return void
681
     */
682
    public static function withDebugBar($callback)
683
    {
684
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
685
            $callback(self::getDebugBar());
686
        }
687
    }
688
689
    /**
690
     * Set the current request. Is provided by the DebugBarMiddleware.
691
     *
692
     * @param HTTPRequest $request
693
     * @return void
694
     */
695
    public static function setRequest(HTTPRequest $request)
696
    {
697
        self::$request = $request;
698
    }
699
700
    /**
701
     * Get the current request
702
     *
703
     * @return HTTPRequest|null
704
     */
705
    public static function getRequest()
706
    {
707
        if (self::$request) {
708
            return self::$request;
709
        }
710
        // Fall back to trying from the global state
711
        if (Controller::curr()) {
712
            return Controller::curr()->getRequest();
713
        }
714
        return null;
715
    }
716
717
    /**
718
     * @return TimeDataCollector|false
719
     */
720
    public static function getTimeCollector()
721
    {
722
        $debugbar = self::getDebugBar();
723
        if (!$debugbar) {
0 ignored issues
show
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
724
            return false;
725
        }
726
        //@phpstan-ignore-next-line
727
        return $debugbar->getCollector('time');
728
    }
729
730
    /**
731
     * @return MessagesCollector|false
732
     */
733
    public static function getMessageCollector()
734
    {
735
        $debugbar = self::getDebugBar();
736
        if (!$debugbar) {
0 ignored issues
show
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
737
            return false;
738
        }
739
        //@phpstan-ignore-next-line
740
        return  $debugbar->getCollector('messages');
741
    }
742
743
    /**
744
     * Start/stop time tracking (also before init)
745
     *
746
     * @param string $label
747
     * @return void
748
     */
749
    public static function trackTime($label)
750
    {
751
        if (!isset(self::$extraTimes[$label])) {
752
            self::$extraTimes[$label] = [microtime(true)];
753
        } else {
754
            self::$extraTimes[$label][] = microtime(true);
755
756
            // If we have the debugbar instance, add the measure
757
            if (self::$debugbar) {
758
                $timeData = self::getTimeCollector();
759
                if (!$timeData) {
0 ignored issues
show
$timeData is of type LeKoala\DebugBar\Collector\TimeDataCollector, thus it always evaluated to true.
Loading history...
760
                    return;
761
                }
762
                $values = self::$extraTimes[$label];
763
                $timeData->addMeasure(
764
                    $label,
765
                    $values[0],
766
                    $values[1]
767
                );
768
                unset(self::$extraTimes[$label]);
769
            }
770
        }
771
    }
772
773
    /**
774
     * Close any open extra time record
775
     *
776
     * @return void
777
     */
778
    public static function closeExtraTime()
779
    {
780
        foreach (self::$extraTimes as $label => $values) {
781
            if (!isset($values[1])) {
782
                self::$extraTimes[$label][] = microtime(true);
783
            }
784
        }
785
    }
786
787
    /**
788
     * Add extra time to time collector
789
     * @return void
790
     */
791
    public static function measureExtraTime()
792
    {
793
        $timeData = self::getTimeCollector();
794
        if (!$timeData) {
0 ignored issues
show
$timeData is of type LeKoala\DebugBar\Collector\TimeDataCollector, thus it always evaluated to true.
Loading history...
795
            return;
796
        }
797
        foreach (self::$extraTimes as $label => $values) {
798
            if (!isset($values[1])) {
799
                continue; // unfinished measure
800
            }
801
            $timeData->addMeasure(
802
                $label,
803
                $values[0],
804
                $values[1]
805
            );
806
            unset(self::$extraTimes[$label]);
807
        }
808
    }
809
}
810