Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.84% covered (success)
91.84%
45 / 49
88.89% covered (success)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
QueryHandler
91.84% covered (success)
91.84%
45 / 49
88.89% covered (success)
88.89%
8 / 9
22.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setProfiler
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasProfiler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProfiler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 profiler
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
2
 prepareMessage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 log
73.33% covered (success)
73.33%
11 / 15
0.00% covered (danger)
0.00%
0 / 1
7.93
 __get
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Pop PHP Framework (https://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@noladev.com>
7 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
8 * @license    https://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Debug\Handler;
15
16use Pop\Db\Adapter\Profiler\Profiler;
17use Pop\Log\Logger;
18
19/**
20 * Debug query handler class
21 *
22 * @category   Pop
23 * @package    Pop\Debug
24 * @author     Nick Sagona, III <dev@noladev.com>
25 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    3.0.0
28 */
29class QueryHandler extends AbstractHandler
30{
31
32    /**
33     * Profiler
34     * @var ?Profiler
35     */
36    protected ?Profiler $profiler = null;
37
38    /**
39     * Constructor
40     *
41     * Instantiate a query handler object
42     *
43     * @param ?Profiler $profiler
44     * @param ?string   $name
45     * @param ?Logger   $logger
46     * @param array     $loggingParams
47     */
48    public function __construct(?Profiler $profiler = null, ?string $name = null, ?Logger $logger = null, array $loggingParams = [])
49    {
50        parent::__construct($name, $logger, $loggingParams);
51
52        if ($profiler !== null) {
53            $this->setProfiler($profiler);
54        }
55    }
56
57    /**
58     * Set profiler
59     *
60     * @param  Profiler $profiler
61     * @return QueryHandler
62     */
63    public function setProfiler(Profiler $profiler): QueryHandler
64    {
65        $this->profiler = $profiler;
66        return $this;
67    }
68
69    /**
70     * Determine if the handler has a profiler
71     *
72     * @return bool
73     */
74    public function hasProfiler(): bool
75    {
76        return ($this->profiler !== null);
77    }
78
79    /**
80     * Get profiler
81     *
82     * @return Profiler
83     */
84    public function getProfiler(): Profiler
85    {
86        return $this->profiler;
87    }
88
89    /**
90     * Get profiler (alias method)
91     *
92     * @return Profiler
93     */
94    public function profiler(): Profiler
95    {
96        return $this->profiler;
97    }
98
99    /**
100     * Prepare handler data for storage
101     *
102     * @return array
103     */
104    public function prepare(): array
105    {
106        $this->setStart((float)$this->profiler->getStart());
107        $this->setEnd((float)$this->profiler->getFinish());
108        $this->setElapsed((float)$this->profiler->getElapsed());
109
110        $data = [
111            'start'   => number_format((float)$this->getStart(), 5, '.', ''),
112            'end'     => number_format((float)$this->getEnd(), 5, '.', ''),
113            'elapsed' => $this->getElapsed(),
114            'steps'   => []
115        ];
116
117        foreach ($this->profiler->getSteps() as $step) {
118            $data['steps'][] = [
119                'start'   => number_format($step->getStart(), 5, '.', ''),
120                'end'     => number_format($step->getFinish(), 5, '.', ''),
121                'elapsed' => $step->getElapsed(),
122                'query'   => $step->getQuery(),
123                'params'  => $step->getParams(),
124                'errors'  => $step->getErrors()
125            ];
126        }
127
128        return $data;
129    }
130
131    /**
132     * Prepare handler message
133     *
134     * @param  ?array $context
135     * @return string
136     */
137    public function prepareMessage(?array $context = null): string
138    {
139        if ($context === null) {
140            $context = $this->prepare();
141        }
142
143        return (!empty($context['steps']) && (count($context['steps']) > 1)) ?
144            '(' . count($context['steps']) . ') new queries have been executed.' :
145            '(1) new query has been executed.';
146    }
147
148    /**
149     * Trigger handler logging
150     *
151     * @throws Exception
152     * @return void
153     */
154    public function log(): void
155    {
156        if (($this->hasLogger()) && ($this->hasLoggingParams())) {
157            $logLevel  = $this->loggingParams['level'] ?? null;
158            $timeLimit = $this->loggingParams['limit'] ?? null;
159
160            if ($logLevel !== null) {
161                $context = $this->prepare();
162                if ($timeLimit !== null) {
163                    foreach ($context['steps'] as $step) {
164                        $elapsedTime = $step['elapsed'] ?? 0;
165                        if ($elapsedTime >= $timeLimit) {
166                            $this->logger->log($logLevel, 'A query has exceeded the time limit of ' . $timeLimit .
167                                ' second(s) by ' . $elapsedTime - $timeLimit . ' second(s). The query execution was a total of ' .
168                                $elapsedTime . ' second(s).', $context
169                            );
170                        }
171                    }
172                } else {
173                    $this->logger->log($logLevel, $this->prepareMessage($context), $context);
174                }
175            } else {
176                throw new Exception('Error: The log level parameter was not set.');
177            }
178        }
179    }
180
181    /**
182     * Magic get method to return the profiler.
183     *
184     * @param  string $name
185     * @return mixed
186     */
187    public function __get(string $name): mixed
188    {
189        return match ($name) {
190            'profiler' => $this->profiler,
191            default    => null,
192        };
193    }
194
195}