Passed
Push — master ( 81b9da...c78459 )
by Thomas
02:58 queued 13s
created

DebugBar::getTimeCollector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Collector\DatabaseCollector;
40
use LeKoala\DebugBar\Collector\TimeDataCollector;
41
use DebugBar\Bridge\SwiftMailer\SwiftLogCollector;
42
use DebugBar\Bridge\SwiftMailer\SwiftMailCollector;
43
use LeKoala\DebugBar\Collector\PartialCacheCollector;
44
use LeKoala\DebugBar\Collector\SilverStripeCollector;
45
use LeKoala\DebugBar\Middleware\DebugBarConfigMiddleware;
0 ignored issues
show
Bug introduced by Thomas
The type LeKoala\DebugBar\Middlew...ebugBarConfigMiddleware 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...
46
use LeKoala\DebugBar\Proxy\DeltaConfigManifestProxy;
47
use SilverStripe\Config\Collections\CachedConfigCollection;
48
use SilverStripe\Config\Collections\DeltaConfigCollection;
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
    protected static $extraTimes = [];
84
85
    /**
86
     * Get the Debug Bar instance
87
     * @throws Exception
88
     * @global array $databaseConfig
89
     * @return BaseDebugBar
90
     */
91
    public static function getDebugBar()
92
    {
93
        if (self::$debugbar !== null) {
94
            return self::$debugbar;
95
        }
96
97
        $reasons = self::disabledCriteria();
98
        if (!empty($reasons)) {
99
            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...
100
            return;
101
        }
102
103
        self::initDebugBar();
104
105
        if (!self::$debugbar) {
106
            throw new Exception("Failed to initialize the DebugBar");
107
        }
108
109
        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...
110
    }
111
112
    /**
113
     * Init the debugbar instance
114
     *
115
     * @global array $databaseConfig
116
     * @return BaseDebugBar|null
117
     */
118
    public static function initDebugBar()
119
    {
120
        // Prevent multiple inits
121
        if (self::$debugbar) {
122
            return self::$debugbar;
123
        }
124
125
        self::$debugbar = $debugbar = new BaseDebugBar();
126
127
        if (isset($_REQUEST['showqueries']) && Director::isDev()) {
128
            self::setShowQueries(true);
129
            unset($_REQUEST['showqueries']);
130
        }
131
132
        $debugbar->addCollector(new PhpInfoCollector());
133
        $debugbar->addCollector(new TimeDataCollector());
134
        self::measureExtraTime();
135
        $debugbar->addCollector(new MemoryCollector());
136
137
        // Add config proxy replacing the core config manifest
138
        if (self::config()->config_collector) {
139
            /** @var ConfigLoader $configLoader */
140
            $configLoader = Injector::inst()->get(Kernel::class)->getConfigLoader();
141
            // There is no getManifests method on ConfigLoader
142
            $manifests = self::getProtectedValue($configLoader, 'manifests');
143
            foreach ($manifests as $manifestIdx => $manifest) {
144
                if ($manifest instanceof CachedConfigCollection) {
145
                    $manifest = new ConfigManifestProxy($manifest);
146
                    $manifests[$manifestIdx] = $manifest;
147
                }
148
                if ($manifest instanceof DeltaConfigCollection) {
149
                    $manifest = DeltaConfigManifestProxy::createFromOriginal($manifest);
150
                    $manifests[$manifestIdx] = $manifest;
151
                }
152
            }
153
            // Don't push as it may change stack order
154
            self::setProtectedValue($configLoader, 'manifests', $manifests);
155
        }
156
157
        if (self::config()->db_collector) {
158
            $connector = DB::get_connector();
159
            if (!self::config()->get('force_proxy') && $connector instanceof PDOConnector) {
160
                // Use a little bit of magic to replace the pdo instance
161
                $refObject = new ReflectionObject($connector);
162
                $refProperty = $refObject->getProperty('pdoConnection');
163
                $refProperty->setAccessible(true);
164
                $traceablePdo = new TraceablePDO($refProperty->getValue($connector));
165
                $refProperty->setValue($connector, $traceablePdo);
166
167
                $debugbar->addCollector(new PDOCollector($traceablePdo));
168
            } else {
169
                $debugbar->addCollector(new DatabaseCollector);
170
            }
171
        }
172
173
        // Add message collector last so other collectors can send messages to the console using it
174
        $debugbar->addCollector(new MessagesCollector());
175
176
        // Aggregate monolog into messages
177
        $logger = Injector::inst()->get(LoggerInterface::class);
178
        if ($logger instanceof Logger) {
179
            $logCollector = new MonologCollector($logger);
180
            $logCollector->setFormatter(new LogFormatter);
181
            $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

181
            $debugbar['messages']->/** @scrutinizer ignore-call */ 
182
                                   aggregate($logCollector);
Loading history...
182
        }
183
184
        // Add some SilverStripe specific infos
185
        $debugbar->addCollector(new SilverStripeCollector);
186
187
        if (self::config()->get('enable_storage')) {
188
            $debugBarTempFolder = TEMP_FOLDER . '/debugbar';
189
            $debugbar->setStorage($fileStorage = new FileStorage($debugBarTempFolder));
190
            if (isset($_GET['flush']) && is_dir($debugBarTempFolder)) {
191
                // FileStorage::clear() is implemented with \DirectoryIterator which throws UnexpectedValueException if dir can not be opened
192
                $fileStorage->clear();
193
            }
194
        }
195
196
        if (self::config()->config_collector) {
197
            // Add the config collector
198
            $debugbar->addCollector(new ConfigCollector);
199
        }
200
201
        // Partial cache
202
        if (self::config()->partial_cache_collector) {
203
            $debugbar->addCollector(new PartialCacheCollector);
204
        }
205
206
        // Email logging
207
        if (self::config()->email_collector) {
208
            $mailer = Injector::inst()->get(Mailer::class);
209
            if ($mailer instanceof SwiftMailer) {
210
                $swiftInst = $mailer->getSwiftMailer();
211
                $debugbar['messages']->aggregate(new SwiftLogCollector($swiftInst));
212
                $debugbar->addCollector(new SwiftMailCollector($swiftInst));
213
            }
214
        }
215
216
        // Since we buffer everything, why not enable all dev options ?
217
        if (self::config()->get('auto_debug')) {
218
            $_REQUEST['debug'] = true;
219
            $_REQUEST['debug_request'] = true;
220
        }
221
222
        if (isset($_REQUEST['debug']) || isset($_REQUEST['debug_request'])) {
223
            self::$bufferingEnabled = true;
224
            ob_start(); // We buffer everything until we have called an action
225
        }
226
227
        return $debugbar;
228
    }
229
230
    /**
231
     * Access a protected property when the api does not allow access
232
     *
233
     * @param object $object
234
     * @param string $property
235
     * @return mixed
236
     */
237
    protected static function getProtectedValue($object, $property)
238
    {
239
        $refObject = new ReflectionObject($object);
240
        $refProperty = $refObject->getProperty($property);
241
        $refProperty->setAccessible(true);
242
        return $refProperty->getValue($object);
243
    }
244
245
    /**
246
     * Set a protected property when the api does not allow access
247
     *
248
     * @param object $object
249
     * @param string $property
250
     * @param mixed $newValue
251
     * @return void
252
     */
253
    protected static function setProtectedValue($object, $property, $newValue)
254
    {
255
        $refObject = new ReflectionObject($object);
256
        $refProperty = $refObject->getProperty($property);
257
        $refProperty->setAccessible(true);
258
        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...
259
    }
260
261
    /**
262
     * Clear the current instance of DebugBar
263
     *
264
     * @return void
265
     */
266
    public static function clearDebugBar()
267
    {
268
        self::$debugbar = null;
269
    }
270
271
    /**
272
     * @return boolean
273
     */
274
    public static function getShowQueries()
275
    {
276
        return self::$showQueries;
277
    }
278
279
    /**
280
     * Override default showQueries mode
281
     *
282
     * @param boolean $showQueries
283
     * @return void
284
     */
285
    public static function setShowQueries($showQueries)
286
    {
287
        self::$showQueries = $showQueries;
288
    }
289
290
    /**
291
     * Helper to access this module resources
292
     *
293
     * @param string $path
294
     * @return ModuleResource
295
     */
296
    public static function moduleResource($path)
297
    {
298
        return ModuleLoader::getModule('lekoala/silverstripe-debugbar')->getResource($path);
299
    }
300
301
    /**
302
     * Include DebugBar assets using Requirements API
303
     *
304
     * @return void
305
     */
306
    public static function includeRequirements()
307
    {
308
        $debugbar = self::getDebugBar();
309
310
        if (!$debugbar) {
0 ignored issues
show
introduced by Thomas
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
311
            return;
312
        }
313
314
        // Already called
315
        if (self::$renderer) {
316
            return;
317
        }
318
319
        $renderer = $debugbar->getJavascriptRenderer();
320
321
        // We don't need the true path since we are going to use Requirements API that appends the BASE_PATH
322
        $assetsResource = self::moduleResource('assets');
323
        $renderer->setBasePath($assetsResource->getRelativePath());
324
        $renderer->setBaseUrl(Director::makeRelative($assetsResource->getURL()));
325
326
        $includeJquery = self::config()->get('include_jquery');
327
        // In CMS, jQuery is already included
328
        if (self::isAdminController()) {
329
            $includeJquery = false;
330
        }
331
        // If jQuery is already included, set to false
332
        $js = Requirements::backend()->getJavascript();
333
        foreach ($js as $url => $args) {
334
            $name = basename($url);
335
            if ($name == 'jquery.js' || $name == 'jquery.min.js') {
336
                $includeJquery = false;
337
                break;
338
            }
339
        }
340
341
        if ($includeJquery) {
342
            $renderer->setEnableJqueryNoConflict(true);
343
        } else {
344
            $renderer->disableVendor('jquery');
345
            $renderer->setEnableJqueryNoConflict(false);
346
        }
347
348
        if (DebugBar::config()->get('enable_storage')) {
349
            $renderer->setOpenHandlerUrl('__debugbar');
350
        }
351
352
        foreach ($renderer->getAssets('css') as $cssFile) {
353
            Requirements::css(Director::makeRelative(ltrim($cssFile, '/')));
354
        }
355
356
        foreach ($renderer->getAssets('js') as $jsFile) {
357
            Requirements::javascript(Director::makeRelative(ltrim($jsFile, '/')));
358
        }
359
360
        self::$renderer = $renderer;
361
    }
362
363
    /**
364
     * Returns the script to display the DebugBar
365
     *
366
     * @return string
367
     */
368
    public static function renderDebugBar()
369
    {
370
        if (!self::$renderer) {
371
            return;
372
        }
373
374
        // Requirements may have been cleared (CMS iframes...) or not set (Security...)
375
        $js = Requirements::backend()->getJavascript();
376
        $debugBarResource = self::moduleResource('assets/debugbar.js');
377
        $path = $debugBarResource->getRelativePath();
378
379
        // Url in getJavascript has a / slash, so fix if necessary
380
        $path = str_replace("assets\\debugbar.js", "assets/debugbar.js", $path);
381
        if (!array_key_exists($path, $js)) {
382
            return;
383
        }
384
        $initialize = true;
385
        if (Director::is_ajax()) {
386
            $initialize = false;
387
        }
388
389
        $script = self::$renderer->render($initialize);
390
        return $script;
391
    }
392
393
    /**
394
     * Get all criteria why the DebugBar could be disabled
395
     *
396
     * @return array
397
     */
398
    public static function disabledCriteria()
399
    {
400
        $reasons = array();
401
        if (!Director::isDev()) {
402
            $reasons[] = 'Not in dev mode';
403
        }
404
        if (self::isDisabled()) {
405
            $reasons[] = 'Disabled by a constant or configuration';
406
        }
407
        if (self::vendorNotInstalled()) {
408
            $reasons[] = 'DebugBar is not installed in vendors';
409
        }
410
        if (self::notLocalIp()) {
411
            $reasons[] = 'Not a local ip';
412
        }
413
        if (Director::is_cli()) {
414
            $reasons[] = 'In CLI mode';
415
        }
416
        if (self::isDevUrl()) {
417
            $reasons[] = 'Dev tools';
418
        }
419
        if (self::isAdminUrl() && !self::config()->get('enabled_in_admin')) {
420
            $reasons[] = 'In admin';
421
        }
422
        if (isset($_GET['CMSPreview'])) {
423
            $reasons[] = 'CMS Preview';
424
        }
425
        return $reasons;
426
    }
427
428
    /**
429
     * Determine why DebugBar is disabled
430
     *
431
     * Deprecated in favor of disabledCriteria
432
     *
433
     * @return string
434
     */
435
    public static function whyDisabled()
436
    {
437
        $reasons = self::disabledCriteria();
438
        if (!empty($reasons)) {
439
            return $reasons[0];
440
        }
441
        return "I don't know why";
442
    }
443
444
    public static function vendorNotInstalled()
445
    {
446
        return !class_exists('DebugBar\\StandardDebugBar');
447
    }
448
449
    public static function notLocalIp()
450
    {
451
        if (!self::config()->get('check_local_ip')) {
452
            return false;
453
        }
454
        if (isset($_SERVER['REMOTE_ADDR'])) {
455
            return !in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', '1'));
456
        }
457
        return false;
458
    }
459
460
    public static function isDisabled()
461
    {
462
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
463
            return true;
464
        }
465
        return false;
466
    }
467
468
    public static function isDevUrl()
469
    {
470
        return strpos(self::getRequestUrl(), '/dev/') === 0;
471
    }
472
473
    public static function isAdminUrl()
474
    {
475
        $baseUrl = rtrim(BASE_URL, '/');
476
        if (class_exists(AdminRootController::class)) {
477
            $adminUrl = AdminRootController::config()->get('url_base');
478
        } else {
479
            $adminUrl = 'admin';
480
        }
481
482
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
483
    }
484
485
    public static function isAdminController()
486
    {
487
        if (Controller::curr()) {
488
            return Controller::curr() instanceof LeftAndMain;
489
        }
490
        return self::isAdminUrl();
491
    }
492
493
    /**
494
     * Avoid triggering data collection for open handler
495
     *
496
     * @return boolean
497
     */
498
    public static function isDebugBarRequest()
499
    {
500
        if ($url = self::getRequestUrl()) {
501
            return strpos($url, '/__debugbar') === 0;
502
        }
503
        return true;
504
    }
505
506
    /**
507
     * Get request url
508
     *
509
     * @return string
510
     */
511
    public static function getRequestUrl()
512
    {
513
        if (isset($_REQUEST['url'])) {
514
            return $_REQUEST['url'];
515
        }
516
        if (isset($_SERVER['REQUEST_URI'])) {
517
            return $_SERVER['REQUEST_URI'];
518
        }
519
        return '';
520
    }
521
522
    /**
523
     * Helper to make code cleaner
524
     *
525
     * @param callable $callback
526
     */
527
    public static function withDebugBar($callback)
528
    {
529
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
530
            $callback(self::getDebugBar());
531
        }
532
    }
533
534
    /**
535
     * Set the current request. Is provided by the DebugBarMiddleware.
536
     *
537
     * @param HTTPRequest $request
538
     */
539
    public static function setRequest(HTTPRequest $request)
540
    {
541
        self::$request = $request;
542
    }
543
544
    /**
545
     * Get the current request
546
     *
547
     * @return HTTPRequest
548
     */
549
    public static function getRequest()
550
    {
551
        if (self::$request) {
552
            return self::$request;
553
        }
554
        // Fall back to trying from the global state
555
        if (Controller::has_curr()) {
556
            return Controller::curr()->getRequest();
557
        }
558
    }
559
560
    /**
561
     * @return TimeDataCollector
562
     */
563
    public static function getTimeCollector()
564
    {
565
        return self::getDebugBar()->getCollector('time');
566
    }
567
568
    /**
569
     * @return MessagesCollector
570
     */
571
    public static function getMessageCollector()
572
    {
573
        return self::getDebugBar()->getCollector('time');
574
    }
575
576
    /**
577
     * Start/stop time tracking (also before init)
578
     *
579
     * @param string $label
580
     * @return void
581
     */
582
    public static function trackTime($label)
583
    {
584
        if (self::$debugbar) {
585
            $timeData = self::getTimeCollector();
586
            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...
587
                return;
588
            }
589
            if ($timeData->hasStartedMeasure($label)) {
590
                $timeData->stopMeasure($label);
591
            } else {
592
                $timeData->startMeasure($label);
593
            }
594
        } else {
595
            // Store in temp array
596
            if (!isset(self::$extraTimes[$label])) {
597
                self::$extraTimes[$label] = [microtime(true)];
598
            } else {
599
                self::$extraTimes[$label][] = microtime(true);
600
            }
601
        }
602
    }
603
604
    /**
605
     * Add extra time to time collector
606
     */
607
    public static function measureExtraTime()
608
    {
609
        $timeData = self::getTimeCollector();
610
        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...
611
            return;
612
        }
613
        foreach (self::$extraTimes as $label => $values) {
614
            if (!isset($values[1])) {
615
                continue; // unfinished measure
616
            }
617
            $timeData->addMeasure(
618
                $label,
619
                $values[0],
620
                $values[1]
621
            );
622
        }
623
        self::$extraTimes = [];
624
    }
625
}
626