Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.52% covered (success)
80.52%
62 / 77
80.00% covered (success)
80.00%
8 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
QueryHandler
80.52% covered (success)
80.52%
62 / 77
80.00% covered (success)
80.00%
8 / 10
38.10
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%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
 prepareHeaderAsString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 prepareAsString
58.33% covered (warning)
58.33%
14 / 24
0.00% covered (danger)
0.00%
0 / 1
14.86
 log
78.26% covered (success)
78.26%
18 / 23
0.00% covered (danger)
0.00%
0 / 1
12.24
 __get
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://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@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    2.2.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        $elapsed = $this->profiler->getElapsed();
107        $data    = [
108            'start'   => number_format((float)$this->profiler->getStart(), 5, '.', ''),
109            'finish'  => number_format((float)$this->profiler->getFinish(), 5, '.', ''),
110            'elapsed' => $elapsed,
111            'steps'   => []
112        ];
113
114        foreach ($this->profiler->getSteps() as $step) {
115            $data['steps'][] = [
116                'start'   => number_format($step->getStart(), 5, '.', ''),
117                'finish'  => number_format($step->getFinish(), 5, '.', ''),
118                'elapsed' => $step->getElapsed(),
119                'query'   => $step->getQuery(),
120                'params'  => $step->getParams(),
121                'errors'  => $step->getErrors()
122            ];
123        }
124
125        return $data;
126    }
127
128    /**
129     * Prepare header string
130     *
131     * @return string
132     */
133    public function prepareHeaderAsString(): string
134    {
135        $string  = ((!empty($this->name)) ? $this->name . ' ' : '') . 'Query Handler';
136        $string .= PHP_EOL . str_repeat('=', strlen($string)) . PHP_EOL;
137
138        return $string;
139    }
140
141    /**
142     * Prepare handler data as string
143     *
144     * @return string
145     */
146    public function prepareAsString(): string
147    {
148        $elapsed = $this->profiler->getElapsed();
149        $string  = "Start:\t\t\t" . number_format((float)$this->profiler->getStart(), 5, '.', '') . PHP_EOL;
150        $string .= "Finish:\t\t\t" . number_format((float)$this->profiler->getFinish(), 5, '.', '') . PHP_EOL;
151        $string .= "Elapsed:\t\t" . $elapsed . ' seconds' . PHP_EOL . PHP_EOL;
152
153        $string .= "Queries:" . PHP_EOL;
154        $string .= "--------" . PHP_EOL;
155        foreach ($this->profiler->getSteps() as $step) {
156            $string .= $step->getQuery() . ' [' . $step->getElapsed() . ']' . PHP_EOL;
157            $string .= "Start:\t\t\t" . number_format($step->getStart(), 5, '.', '') . PHP_EOL;
158            $string .= "Finish:\t\t\t" . number_format($step->getFinish(), 5, '.', '') . PHP_EOL;
159            if ($step->hasParams()) {
160                $string .= "Params:" . PHP_EOL;
161                foreach ($step->getParams() as $name => $value) {
162                    if (is_array($value)) {
163                        foreach ($value as $v) {
164                            $string .= "\t" . $name . ' => ' . $v . PHP_EOL;
165                        }
166                    } else {
167                        $string .= "\t" . $name . ' => ' . $value . PHP_EOL;
168                    }
169                }
170            }
171            if ($step->hasErrors()) {
172                $string .= "Errors:" . PHP_EOL;
173                foreach ($step->getErrors() as $time => $error) {
174                    $string .= "\t[" . number_format($time, 5, '.', '') . "]" . $error['error'] .
175                        ((!empty($error['number'])) ? ' [' . $error['number'] . ']' : '') . PHP_EOL;
176                }
177
178            }
179            $string .= PHP_EOL;
180        }
181
182        return $string;
183    }
184
185    /**
186     * Trigger handler logging
187     *
188     * @throws Exception
189     * @return void
190     */
191    public function log(): void
192    {
193        if (($this->hasLogger()) && ($this->hasLoggingParams())) {
194            $logLevel   = $this->loggingParams['level'] ?? null;
195            $useContext = $this->loggingParams['context'] ?? null;
196            $timeLimit  = $this->loggingParams['limit'] ?? null;
197
198            if ($logLevel !== null) {
199                if ($timeLimit !== null) {
200                    foreach ($this->profiler->getSteps() as $step) {
201                        $elapsedTime = $step->getElapsed();
202                        if ($elapsedTime >= $timeLimit) {
203                            $this->logger->log($logLevel, 'A query has exceeded the time limit of ' . $timeLimit .
204                                ' second(s) by ' . $elapsedTime - $timeLimit . ' second(s). The query execution was a total of ' .
205                                $elapsedTime . ' second(s).'
206                            );
207                        }
208                    }
209                } else {
210                    $context = [];
211                    $message = (count($this->profiler->getSteps()) > 1) ?
212                        '(' . count($this->profiler->getSteps()) . ') new queries have been executed.' :
213                        '(1) new query has been executed.';
214
215                    $context['queries'] = (($useContext !== null) && (strtolower($useContext) == 'text')) ?
216                        $this->prepareAsString() : $this->prepare();
217
218                    if (is_string($useContext)) {
219                        $context['format'] = $useContext;
220                    }
221
222                    $this->logger->log($logLevel, $message, $context);
223                }
224            } else {
225                throw new Exception('Error: The log level parameter was not set.');
226            }
227        }
228    }
229
230    /**
231     * Magic get method to return the profiler.
232     *
233     * @param  string $name
234     * @return mixed
235     */
236    public function __get(string $name): mixed
237    {
238        return match ($name) {
239            'profiler' => $this->profiler,
240            default    => null,
241        };
242    }
243
244}