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

DebugBar::measureExtraTime()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 0
dl 0
loc 17
rs 9.9
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
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
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
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
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
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
$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
$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
$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