Completed
Push — master ( 339a5a...cb0132 )
by Alexander
03:08
created

StatementProfiler::substituteParameters()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 9
nc 3
nop 2
crap 3
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 = false;
40
41
	/**
42
	 * Ignore statements.
43
	 *
44
	 * @var array
45
	 */
46
	protected $ignoreStatements = array(
47
		// The "AbstractPlugin::getLastRevision" method.
48
		'SELECT LastRevision FROM PluginData WHERE Name = :name',
49
50
		// The "AbstractPlugin::getProject" method.
51
		'SELECT Id FROM Projects WHERE Path = :path',
52
53
		// The "AbstractDatabaseCollectorPlugin::getProjects" method.
54
		'SELECT Path, Id AS PathId, RevisionAdded, RevisionDeleted, RevisionLastSeen FROM Paths WHERE PathHash IN (:path_hashes)',
55
	);
56
57
	/**
58
	 * Console IO.
59
	 *
60
	 * @var ConsoleIO
61
	 */
62
	private $_io;
63
64
	/**
65
	 * Debug mode.
66
	 *
67
	 * @var boolean
68
	 */
69
	private $_debugMode = false;
70
71
	/**
72
	 * Creates statement profiler
73
	 *
74
	 * @param ConsoleIO $io Console IO.
75
	 */
76 18
	public function __construct(ConsoleIO $io = null)
77
	{
78 18
		$this->_io = $io;
79 18
		$this->_debugMode = isset($io) && $io->isVerbose();
80 18
	}
81
82
	/**
83
	 * Turns the profiler on and off.
84
	 *
85
	 * @param boolean $active True to turn on, false to turn off.
86
	 *
87
	 * @return void
88
	 */
89 15
	public function setActive($active)
90
	{
91 15
		$this->active = (bool)$active;
92 15
	}
93
94
	/**
95
	 * Is the profiler active?
96
	 *
97
	 * @return boolean
98
	 */
99 17
	public function isActive()
100
	{
101 17
		return (bool)$this->active;
102
	}
103
104
	/**
105
	 * Toggle duplicate statement tracker.
106
	 *
107
	 * @param boolean $track Duplicate statement tracker status.
108
	 *
109
	 * @return void
110
	 */
111 2
	public function trackDuplicates($track)
112
	{
113 2
		$this->trackDuplicates = (bool)$track;
114 2
	}
115
116
	/**
117
	 * Adds a profile entry.
118
	 *
119
	 * @param float  $duration    The query duration.
120
	 * @param string $function    The PDO method that made the entry.
121
	 * @param string $statement   The SQL query statement.
122
	 * @param array  $bind_values The values bound to the statement.
123
	 *
124
	 * @return void
125
	 * @throws \PDOException When duplicate statement is detected.
126
	 */
127 15
	public function addProfile(
128
		$duration,
129
		$function,
130
		$statement,
131
		array $bind_values = array()
132
	) {
133 15
		if ( !$this->isActive() || $function === 'prepare' || !$statement ) {
134 3
			return;
135
		}
136
137 12
		$normalized_statement = $this->normalizeStatement($statement);
138
139 12
		if ( in_array($normalized_statement, $this->ignoreStatements) ) {
140 3
			return;
141
		}
142
143 9
		$profile_key = $this->createProfileKey($normalized_statement, $bind_values);
144
145 9
		if ( $this->trackDuplicates && isset($this->profiles[$profile_key]) ) {
146 1
			$error_msg = 'Duplicate statement:' . PHP_EOL . $normalized_statement;
147 1
			$error_msg .= PHP_EOL . 'Bind Values:' . PHP_EOL . print_r($bind_values, true);
148
149 1
			throw new \PDOException($error_msg);
150
		}
151
152 9
		$this->profiles[$profile_key] = array(
153 9
			'duration' => $duration,
154 9
			'function' => $function,
155 9
			'statement' => $statement,
156 9
			'bind_values' => $bind_values,
157
		);
158
159 9
		if ( $this->_debugMode ) {
160 1
			$runtime = sprintf('%01.2f', $duration);
161 1
			$substituted_normalized_statement = $this->substituteParameters($normalized_statement, $bind_values);
162 1
			$this->_io->writeln(array(
163 1
				'',
164 1
				'<debug>[db, ' . round($runtime, 2) . 's]: ' . $substituted_normalized_statement . '</debug>',
165 1
			));
166 1
		}
167 9
	}
168
169
	/**
170
	 * Removes a profile entry.
171
	 *
172
	 * @param string $statement   The SQL query statement.
173
	 * @param array  $bind_values The values bound to the statement.
174
	 *
175
	 * @return void
176
	 */
177 2
	public function removeProfile($statement, array $bind_values = array())
178
	{
179 2
		if ( !$this->isActive() ) {
180 1
			return;
181
		}
182
183 1
		$normalized_statement = $this->normalizeStatement($statement);
184 1
		unset($this->profiles[$this->createProfileKey($normalized_statement, $bind_values)]);
185 1
	}
186
187
	/**
188
	 * Normalizes statement.
189
	 *
190
	 * @param string $statement The SQL query statement.
191
	 *
192
	 * @return string
193
	 */
194 12
	protected function normalizeStatement($statement)
195
	{
196 12
		return preg_replace('/\s+/', ' ', $statement);
197
	}
198
199
	/**
200
	 * Creates profile key.
201
	 *
202
	 * @param string $normalized_statement The normalized SQL query statement.
203
	 * @param array  $bind_values          The values bound to the statement.
204
	 *
205
	 * @return string
206
	 */
207 9
	protected function createProfileKey($normalized_statement, array $bind_values = array())
208
	{
209 9
		return md5('statement:' . $normalized_statement . ';bind_values:' . serialize($bind_values));
210
	}
211
212
	/**
213
	 * Substitutes parameters in the statement.
214
	 *
215
	 * @param string $normalized_statement The normalized SQL query statement.
216
	 * @param array  $bind_values          The values bound to the statement.
217
	 *
218
	 * @return string
219
	 */
220 1
	protected function substituteParameters($normalized_statement, array $bind_values = array())
221
	{
222 1
		arsort($bind_values);
223
224 1
		foreach ( $bind_values as $param_name => $param_value ) {
225 1
			if ( is_array($param_value) ) {
226 1
				$param_value = '"' . implode('","', $param_value) . '"';
227 1
			}
228
			else {
229 1
				$param_value = '"' . $param_value . '"';
230
			}
231
232 1
			$normalized_statement = str_replace(':' . $param_name, $param_value, $normalized_statement);
233 1
		}
234
235 1
		return $normalized_statement;
236
	}
237
238
	/**
239
	 * Returns all the profile entries.
240
	 *
241
	 * @return array
242
	 */
243 13
	public function getProfiles()
244
	{
245 13
		return $this->profiles;
246
	}
247
248
	/**
249
	 * Reset all the profiles
250
	 *
251
	 * @return void
252
	 */
253 1
	public function resetProfiles()
254
	{
255 1
		$this->profiles = array();
256 1
	}
257
258
}
259