|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace BfwSql\Observers; |
|
4
|
|
|
|
|
5
|
|
|
use \Exception; |
|
6
|
|
|
use \BfwSql\Actions\AbstractActions; |
|
7
|
|
|
use \BfwSql\Actions\Select; |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* Requests observer. |
|
11
|
|
|
* Create a log with SELECT requests executed and informations about it. Run an |
|
12
|
|
|
* EXPLAIN query on the request and add informations retured to log. |
|
13
|
|
|
* |
|
14
|
|
|
* @package bfw-sql |
|
15
|
|
|
* @author Vermeulen Maxime <[email protected]> |
|
16
|
|
|
* @version 2.0 |
|
17
|
|
|
*/ |
|
18
|
|
|
class Explain extends Basic |
|
19
|
|
|
{ |
|
20
|
|
|
/** |
|
21
|
|
|
* @const EXPLAIN_OK Explain status when the request has succeeded |
|
22
|
|
|
*/ |
|
23
|
|
|
const EXPLAIN_OK = 'ok'; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @const EXPLAIN_FAILED Explain status when the request has failed |
|
27
|
|
|
*/ |
|
28
|
|
|
const EXPLAIN_FAILED = 'failed'; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @const EXPLAIN_EMPTY Explain status when the request has returned nothing |
|
32
|
|
|
*/ |
|
33
|
|
|
const EXPLAIN_EMPTY = 'empty'; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @var \BfwSql\Sql The sql instanced used to run EXPLAIN request |
|
37
|
|
|
*/ |
|
38
|
|
|
protected $sql; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* @var \stdClass An object with explain status and informations returned |
|
42
|
|
|
*/ |
|
43
|
|
|
protected $explain; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* Getter accessor to property sql |
|
47
|
|
|
* |
|
48
|
|
|
* @return \BfwSql\Sql |
|
49
|
|
|
*/ |
|
50
|
|
|
public function getSql() |
|
51
|
|
|
{ |
|
52
|
|
|
return $this->sql; |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* Getter accessor to property explain |
|
57
|
|
|
* |
|
58
|
|
|
* @return \stdClass |
|
59
|
|
|
*/ |
|
60
|
|
|
public function getExplain() |
|
61
|
|
|
{ |
|
62
|
|
|
return $this->explain; |
|
63
|
|
|
} |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* {@inheritdoc} |
|
67
|
|
|
* Limited to "system query" event. |
|
68
|
|
|
*/ |
|
69
|
|
|
protected function analyzeUpdate() |
|
70
|
|
|
{ |
|
71
|
|
|
if ($this->action === 'system query') { |
|
72
|
|
|
$this->systemQuery(); |
|
73
|
|
|
} |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* {@inheritdoc} |
|
78
|
|
|
* Limited to \BfwSql\Actions\Select object |
|
79
|
|
|
* We not run EXPLAIN automatically on other request type to not destroy db |
|
80
|
|
|
* |
|
81
|
|
|
* @return void |
|
82
|
|
|
*/ |
|
83
|
|
|
protected function systemQuery() |
|
84
|
|
|
{ |
|
85
|
|
View Code Duplication |
if ($this->context instanceof AbstractActions === false) { |
|
|
|
|
|
|
86
|
|
|
throw new Exception( |
|
87
|
|
|
'"system query" event should have an AbstractActions class' |
|
88
|
|
|
.' into the context.', |
|
89
|
|
|
self::ERR_SYSTEM_QUERY_CONTEXT_CLASS |
|
90
|
|
|
); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
if ($this->context instanceof Select === false) { |
|
94
|
|
|
return; |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
$this->explain = (object) [ |
|
98
|
|
|
'status' => self::EXPLAIN_OK, |
|
99
|
|
|
'datas' => [] |
|
100
|
|
|
]; |
|
101
|
|
|
|
|
102
|
|
|
$this->sql = $this->obtainSql(); |
|
103
|
|
|
|
|
104
|
|
|
$this->runExplain(); |
|
105
|
|
|
return parent::systemQuery(); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Obtain a \BfwSql\Sql instance. |
|
110
|
|
|
* |
|
111
|
|
|
* @return \BfwSql\Sql |
|
112
|
|
|
*/ |
|
113
|
|
|
protected function obtainSql() |
|
114
|
|
|
{ |
|
115
|
|
|
$sqlConnect = $this->context->getSqlConnect(); |
|
116
|
|
|
return new \BfwSql\Sql($sqlConnect); |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
/** |
|
120
|
|
|
* Run the EXPLAIN request |
|
121
|
|
|
* |
|
122
|
|
|
* @return void |
|
123
|
|
|
*/ |
|
124
|
|
|
protected function runExplain() |
|
125
|
|
|
{ |
|
126
|
|
|
$this->sql->query('FLUSH STATUS;'); |
|
127
|
|
|
$pdo = $this->sql->getSqlConnect()->getPDO(); |
|
128
|
|
|
|
|
129
|
|
|
$explainQuery = 'EXPLAIN '.$this->context->assemble(); |
|
130
|
|
|
$request = $pdo->prepare( |
|
131
|
|
|
$explainQuery, |
|
132
|
|
|
$this->context->getPrepareDriversOptions() |
|
133
|
|
|
); |
|
134
|
|
|
$explainResult = $request->execute( |
|
135
|
|
|
$this->context->getPreparedRequestArgs() |
|
136
|
|
|
); |
|
137
|
|
|
|
|
138
|
|
|
if ($explainResult === false) { |
|
139
|
|
|
$this->explain->status = self::EXPLAIN_FAILED; |
|
140
|
|
|
return; |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
|
|
$explainFetch = $request->fetch(\PDO::FETCH_ASSOC); |
|
144
|
|
|
if ($explainFetch === false) { |
|
145
|
|
|
$this->explain->status = self::EXPLAIN_EMPTY; |
|
146
|
|
|
return; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
foreach ($explainFetch as $explainKey => $explainValue) { |
|
150
|
|
|
$this->explain->datas[$explainKey] = $explainValue; |
|
151
|
|
|
} |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
/** |
|
155
|
|
|
* {@inheritdoc} |
|
156
|
|
|
* Add explain informations to monolog too. |
|
157
|
|
|
*/ |
|
158
|
|
|
protected function addQueryToMonoLog($query, $error) |
|
159
|
|
|
{ |
|
160
|
|
|
$this->monolog->getLogger()->debug( |
|
161
|
|
|
'Type: '.$this->action.' ; ' |
|
162
|
|
|
.'Query: '.$query. ' ; ' |
|
163
|
|
|
.'Errors: '.print_r($error, true).' ; ' |
|
164
|
|
|
.'Explain status: '.$this->explain->status.' ; ' |
|
165
|
|
|
.'Explain datas: '.print_r($this->explain->datas, true) |
|
166
|
|
|
); |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.