DiffStorageStore::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 1 Features 3
Metric Value
c 8
b 1
f 3
dl 0
loc 13
rs 9.4285
cc 1
eloc 12
nc 1
nop 9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace DataDiff;
3
4
use Generator;
5
use PDO;
6
use PDOStatement;
7
use Traversable;
8
9
class DiffStorageStore implements DiffStorageStoreInterface {
10
	/** @var PDO */
11
	private $pdo;
12
	/** @var PDOStatement */
13
	private $insertStmt;
14
	/** @var PDOStatement */
15
	private $replaceStmt;
16
	/** @var PDOStatement */
17
	private $selectStmt;
18
	/** @var PDOStatement */
19
	private $updateStmt;
20
	/** @var string */
21
	private $storeA;
22
	/** @var string */
23
	private $storeB;
24
	/** @var int */
25
	private $counter = 0;
26
	/** @var callable */
27
	private $duplicateKeyHandler;
28
	/** @var array */
29
	private $converter;
30
	/** @var string[] */
31
	private $keys;
32
	/** @var string[] */
33
	private $valueKeys;
34
35
	/**
36
	 * @param PDO $pdo
37
	 * @param string $keySchema
38
	 * @param string $valueSchema
39
	 * @param string[] $keys
40
	 * @param string[] $valueKeys
41
	 * @param array $converter
42
	 * @param string $storeA
43
	 * @param string $storeB
44
	 * @param callable $duplicateKeyHandler
45
	 */
46
	public function __construct(PDO $pdo, $keySchema, $valueSchema, array $keys, array $valueKeys, array $converter, $storeA, $storeB, $duplicateKeyHandler) {
47
		$this->pdo = $pdo;
48
		$this->selectStmt = $this->pdo->prepare("SELECT s_data FROM data_store WHERE s_ab='{$storeA}' AND s_key={$keySchema} AND (1=1 OR s_value={$valueSchema})");
49
		$this->replaceStmt = $this->pdo->prepare("INSERT OR REPLACE INTO data_store (s_ab, s_key, s_value, s_data, s_sort) VALUES ('{$storeA}', {$keySchema}, {$valueSchema}, :___data, :___sort)");
50
		$this->insertStmt = $this->pdo->prepare("INSERT INTO data_store (s_ab, s_key, s_value, s_data, s_sort) VALUES ('{$storeA}', {$keySchema}, {$valueSchema}, :___data, :___sort)");
51
		$this->updateStmt = $this->pdo->prepare("UPDATE data_store SET s_value={$valueSchema}, s_data=:___data WHERE s_ab='{$storeA}' AND s_key={$keySchema}");
52
		$this->storeA = $storeA;
53
		$this->storeB = $storeB;
54
		$this->keys = $keys;
55
		$this->valueKeys = $valueKeys;
56
		$this->converter = $converter;
57
		$this->duplicateKeyHandler = $duplicateKeyHandler;
58
	}
59
60
	/**
61
	 * @param array $data
62
	 * @param array $translation
63
	 * @param callable $duplicateKeyHandler
64
	 */
65
	public function addRow(array $data, array $translation = null, $duplicateKeyHandler = null) {
66
		$data = $this->translate($data, $translation);
67
		if($duplicateKeyHandler === null) {
68
			$duplicateKeyHandler = $this->duplicateKeyHandler;
69
		}
70
		$buildMetaData = function (array $data, array $keys) {
71
			$metaData = $data;
72
			$metaData = array_diff_key($metaData, array_diff_key($metaData, $keys));
73
			$metaData['___data'] = json_encode($data);
74
			$metaData['___sort'] = $this->counter;
75
			return $metaData;
76
		};
77
		$metaData = $buildMetaData($data, $this->converter);
78
		if($duplicateKeyHandler === null) {
79
			$this->replaceStmt->execute($metaData);
80
		} else {
81
			try {
82
				$this->insertStmt->execute($metaData);
83
			} catch (\PDOException $e) {
84
				if(strpos($e->getMessage(), 'UNIQUE constraint failed') !== false) {
85
					$metaData = $buildMetaData($data, $this->converter);
86
					unset($metaData['___data']);
87
					unset($metaData['___sort']);
88
					$this->selectStmt->execute($metaData);
89
					$oldData = $this->selectStmt->fetch(PDO::FETCH_COLUMN, 0);
90
					if($oldData === null) {
91
						$oldData = [];
92
					} else {
93
						$oldData = json_decode($oldData, true);
94
					}
95
					$data = $duplicateKeyHandler($data, $oldData);
96
					$metaData = $buildMetaData($data, $this->converter);
97
					unset($metaData['___sort']);
98
					$this->updateStmt->execute($metaData);
99
				} else {
100
					throw $e;
101
				}
102
			}
103
		}
104
	}
105
106
	/**
107
	 * @param Traversable|array $rows
108
	 * @param array $translation
109
	 * @param callable $duplicateKeyHandler
110
	 * @return $this
111
	 */
112
	public function addRows($rows, array $translation = null, $duplicateKeyHandler = null) {
113
		foreach($rows as $row) {
114
			$this->addRow($row, $translation, $duplicateKeyHandler);
115
		}
116
		return $this;
117
	}
118
119
	/**
120
	 * Returns true whenever there is any changed, added or removed data available
121
	 */
122
	public function hasAnyChanges() {
123
		/** @noinspection PhpUnusedLocalVariableInspection */
124
		foreach($this->getNewOrChanged() as $_) {
125
			return true;
126
		}
127
		/** @noinspection PhpUnusedLocalVariableInspection */
128
		foreach($this->getMissing() as $_) {
129
			return true;
130
		}
131
		return false;
132
	}
133
134
	/**
135
	 * Get all rows, that are present in this store, but not in the other
136
	 *
137
	 * @return Generator|DiffStorageStoreRow[]
138
	 */
139
	public function getNew() {
140
		return $this->query('
141
			SELECT
142
				s1.s_key AS k,
143
				s1.s_data AS d,
144
				s2.s_data AS f
145
			FROM
146
				data_store AS s1
147
			LEFT JOIN
148
				data_store AS s2 ON s2.s_ab = :sB AND s1.s_key = s2.s_key
149
			WHERE
150
				s1.s_ab = :sA
151
				AND
152
				s2.s_ab IS NULL
153
			ORDER BY
154
				s1.s_sort
155
		', function (DiffStorageStoreRowInterface $row) {
156
			return $this->formatNewRow($row);
157
		});
158
	}
159
160
	/**
161
	 * Get all rows, that have a different value hash in the other store
162
	 *
163
	 * @return Generator|DiffStorageStoreRow[]
164
	 */
165
	public function getChanged() {
166
		return $this->query('
167
			SELECT
168
				s1.s_key AS k,
169
				s1.s_data AS d,
170
				s2.s_data AS f
171
			FROM
172
				data_store AS s1
173
			INNER JOIN
174
				data_store AS s2 ON s2.s_ab = :sB AND s1.s_key = s2.s_key
175
			WHERE
176
				s1.s_ab = :sA
177
				AND
178
				s1.s_value != s2.s_value
179
			ORDER BY
180
				s1.s_sort
181
		', function (DiffStorageStoreRowInterface $row) {
182
			return $this->formatChangedRow($row);
183
		});
184
	}
185
186
	/**
187
	 * @return Generator|DiffStorageStoreRow[]
188
	 */
189
	public function getNewOrChanged() {
190
		return $this->query('
191
			SELECT
192
				s1.s_key AS k,
193
				s1.s_data AS d,
194
				s2.s_data AS f
195
			FROM
196
				data_store AS s1
197
			LEFT JOIN
198
				data_store AS s2 ON s2.s_ab = :sB AND s1.s_key = s2.s_key
199
			WHERE
200
				s1.s_ab = :sA
201
				AND
202
				((s2.s_ab IS NULL) OR (s1.s_value != s2.s_value))
203
			ORDER BY
204
				s1.s_sort
205
		', function (DiffStorageStoreRowInterface $row) {
206
			if(count($row->getForeign()->getValueData())) {
207
				return $this->formatChangedRow($row);
208
			} else {
209
				return $this->formatNewRow($row);
210
			}
211
		});
212
	}
213
214
	/**
215
	 * Get all rows, that are present in the other store, but not in this
216
	 *
217
	 * @return Generator|DiffStorageStoreRow[]
218
	 */
219
	public function getMissing() {
220
		return $this->query('
221
			SELECT
222
				s1.s_key AS k,
223
				s2.s_data AS d,
224
				s1.s_data AS f
225
			FROM
226
				data_store AS s1
227
			LEFT JOIN
228
				data_store AS s2 ON s2.s_ab = :sA AND s2.s_key = s1.s_key
229
			WHERE
230
				s1.s_ab = :sB
231
				AND
232
				s2.s_ab IS NULL
233
			ORDER BY
234
				s1.s_sort
235
		', function (DiffStorageStoreRowInterface $row) {
236
			return $this->formatMissingRow($row);
237
		});
238
	}
239
240
	/**
241
	 * @return $this
242
	 */
243
	public function clearAll() {
244
		$stmt = $this->pdo->query('DELETE FROM data_store WHERE s_ab=:s');
245
		$stmt->execute(['s' => $this->storeA]);
246
		$stmt->closeCursor();
247
	}
248
249
	/**
250
	 * @param string $query
251
	 * @param callable $stringFormatter
252
	 * @return DiffStorageStoreRow[]|Generator
253
	 */
254
	private function query($query, $stringFormatter) {
255
		$stmt = $this->pdo->query($query);
256
		$stmt->execute(['sA' => $this->storeA, 'sB' => $this->storeB]);
257
		while($row = $stmt->fetch(PDO::FETCH_NUM)) {
258
			$d = json_decode($row[1], true);
259
			$f = json_decode($row[2], true);
260
			yield $this->instantiateRow($d, $f, $stringFormatter);
261
		}
262
		$stmt->closeCursor();
263
	}
264
265
	/**
266
	 * @return Traversable|array[]
267
	 */
268
	public function getIterator() {
269
		$query = '
270
			SELECT
271
				s1.s_data AS d
272
			FROM
273
				data_store AS s1
274
			WHERE
275
				s1.s_ab = :s
276
			ORDER BY
277
				s1.s_sort
278
		';
279
		$stmt = $this->pdo->query($query);
280
		$stmt->execute(['s' => $this->storeA]);
281
		while($row = $stmt->fetch(PDO::FETCH_NUM)) {
282
			$row = json_decode($row[0], true);
283
			$row = $this->instantiateRow($row, [], function (DiffStorageStoreRowInterface $row) {
284
				return $this->formatKeyValuePairs($row->getData());
285
			});
286
			yield $row->getData();
287
		}
288
		$stmt->closeCursor();
289
	}
290
291
	/**
292
	 * @param array $data
293
	 * @param array $translation
294
	 * @return array
295
	 */
296
	private function translate(array $data, array $translation = null) {
297
		if($translation !== null) {
298
			$result = [];
299
			foreach($data as $key => $value) {
300
				if(array_key_exists($key, $translation)) {
301
					$key = $translation[$key];
302
				}
303
				$result[$key] = $value;
304
			}
305
			return $result;
306
		}
307
		return $data;
308
	}
309
310
	/**
311
	 * @return int
312
	 */
313
	public function count() {
314
		$query = '
315
			SELECT
316
				COUNT(*)
317
			FROM
318
				data_store AS s1
319
			WHERE
320
				s1.s_ab = :s
321
		';
322
		$stmt = $this->pdo->query($query);
323
		$stmt->execute(['s' => $this->storeA]);
324
		$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
325
		return $count;
326
	}
327
328
	/**
329
	 * @param array $localData
330
	 * @param array $foreignData
331
	 * @param callable $stringFormatter
332
	 * @return DiffStorageStoreRow
333
	 */
334
	private function instantiateRow(array $localData = null, array $foreignData = null, $stringFormatter) {
335
		return new DiffStorageStoreRow($localData, $foreignData, $this->keys, $this->valueKeys, $this->converter, $stringFormatter);
336
	}
337
338
	/**
339
	 * @param DiffStorageStoreRowInterface $row
340
	 * @return string
341
	 */
342
	private function formatNewRow(DiffStorageStoreRowInterface $row) {
343
		$keys = $this->formatKeyValuePairs($row->getLocal()->getKeyData());
344
		$values = $this->formatKeyValuePairs($row->getLocal()->getValueData());
345
		return sprintf("New %s (%s)", $keys, $values);
346
	}
347
348
	/**
349
	 * @param DiffStorageStoreRowInterface $row
350
	 * @return string
351
	 */
352
	private function formatChangedRow(DiffStorageStoreRowInterface $row) {
353
		$keys = $this->formatKeyValuePairs($row->getLocal()->getKeyData());
354
		$values = $row->getForeign()->getValueData();
355
		$valueKeys = array_keys($values);
356
		return sprintf("Changed %s => %s", $keys, $row->getDiffFormatted($valueKeys));
357
	}
358
359
	/**
360
	 * @param DiffStorageStoreRowInterface $row
361
	 * @return string
362
	 */
363
	private function formatMissingRow(DiffStorageStoreRowInterface $row) {
364
		$keys = $this->formatKeyValuePairs($row->getForeign()->getKeyData());
365
		$values = $this->formatKeyValuePairs($row->getForeign()->getValueData());
366
		return sprintf("Missing %s (%s)", $keys, $values);
367
	}
368
369
	/**
370
	 * @param array $keyValues
371
	 * @return string
372
	 */
373
	private function formatKeyValuePairs($keyValues) {
374
		$keyParts = [];
375
		foreach($keyValues as $key => $value) {
376
			if(is_string($value)) {
377
				$value = preg_replace('/\\s+/', ' ', $value);
378
				if(strlen($value) > 20) {
379
					$value = substr($value, 0, 16) . ' ...';
380
				}
381
			}
382
			$keyParts[] = sprintf("%s: %s", $key, json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
383
		}
384
		return join(', ', $keyParts);
385
	}
386
}
387