Completed
Push — master ( 57743c...a8519f )
by Alexander
02:59
created

StatementProfiler::setActive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * This file is part of the SVN-Buddy library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/svn-buddy
9
 */
10
11
namespace ConsoleHelpers\SVNBuddy\Database;
12
13
14
use Aura\Sql\ProfilerInterface;
15
use ConsoleHelpers\ConsoleKit\ConsoleIO;
16
17
class StatementProfiler implements ProfilerInterface
18
{
19
20
	/**
21
	 * Is the profiler active?
22
	 *
23
	 * @var boolean
24
	 */
25
	protected $active = false;
26
27
	/**
28
	 * Retained profiles.
29
	 *
30
	 * @var array
31
	 */
32
	protected $profiles = array();
33
34
	/**
35
	 * Track duplicate statements.
36
	 *
37
	 * @var boolean
38
	 */
39
	protected $trackDuplicates = true;
40
41
	/**
42
	 * Ignored duplicate statements.
43
	 *
44
	 * @var array
45
	 */
46
	protected $ignoredDuplicateStatements = array();
47
48
	/**
49
	 * Console IO.
50
	 *
51
	 * @var ConsoleIO
52
	 */
53
	private $_io;
54
55
	/**
56
	 * Debug mode.
57
	 *
58
	 * @var boolean
59
	 */
60
	private $_debugMode = false;
61
62
	/**
63
	 * Sets IO.
64
	 *
65
	 * @param ConsoleIO $io Console IO.
66
	 *
67
	 * @return void
68
	 */
69 3
	public function setIO(ConsoleIO $io = null)
70
	{
71 3
		$this->_io = $io;
72 3
		$this->_debugMode = isset($io) && $io->isVerbose();
73 3
	}
74
75
	/**
76
	 * Adds statement to ignore list.
77
	 *
78
	 * @param string $statement The SQL query statement.
79
	 *
80
	 * @return void
81
	 */
82 5
	public function ignoreDuplicateStatement($statement)
83
	{
84 5
		$this->ignoredDuplicateStatements[] = $this->normalizeStatement($statement);
85 5
	}
86
87
	/**
88
	 * Turns the profiler on and off.
89
	 *
90
	 * @param boolean $active True to turn on, false to turn off.
91
	 *
92
	 * @return void
93
	 */
94 18
	public function setActive($active)
95
	{
96 18
		$this->active = (bool)$active;
97 18
	}
98
99
	/**
100
	 * Is the profiler active?
101
	 *
102
	 * @return boolean
103
	 */
104 19
	public function isActive()
105
	{
106 19
		return (bool)$this->active;
107
	}
108
109
	/**
110
	 * Toggle duplicate statement tracker.
111
	 *
112
	 * @param boolean $track Duplicate statement tracker status.
113
	 *
114
	 * @return void
115
	 */
116 8
	public function trackDuplicates($track)
117
	{
118 8
		$this->trackDuplicates = (bool)$track;
119 8
	}
120
121
	/**
122
	 * Adds a profile entry.
123
	 *
124
	 * @param float  $duration    The query duration.
125
	 * @param string $function    The PDO method that made the entry.
126
	 * @param string $statement   The SQL query statement.
127
	 * @param array  $bind_values The values bound to the statement.
128
	 *
129
	 * @return void
130
	 * @throws \PDOException When duplicate statement is detected.
131
	 */
132 17
	public function addProfile(
133
		$duration,
134
		$function,
135
		$statement,
136
		array $bind_values = array()
137
	) {
138 17
		if ( !$this->isActive() || $function === 'prepare' || !$statement ) {
139 3
			return;
140
		}
141
142 14
		$normalized_statement = $this->normalizeStatement($statement);
143 14
		$profile_key = $this->createProfileKey($normalized_statement, $bind_values);
144
145 14
		if ( $this->trackDuplicates
146 14
			&& !in_array($normalized_statement, $this->ignoredDuplicateStatements)
147 14
			&& isset($this->profiles[$profile_key])
148 14
		) {
149 2
			$error_msg = 'Duplicate statement:' . PHP_EOL . $normalized_statement;
150 2
			$error_msg .= PHP_EOL . 'Bind Values:' . PHP_EOL . print_r($bind_values, true);
151
152 2
			throw new \PDOException($error_msg);
153
		}
154
155 14
		$this->profiles[$profile_key] = array(
156 14
			'duration' => $duration,
157 14
			'function' => $function,
158 14
			'statement' => $statement,
159 14
			'bind_values' => $bind_values,
160
		);
161
162 14
		if ( $this->_debugMode ) {
163 2
			$runtime = sprintf('%01.2f', $duration);
164 2
			$substituted_normalized_statement = $this->substituteParameters($normalized_statement, $bind_values);
165 2
			$this->_io->writeln(array(
166 2
				'',
167 2
				'<debug>[db, ' . round($runtime, 2) . 's]: ' . $substituted_normalized_statement . '</debug>',
168 2
			));
169 2
		}
170 14
	}
171
172
	/**
173
	 * Removes a profile entry.
174
	 *
175
	 * @param string $statement   The SQL query statement.
176
	 * @param array  $bind_values The values bound to the statement.
177
	 *
178
	 * @return void
179
	 */
180 2
	public function removeProfile($statement, array $bind_values = array())
181
	{
182 2
		if ( !$this->isActive() ) {
183 1
			return;
184
		}
185
186 1
		$normalized_statement = $this->normalizeStatement($statement);
187 1
		unset($this->profiles[$this->createProfileKey($normalized_statement, $bind_values)]);
188 1
	}
189
190
	/**
191
	 * Normalizes statement.
192
	 *
193
	 * @param string $statement The SQL query statement.
194
	 *
195
	 * @return string
196
	 */
197 15
	protected function normalizeStatement($statement)
198
	{
199 15
		return preg_replace('/\s+/', ' ', $statement);
200
	}
201
202
	/**
203
	 * Creates profile key.
204
	 *
205
	 * @param string $normalized_statement The normalized SQL query statement.
206
	 * @param array  $bind_values          The values bound to the statement.
207
	 *
208
	 * @return string
209
	 */
210 14
	protected function createProfileKey($normalized_statement, array $bind_values = array())
211
	{
212 14
		return md5('statement:' . $normalized_statement . ';bind_values:' . serialize($bind_values));
213
	}
214
215
	/**
216
	 * Substitutes parameters in the statement.
217
	 *
218
	 * @param string $normalized_statement The normalized SQL query statement.
219
	 * @param array  $bind_values          The values bound to the statement.
220
	 *
221
	 * @return string
222
	 */
223 2
	protected function substituteParameters($normalized_statement, array $bind_values = array())
224
	{
225 2
		arsort($bind_values);
226
227 2
		foreach ( $bind_values as $param_name => $param_value ) {
228 2
			if ( is_array($param_value) ) {
229 1
				$param_value = '"' . implode('","', $param_value) . '"';
230 1
			}
231
			else {
232 2
				$param_value = '"' . $param_value . '"';
233
			}
234
235 2
			$normalized_statement = str_replace(':' . $param_name, $param_value, $normalized_statement);
236 2
		}
237
238 2
		return $normalized_statement;
239
	}
240
241
	/**
242
	 * Returns all the profile entries.
243
	 *
244
	 * @return array
245
	 */
246 13
	public function getProfiles()
247
	{
248 13
		return $this->profiles;
249
	}
250
251
	/**
252
	 * Reset all the profiles
253
	 *
254
	 * @return void
255
	 */
256 1
	public function resetProfiles()
257
	{
258 1
		$this->profiles = array();
259 1
	}
260
261
}
262