• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

myaaghubi / Debench / 17006397208

16 Aug 2025 08:32AM UTC coverage: 95.377% (-4.6%) from 100.0%
17006397208

push

github

web-flow
Merge pull request #8 from myaaghubi/v1.7

V1.7

32 of 49 new or added lines in 3 files covered. (65.31%)

2 existing lines in 1 file now uncovered.

392 of 411 relevant lines covered (95.38%)

21.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.56
/lib/Debench/Debench.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * @package Debench
7
 * @link http://github.com/myaaghubi/debench Github
8
 * @author Mohammad Yaaghubi <m.yaaghubi.abc@gmail.com>
9
 * @copyright Copyright (c) 2025, Mohammad Yaaghubi
10
 * @license MIT License
11
 */
12

13
namespace DEBENCH;
14

15
class Debench
16
{
17
    private static bool $enable;
18
    private static string $ui;
19
    private static string $path;
20
    private static string $pathUI;
21
    private static bool $minimalOnly;
22

23
    private array $checkPoints;
24
    private array $exceptions;
25
    private static array $messages;
26

27
    private int $initPointMS;
28
    private int $endPointMS;
29
    private int $lastCheckPointInMS;
30
    private int $lastCheckPointNumber;
31

32
    private static ?Debench $instance = null;
33

34

35
    /**
36
     * Debench constructor
37
     *
38
     * @param  bool $enable
39
     * @param  string $ui
40
     * @return void
41
     */
42
    public function __construct(bool $enable = true, string $path = 'public', string $ui = 'assets')
66✔
43
    {
44
        self::$enable = $enable;
66✔
45

46
        $base = \Base::instance();
66✔
47
        if (!self::$enable|| $base->get('AJAX') || ($base->get("CLI")&&defined('PHPUnit'))) {
66✔
48
            return;
2✔
49
        }
50

51
        self::$ui = rtrim($ui, '/');
66✔
52
        self::$path = rtrim($path, '/');
66✔
53
        if (empty($path)) {
66✔
NEW
54
            self::$path = dirname((Utils::getBacktrace()[0])['file']);
×
55
        }
56
        self::$pathUI = self::$path . '/' . self::$ui . '/debench';
66✔
57

58

59

60
        $currentUrl = $base->get('URI');
66✔
61
        $path = parse_url($currentUrl, PHP_URL_PATH);
66✔
62
        $fileInfo = pathinfo($path);
66✔
63

64
        if (!empty($fileInfo['extension']) && in_array($fileInfo['extension'], ['js', 'css'])) {
66✔
UNCOV
65
            return;
×
66
        }
67

68
        // make sure about the theme
69
        Template::makeUI(self::$pathUI);
66✔
70

71
        // Script initial point
72
        $this->addScriptPoint('Script');
66✔
73

74
        // Debench initial point
75
        $this->newPoint('Debench');
66✔
76

77
        set_exception_handler([$this, 'addException']);
66✔
78

79
        set_error_handler([$this, 'addAsException']);
66✔
80

81
        register_shutdown_function([$this, 'shutdownFunction']);
66✔
82

83
        self::$instance = $this;
66✔
84
    }
85

86

87
    /**
88
     * To finalize the Debench
89
     * 
90
     * @return bool
91
     */
92
    private function shutdownFunction(): bool
2✔
93
    {
94
        if (!self::$enable) {
2✔
95
            return false;
2✔
96
        }
97

98
        $this->processCheckPoints();
2✔
99

100
        $output = $this->makeOutput();
2✔
101

102
        print Utils::isInTestMode() ? '' : $output;
2✔
103

104
        return !empty($output);
2✔
105
    }
106

107

108
    /**
109
     * Run session_start()
110
     * 
111
     * @return void
112
     */
113
    private function startSession(): void
2✔
114
    {
115
        if (session_status() != PHP_SESSION_ACTIVE) {
2✔
116
            @session_start();
2✔
117
        }
118
    }
119

120

121
    /**
122
     * Add a new item in checkpoint[]
123
     * 
124
     * @param  int $currentTime
125
     * @param  int $memory
126
     * @param  string $path
127
     * @param  int $lineNumber
128
     * @param  string $key
129
     * @return void
130
     */
131
    private function addCheckPoint(int $currentTime, int $memory, string $path, int $lineNumber, string $key = ''): void
66✔
132
    {
133
        if (empty($key) || !$this->checkTag($key)) {
66✔
134
            throw new \Exception("The `key` ($key) is empty or is not in the right format!!");
4✔
135
        }
136

137
        $checkPoint = new CheckPoint($currentTime, $memory, $path, $lineNumber);
66✔
138

139
        if (!isset($this->checkPoints)) {
66✔
140
            $this->checkPoints = [];
66✔
141
        }
142

143
        $this->checkPoints[$key] = $checkPoint;
66✔
144
    }
145

146

147
    /**
148
     * Add the first checkpoint
149
     *
150
     * @param  string $tag
151
     * @return void
152
     */
153
    private function addScriptPoint(string $tag = ''): void
66✔
154
    {
155
        $time = $this->getRequestTime();
66✔
156
        $path = $this->getScriptName();
66✔
157
        $tag = $this->makeTag($tag, $this->incrementLastCheckPointNumber(true));
66✔
158

159
        // add a check point as preload
160
        $this->addCheckPoint($time, 0, $path, 0, $tag);
66✔
161
    }
162

163

164
    /**
165
     * Add a new checkpoint
166
     * 
167
     * @param  string $tag
168
     * @return void
169
     */
170
    public function newPoint(string $tag = ''): void
66✔
171
    {
172
        if (!self::$enable) {
66✔
173
            return;
2✔
174
        }
175

176
        $currentTime = $this->getCurrentTime();
66✔
177
        if (empty($this->initPointMS)) {
66✔
178
            $this->initPointMS = $currentTime;
66✔
179
        }
180

181
        $ramUsage = $this->getRamUsage();
66✔
182

183
        // debug_backtrace
184
        $debugBT = Utils::getBacktrace()[0];
66✔
185

186
        $file = $debugBT['file'];
66✔
187
        $line = $debugBT['line'];
66✔
188

189

190
        $tag = $this->makeTag($tag, $this->incrementLastCheckPointNumber(true));
66✔
191

192
        $this->addCheckPoint($currentTime, $ramUsage, $file, $line, $tag);
66✔
193

194
        $this->setLastCheckPointInMS($currentTime);
66✔
195
    }
196

197

198
    /**
199
     * benchmark & compare
200
     * 
201
     * @param  string $tag
202
     * @return void
203
     */
NEW
204
    public static function compare($func1 = null, $func2 = null, string $tag = '', int $iterations = 1000): void
×
205
    {
NEW
206
        if (!self::$enable) {
×
NEW
207
            return;
×
208
        }
209

NEW
210
        $currentTime = self::currentTime();
×
NEW
211
        if ($func1) {
×
NEW
212
            for ($i = 0; $i < $iterations; $i++) {
×
NEW
213
                $func1();
×
214
            }
215
        }
216

NEW
217
        $result1 = self::currentTime() - $currentTime;
×
218

NEW
219
        if ($func2) {
×
NEW
220
            for ($i = 0; $i < $iterations; $i++) {
×
NEW
221
                $func2();
×
222
            }
223
        }
224

NEW
225
        $result2 = self::currentTime() - $currentTime - $result1;
×
226

NEW
227
        self::comparison($tag, $iterations, $result1, $result2);
×
228
    }
229

230

231
    /**
232
     * Calculate elapsed time for each checkpoint
233
     *
234
     * @return void
235
     */
236
    private function processCheckPoints(): void
4✔
237
    {
238
        // depends on the the array, it may the below loop take some time to process
239
        $this->endPointMS = $this->getCurrentTime();
4✔
240

241
        $prevKey = '';
4✔
242
        $prevCP = null;
4✔
243

244
        foreach ($this->getCheckPoints() as $key => $cp) {
4✔
245
            if (!is_null($prevCP) && !empty($prevKey)) {
4✔
246
                $diff = $cp->getTimestamp() - $prevCP->getTimestamp();
4✔
247
                $this->checkPoints[$prevKey]->setTimestamp($diff);
4✔
248
            }
249

250
            $prevKey = $key;
4✔
251
            $prevCP = $cp;
4✔
252
        }
253

254
        $diff = 0;
4✔
255
        if (!is_null($prevCP) && !empty($prevKey)) {
4✔
256
            $diff = $this->endPointMS - $prevCP->getTimestamp();
4✔
257
            $this->checkPoints[$prevKey]->setTimestamp($diff);
4✔
258
        }
259
    }
260

261

262
    /**
263
     * Is Debench in minimal mode
264
     *
265
     * @return bool
266
     */
267
    public function isMinimalOnly(): bool
6✔
268
    {
269
        return self::$minimalOnly ?? false;
6✔
270
    }
271

272

273
    /**
274
     * Set Debench to only minimal mode
275
     *
276
     * @param  bool $minimalModeOnly
277
     * @return void
278
     */
279
    public function setMinimalOnly(bool $minimalModeOnly): void
4✔
280
    {
281
        self::$minimalOnly = $minimalModeOnly;
4✔
282
    }
283

284

285
    /**
286
     * Set Debench to only minimal mode
287
     *
288
     * @param  bool $minimalModeOnly
289
     * @return void
290
     */
291
    public static function minimalOnly(bool $minimalModeOnly): void
2✔
292
    {
293
        self::$minimalOnly = $minimalModeOnly;
2✔
294
    }
295

296

297
    /**
298
     * Is Debench enable
299
     *
300
     * @return bool
301
     */
302
    public function isEnable(): bool
4✔
303
    {
304
        return self::$enable;
4✔
305
    }
306

307

308
    /**
309
     * Set Debench enable
310
     *
311
     * @param  bool $enable
312
     * @return void
313
     */
314
    public function setEnable(bool $enable): void
6✔
315
    {
316
        self::$enable = $enable;
6✔
317
    }
318

319

320
    /**
321
     * Set Debench enable
322
     *
323
     * @param  bool $enable
324
     * @return void
325
     */
326
    public static function enable(bool $enable): void
2✔
327
    {
328
        self::$enable = $enable;
2✔
329
    }
330

331

332
    /**
333
     * Get the last checkpoint in milliseconds
334
     *
335
     * @return int
336
     */
337
    private function getLastCheckPointInMS(): int
2✔
338
    {
339
        return $this->lastCheckPointInMS ?? 0;
2✔
340
    }
341

342

343
    /**
344
     * Set the last checkpoint in milliseconds
345
     *
346
     * @param  int $timestamp
347
     * @return void
348
     */
349
    private function setLastCheckPointInMS(int $timestamp): void
66✔
350
    {
351
        $this->lastCheckPointInMS = $timestamp;
66✔
352
    }
353

354

355
    /**
356
     * Get the last checkpoint number
357
     *
358
     * @return int
359
     */
360
    private function getLastCheckPointNumber(): int
2✔
361
    {
362
        return $this->lastCheckPointNumber ?? 0;
2✔
363
    }
364

365

366
    /**
367
     * Get the last checkpoint number, and increase it
368
     *
369
     * @param  bool $postfix
370
     * @return int
371
     */
372
    private function incrementLastCheckPointNumber(bool $postfix = true): int
66✔
373
    {
374
        if (!isset($this->lastCheckPointNumber)) {
66✔
375
            $this->lastCheckPointNumber = 0;
66✔
376
        }
377

378
        if ($postfix) {
66✔
379
            return $this->lastCheckPointNumber++;
66✔
380
        }
381

382
        return ++$this->lastCheckPointNumber;
2✔
383
    }
384

385

386
    /**
387
     * Get the $pathUI
388
     *
389
     * @return string
390
     */
391
    public function getPathUI(): string
66✔
392
    {
393
        return self::$pathUI ?? '';
66✔
394
    }
395

396

397
    /**
398
     * Get checkpoints array
399
     *
400
     * @return array<string,object>
401
     */
402
    public function getCheckPoints(): array
14✔
403
    {
404
        return $this->checkPoints ?? [];
14✔
405
    }
406

407

408
    /**
409
     * Get the ram usage
410
     *
411
     * @param  bool $formatted
412
     * @param  bool $roundUnderMB
413
     * @return int|string
414
     */
415
    public function getRamUsage(bool $formatted = false, bool $roundUnderMB = false): int|string
66✔
416
    {
417
        // true => memory_real_usage
418
        $peak = memory_get_usage();
66✔
419

420
        if ($formatted)
66✔
421
            return Utils::toFormattedBytes($peak, $roundUnderMB);
6✔
422

423
        return $peak;
66✔
424
    }
425

426

427
    /**
428
     * Get the real ram usage (peak)
429
     *
430
     * @param  bool $formatted
431
     * @param  bool $roundUnderMB
432
     * @return int|string
433
     */
434
    public function getRamUsagePeak(bool $formatted = false, bool $roundUnderMB = false): int|string
6✔
435
    {
436
        // true => memory_real_usage
437
        $peak = memory_get_peak_usage(true);
6✔
438

439
        if ($formatted)
6✔
440
            return Utils::toFormattedBytes($peak, $roundUnderMB);
6✔
441

442
        return $peak;
2✔
443
    }
444

445

446
    /**
447
     * Get the elapsed time from beginning till now in milliseconds
448
     *
449
     * @return int
450
     */
451
    public function getExecutionTime(): int
6✔
452
    {
453
        return $this->getCurrentTime() - $this->getRequestTime();
6✔
454
        // what about loads before Debench such as composer !?
455
        // return $this->endPointMS - $this->initPointMS;
456
    }
457

458

459
    /**
460
     * Get the request time in milliseconds
461
     *
462
     * @return int
463
     */
464
    public function getRequestTime(): int
66✔
465
    {
466
        return intval($_SERVER["REQUEST_TIME_FLOAT"] * 1000);
66✔
467
    }
468

469

470
    /**
471
     * Get the script name
472
     *
473
     * @return string
474
     */
475
    public function getScriptName(): string
66✔
476
    {
477
        return $_SERVER['SCRIPT_NAME'] ?? 'non';
66✔
478
    }
479

480

481
    /**
482
     * Get the $_SERVER['REQUEST_METHOD']
483
     *
484
     * @return string
485
     */
486
    public function getRequestMethod(): string
6✔
487
    {
488
        return $_SERVER['REQUEST_METHOD'] ?? 'non';
6✔
489
    }
490

491

492
    /**
493
     * Get the http_response_code()
494
     *
495
     * @return string
496
     */
497
    public function getResponseCode(): int
6✔
498
    {
499
        $rCode = http_response_code();
6✔
500
        // 501: Not Implemented
501
        return $rCode === false ? 501 : $rCode;
6✔
502
    }
503

504

505
    /**
506
     * Get the current time in milliseconds
507
     *
508
     * @return int
509
     */
510
    public function getCurrentTime(): int
66✔
511
    {
512
        return self::currentTime();
66✔
513
    }
514

515

516
    /**
517
     * Get the current time in milliseconds
518
     *
519
     * @return int
520
     */
521
    public static function currentTime(): int
66✔
522
    {
523
        $microtime = microtime(true) * 1000;
66✔
524
        return intval($microtime);
66✔
525
    }
526

527

528
    /**
529
     * Get the count of all loaded files 
530
     *
531
     * @return int
532
     */
533
    public function getLoadedFilesCount(): int
2✔
534
    {
535
        return count(get_required_files());
2✔
536
    }
537

538

539
    /**
540
     * Get the right tag name to show, just remove the '#' with checkpoint number
541
     *
542
     * @param  string $tag
543
     * @return string
544
     */
545
    private function getTagName(string $tag = ''): string
4✔
546
    {
547
        // return substr($tag, 0, strrpos($tag, '#'));
548
        return "#" . substr($tag, 0, strrpos($tag, '#'));
4✔
549
    }
550

551

552
    /**
553
     * Make the tag
554
     * 
555
     * @param  string $tag
556
     * @param  int $id
557
     * @return string
558
     */
559
    private function makeTag(string $tag, int $id): string
66✔
560
    {
561
        if (empty($tag)) {
66✔
562
            $tag = 'point ' . $id;
8✔
563
        }
564

565
        // tags(keys) should to be unique
566
        return $tag .= '#' . $id;
66✔
567
    }
568

569

570
    /**
571
     * Validate the tag
572
     * 
573
     * @param  string $tag
574
     * @return bool
575
     */
576
    private function checkTag(string $tag): bool
66✔
577
    {
578
        $regex = "/^([a-zA-Z0-9_ -]+)#[0-9]+$/";
66✔
579

580
        if (preg_match($regex, $tag)) {
66✔
581
            return true;
66✔
582
        }
583

584
        return false;
8✔
585
    }
586

587

588
    /**
589
     * Add an info
590
     * 
591
     * @param  string $message
592
     * @return void
593
     */
594
    public static function info(string $message): void
4✔
595
    {
596
        self::addMessage($message, MessageLevel::INFO);
4✔
597
    }
598

599

600
    /**
601
     * Add a warning
602
     * 
603
     * @param  string $message
604
     * @return void
605
     */
606
    public static function warning(string $message): void
2✔
607
    {
608
        self::addMessage($message, MessageLevel::WARNING);
2✔
609
    }
610

611

612
    /**
613
     * Add an error
614
     * 
615
     * @param  string $message
616
     * @return void
617
     */
618
    public static function error(string $message): void
2✔
619
    {
620
        self::addMessage($message, MessageLevel::ERROR);
2✔
621
    }
622

623

624
    /**
625
     * Dump var/s
626
     * 
627
     * @param  mixed $var/s
628
     * @return void
629
     */
630
    public static function dump(...$args): void
2✔
631
    {
632
        $messages = [];
2✔
633

634
        foreach (func_get_args() as $var) {
2✔
635
            ob_start();
2✔
636
            var_dump($var);
2✔
637
            $dumped = ob_get_clean();
2✔
638
            $messages[] = preg_replace("/\n*<small>.*?<\/small>/", "", $dumped, 1);
2✔
639
        }
640

641
        $messageString = implode('', $messages);
2✔
642

643
        self::addMessage($messageString, MessageLevel::DUMP);
2✔
644
    }
645

646

647
    /**
648
     * Add a comparison result
649
     * 
650
     * @param  string $tag
651
     * @param  int $iterations
652
     * @param  int $result1
653
     * @param  int $result2
654
     * @return void
655
     */
NEW
656
    protected static function comparison(string $tag, int $iterations, int $result1, int $result2): void
×
657
    {
NEW
658
        self::addMessage("$tag x" . number_format($iterations) . " > res1: " . number_format($result1) . " ms / res2: " . number_format($result2) . " ms", MessageLevel::COMPARISON);
×
659
    }
660

661

662
    /**
663
     * Add a message
664
     * 
665
     * @param  string $message
666
     * @param  MessageLevel $level
667
     * @return void
668
     */
669
    private static function addMessage(string $message, MessageLevel $level): void
10✔
670
    {
671
        $lastBT = Utils::getBacktrace()[0];
10✔
672
        $path = $lastBT['file'];
10✔
673
        $line = $lastBT['line'];
10✔
674

675
        $messageObject = new Message($message, $level, $path, $line);
10✔
676

677
        if (empty(self::$messages)) {
10✔
678
            self::$messages = [];
10✔
679
        }
680

681
        self::$messages[] = $messageObject;
10✔
682
    }
683

684

685
    /**
686
     * Get the exceptions array
687
     * 
688
     * @return array
689
     */
690
    public static function messages(): array
12✔
691
    {
692
        return self::$messages ?? [];
12✔
693
    }
694

695

696
    /**
697
     * Clear messages
698
     * 
699
     * @return void
700
     */
701
    public static function clearMessages(): void
8✔
702
    {
703
        self::$messages = [];
8✔
704
    }
705

706

707
    /**
708
     * Add an exception to exceptions array
709
     * 
710
     * @param  Throwable $exception
711
     * @return void
712
     */
713
    public function addException(\Throwable $exception): void
4✔
714
    {
715
        if (!isset($this->exceptions)) {
4✔
716
            $this->exceptions = [];
4✔
717
        }
718

719
        $this->exceptions[] = $exception;
4✔
720
    }
721

722

723
    /**
724
     * Get the exceptions array
725
     * 
726
     * @return array
727
     */
728
    private function getExceptions(): array
8✔
729
    {
730
        return $this->exceptions ?? [];
8✔
731
    }
732

733

734
    /**
735
     * Throw errors as exceptions
736
     * 
737
     * @param int $level
738
     * @param string $message
739
     * @param string $file
740
     * @param int $line
741
     * @param array $context
742
     * @return void
743
     */
744
    public function addAsException(int $level, string $message, string $file = '', int $line = 0, array $context = []): void
68✔
745
    {
746
        if (!isset($this->exceptions)) {
68✔
747
            $this->exceptions = [];
60✔
748
        }
749

750
        $this->exceptions[]  = new \ErrorException($message, 0, $level, $file, $line);
68✔
751
    }
752

753

754
    /**
755
     * Make formatted output
756
     *
757
     * @return string
758
     */
759
    private function makeOutput(): string
4✔
760
    {
761
        $eTime = $this->getExecutionTime();
4✔
762

763
        // ------- the minimal widget
764
        if ($this->isMinimalOnly()) {
4✔
765
            return Template::render(self::$pathUI . '/widget.minimal.htm', [
2✔
766
                'base' => self::$ui,
2✔
767
                'ramUsage' => $this->getRamUsage(true, true),
2✔
768
                'requestInfo' => $this->getRequestMethod() . ' ' . $this->getResponseCode(),
2✔
769
                'fullExecTime' => $eTime
2✔
770
            ]);
2✔
771
        }
772

773

774
        // ------- infoLog
775
        $infoLog = Template::render(self::$pathUI . '/widget.log.info.htm', [
4✔
776
            "phpVersion" => SystemInfo::getPHPVersion(),
4✔
777
            "opcache" => SystemInfo::getOPCacheStatus(),
4✔
778
            "systemAPI" => SystemInfo::getSystemAPI(),
4✔
779
        ]);
4✔
780

781

782
        // ------- timeLog
783
        $timeLog = '';
4✔
784

785
        foreach ($this->getCheckPoints() as $key => $cp) {
4✔
786
            $timeLog .= Template::render(self::$pathUI . '/widget.log.checkpoint.htm', [
4✔
787
                "name" => $this->getTagName($key),
4✔
788
                "path" => $cp->getPath(),
4✔
789
                "fileName" => basename($cp->getPath()),
4✔
790
                "lineNumber" => $cp->getLineNumber(),
4✔
791
                "timestamp" => $cp->getTimestamp(),
4✔
792
                "memory" => Utils::toFormattedBytes($cp->getMemory()),
4✔
793
                "percent" => round($cp->getTimestamp() / ($eTime > 1 ? $eTime : 1) * 100),
4✔
794
            ]);
4✔
795
        }
796

797

798
        // ------- logPost
799
        $logPost = $this->makeOutputLoop(self::$pathUI . '/widget.log.request.post.htm', $_POST, false);
4✔
800

801

802
        // ------- logGet
803
        $logGet = $this->makeOutputLoop(self::$pathUI . '/widget.log.request.get.htm', $_GET, false);
4✔
804

805

806
        // ------- logCookie
807
        $logCookie = $this->makeOutputLoop(self::$pathUI . '/widget.log.request.cookie.htm', $_COOKIE, false);
4✔
808

809

810
        if (empty($logPost . $logGet . $logCookie)) {
4✔
UNCOV
811
            $logPost = '<b>Nothing</b> Yet!';
×
812
        }
813

814

815
        // ------- logSession
816
        $session = isset($_SESSION) ? $_SESSION : null;
4✔
817

818
        $logSession = $this->makeOutputLoop(self::$pathUI . '/widget.log.request.session.htm', $session);
4✔
819

820
        if (!isset($session)) {
4✔
821
            $logSession = '<b>_SESSION</b> is not available!';
2✔
822
        }
823

824

825
        // ------- logMessages
826
        $logMessage = '<b>Nothing</b> Yet!';
4✔
827

828
        if (!empty(self::messages())) {
4✔
829
            $logMessage = '';
2✔
830
        }
831

832
        foreach (self::messages() as $message) {
4✔
833
            $file = basename($message->getPath());
2✔
834
            $path = str_replace($file, "<b>$file</b>", $message->getPath());
2✔
835

836
            $logMessage .= Template::render(self::$pathUI . '/widget.log.message.htm', [
2✔
837
                // "code" => $exception->getCode(),
838
                "level" => strtoupper($message->getLevel()->name()),
2✔
839
                "message" => $message->getMessage(),
2✔
840
                "path" => $path,
2✔
841
                "line" => $message->getLineNumber(),
2✔
842
            ]);
2✔
843
        }
844

845

846
        // ------- logException
847
        $logException = '';
4✔
848

849
        foreach ($this->getExceptions() as $exception) {
4✔
850
            $file = basename($exception->getFile());
2✔
851
            $path = str_replace($file, "<b>$file</b>", $exception->getFile());
2✔
852

853
            $fullTrace = '';
2✔
854
            $traces = $exception->getTrace();
2✔
855
            foreach ($traces as $trace) {
2✔
856
                $fullTrace .= $trace['file'] . ":" . $trace['line'] . "\n";
2✔
857
            }
858

859

860
            $logException .= Template::render(self::$pathUI . '/widget.log.exception.htm', [
2✔
861
                // "code" => $exception->getCode(),
862
                "message" => $exception->getMessage(),
2✔
863
                "path" => $path,
2✔
864
                "line" => $exception->getLine(),
2✔
865
                "fullTrace" => $fullTrace,
2✔
866
            ]);
2✔
867
        }
868

869
        if (empty($logException)) {
4✔
870
            $logException = '<b>Nothing</b> Yet!';
4✔
871
        }
872

873

874
        // ------- the main widget
875
        return Template::render(self::$pathUI . '/widget.htm', [
4✔
876
            'base' => self::$ui,
4✔
877
            'ramUsagePeak' => $this->getRamUsagePeak(true, true),
4✔
878
            'ramUsage' => $this->getRamUsage(true, true),
4✔
879
            // 'includedFilesCount' => $this->getLoadedFilesCount(),
880
            'preloadTime' => $this->initPointMS - $this->getRequestTime(),
4✔
881
            'pointsCount' => count($this->getCheckPoints()),
4✔
882
            'request' => count($_POST) + count($_GET) + count($_COOKIE),
4✔
883
            'logPost' => $logPost,
4✔
884
            'logGet' => $logGet,
4✔
885
            'logCookie' => $logCookie,
4✔
886
            'session' => count($_SESSION ?? []),
4✔
887
            'sessionLog' => $logSession,
4✔
888
            'infoLog' => $infoLog,
4✔
889
            'timeLog' => $timeLog,
4✔
890
            'logMessage' => $logMessage,
4✔
891
            'message' => count(self::messages()),
4✔
892
            'logException' => $logException,
4✔
893
            'exception' => count($this->getExceptions()),
4✔
894
            'requestInfo' => $this->getRequestMethod() . ' ' . $this->getResponseCode(),
4✔
895
            'fullExecTime' => $eTime
4✔
896
        ]);
4✔
897
    }
898

899

900
    /**
901
     * Make formatted output in loop, $key => $value
902
     *
903
     * @return string
904
     */
905
    private function makeOutputLoop(string $theme, array|null $data, string|false $message = ''): string
6✔
906
    {
907
        if (empty($data)) {
6✔
908
            if ($message === false) {
6✔
909
                return '';
4✔
910
            }
911
            return empty($message) || !is_string($message) ? '<b>Nothing</b> Yet!' : $message;
6✔
912
        }
913

914
        $output = '';
6✔
915

916
        foreach ($data as $key => $value) {
6✔
917
            if (is_array($value)) {
6✔
NEW
918
                $value = "array [" . implode(', ', $value) . "]";
×
919
            }
920
            $output .= Template::render($theme, [
6✔
921
                "key" => $key,
6✔
922
                "value" => $value
6✔
923
            ]);
6✔
924
        }
925

926
        return $output;
6✔
927
    }
928

929

930
    /**
931
     * Add a new checkpoint
932
     * 
933
     * @param  string $tag
934
     * @return object
935
     */
936
    public static function point(string $tag = ''): void
4✔
937
    {
938
        self::getInstance()->newPoint($tag);
4✔
939
    }
940

941

942
    /**
943
     * Gets the instance
944
     * 
945
     * @param  bool $enable
946
     * @param  string $ui
947
     * @return Debench
948
     */
949
    public static function getInstance(bool $enable = true, string $path = 'public', string $ui = 'assets'): Debench
66✔
950
    {
951
        if (self::$instance === null) {
66✔
952
            self::$instance = new self($enable, $path, $ui);
2✔
953
        }
954

955
        return self::$instance;
66✔
956
    }
957

958

959
    /**
960
     * Prevent from being unserialized
961
     * 
962
     * @return void
963
     */
964
    public function __wakeup(): void
2✔
965
    {
966
        throw new \Exception("Cannot unserialize singleton");
2✔
967
    }
968
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc