Completed
Push — master ( de7e19...857622 )
by Ron
02:42
created

DiffStorageStore::translate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
rs 9.2
cc 4
eloc 9
nc 4
nop 2
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 $selectStmt;
16
	/** @var PDOStatement */
17
	private $updateStmt;
18
	/** @var string */
19
	private $storeA;
20
	/** @var string */
21
	private $storeB;
22
	/** @var int */
23
	private $counter = 0;
24
	/** @var callable */
25
	private $duplicateKeyHandler;
26
	/** @var array */
27
	private $converter;
28
	/** @var string[] */
29
	private $keys;
30
	/** @var string[] */
31
	private $valueKeys;
32
33
	/**
34
	 * @param PDO $pdo
35
	 * @param string $keySchema
36
	 * @param string $valueSchema
37
	 * @param string[] $keys
38
	 * @param string[] $valueKeys
39
	 * @param array $converter
40
	 * @param string $storeA
41
	 * @param string $storeB
42
	 * @param callable $duplicateKeyHandler
43
	 */
44
	public function __construct(PDO $pdo, $keySchema, $valueSchema, array $keys, array $valueKeys, array $converter, $storeA, $storeB, $duplicateKeyHandler) {
45
		$this->pdo = $pdo;
46
		$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})");
47
		$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)");
48
		$this->updateStmt = $this->pdo->prepare("UPDATE data_store SET s_value={$valueSchema}, s_data=:___data WHERE s_ab='{$storeA}' AND s_key={$keySchema}");
49
		$this->storeA = $storeA;
50
		$this->storeB = $storeB;
51
		$this->keys = $keys;
52
		$this->valueKeys = $valueKeys;
53
		$this->converter = $converter;
54
		$this->duplicateKeyHandler = $duplicateKeyHandler;
55
	}
56
57
	/**
58
	 * @param array $data
59
	 * @param array $translation
60
	 * @param callable $duplicateKeyHandler
61
	 */
62
	public function addRow(array $data, array $translation = null, $duplicateKeyHandler = null) {
63
		$data = $this->translate($data, $translation);
64
		if($duplicateKeyHandler === null) {
65
			$duplicateKeyHandler = $this->duplicateKeyHandler;
66
		}
67
		$buildMetaData = function (array $data, array $keys) {
68
			$metaData = $data;
69
			$metaData = array_diff_key($metaData, array_diff_key($metaData, $keys));
70
			$metaData['___data'] = json_encode($data);
71
			$metaData['___sort'] = $this->counter;
72
			return $metaData;
73
		};
74
		try {
75
			$metaData = $buildMetaData($data, $this->converter);
76
			$this->insertStmt->execute($metaData);
77
		} catch (\PDOException $e) {
78
			if(strpos($e->getMessage(), 'UNIQUE constraint failed') !== false) {
79
				$metaData = $buildMetaData($data, $this->converter);
80
				unset($metaData['___data']);
81
				unset($metaData['___sort']);
82
				$this->selectStmt->execute($metaData);
83
				$oldData = $this->selectStmt->fetch(PDO::FETCH_COLUMN, 0);
84
				if($oldData === null) {
85
					$oldData = [];
86
				} else {
87
					$oldData = json_decode($oldData, true);
88
				}
89
				$data = $duplicateKeyHandler($data, $oldData);
90
				$metaData = $buildMetaData($data, $this->converter);
91
				unset($metaData['___sort']);
92
				$this->updateStmt->execute($metaData);
93
			} else {
94
				throw $e;
95
			}
96
		}
97
	}
98
99
	/**
100
	 * @param Traversable|array $rows
101
	 * @param array $translation
102
	 * @param callable $duplicateKeyHandler
103
	 * @return $this
104
	 */
105
	public function addRows($rows, array $translation = null, $duplicateKeyHandler = null) {
106
		foreach($rows as $row) {
107
			$this->addRow($row, $translation, $duplicateKeyHandler);
108
		}
109
		return $this;
110
	}
111
112
	/**
113
	 * Returns true whenever there is any changed, added or removed data available
114
	 */
115
	public function hasAnyChanges() {
116
		/** @noinspection PhpUnusedLocalVariableInspection */
117
		foreach($this->getNewOrChanged() as $_) {
118
			return true;
119
		}
120
		/** @noinspection PhpUnusedLocalVariableInspection */
121
		foreach($this->getMissing() as $_) {
122
			return true;
123
		}
124
		return false;
125
	}
126
127
	/**
128
	 * Get all rows, that are present in this store, but not in the other
129
	 *
130
	 * @return Generator|DiffStorageStoreRow[]
131
	 */
132
	public function getNew() {
133
		return $this->query('
134
			SELECT
135
				s1.s_key AS k,
136
				s1.s_data AS d,
137
				s2.s_data AS f
138
			FROM
139
				data_store AS s1
140
			LEFT JOIN
141
				data_store AS s2 ON s2.s_ab = :sB AND s1.s_key = s2.s_key
142
			WHERE
143
				s1.s_ab = :sA
144
				AND
145
				s2.s_ab IS NULL
146
			ORDER BY
147
				s1.s_sort
148
		');
149
	}
150
151
	/**
152
	 * Get all rows, that have a different value hash in the other store
153
	 *
154
	 * @return Generator|DiffStorageStoreRow[]
155
	 */
156
	public function getChanged() {
157
		return $this->query('
158
			SELECT
159
				s1.s_key AS k,
160
				s1.s_data AS d,
161
				s2.s_data AS f
162
			FROM
163
				data_store AS s1
164
			INNER JOIN
165
				data_store AS s2 ON s2.s_ab = :sB AND s1.s_key = s2.s_key
166
			WHERE
167
				s1.s_ab = :sA
168
				AND
169
				s1.s_value != s2.s_value
170
			ORDER BY
171
				s1.s_sort
172
		');
173
	}
174
175
	/**
176
	 * @return Generator|DiffStorageStoreRow[]
177
	 */
178
	public function getNewOrChanged() {
179
		return $this->query('
180
			SELECT
181
				s1.s_key AS k,
182
				s1.s_data AS d,
183
				s2.s_data AS f
184
			FROM
185
				data_store AS s1
186
			LEFT JOIN
187
				data_store AS s2 ON s2.s_ab = :sB AND s1.s_key = s2.s_key
188
			WHERE
189
				s1.s_ab = :sA
190
				AND
191
				((s2.s_ab IS NULL) OR (s1.s_value != s2.s_value))
192
			ORDER BY
193
				s1.s_sort
194
		');
195
	}
196
197
	/**
198
	 * Get all rows, that are present in the other store, but not in this
199
	 *
200
	 * @return Generator|DiffStorageStoreRow[]
201
	 */
202
	public function getMissing() {
203
		return $this->query('
204
			SELECT
205
				s1.s_key AS k,
206
				s2.s_data AS d,
207
				s1.s_data AS f
208
			FROM
209
				data_store AS s1
210
			LEFT JOIN
211
				data_store AS s2 ON s2.s_ab = :sA AND s2.s_key = s1.s_key
212
			WHERE
213
				s1.s_ab = :sB
214
				AND
215
				s2.s_ab IS NULL
216
			ORDER BY
217
				s1.s_sort
218
		');
219
	}
220
221
	/**
222
	 * @return $this
223
	 */
224
	public function clearAll() {
225
		$stmt = $this->pdo->query('DELETE FROM data_store WHERE s_ab=:s');
226
		$stmt->execute(['s' => $this->storeA]);
227
		$stmt->closeCursor();
228
	}
229
230
	/**
231
	 * @param string $query
232
	 * @return Generator|DiffStorageStoreRow[]
233
	 */
234
	private function query($query) {
235
		$stmt = $this->pdo->query($query);
236
		$stmt->execute(['sA' => $this->storeA, 'sB' => $this->storeB]);
237
		while($row = $stmt->fetch(PDO::FETCH_NUM)) {
238
			$d = json_decode($row[1], true);
239
			$f = json_decode($row[2], true);
240
			yield $this->instantiateRow($d, $f);
241
		}
242
		$stmt->closeCursor();
243
	}
244
245
	/**
246
	 * @return Traversable|array[]
247
	 */
248
	public function getIterator() {
249
		$query = '
250
			SELECT
251
				s1.s_data AS d
252
			FROM
253
				data_store AS s1
254
			WHERE
255
				s1.s_ab = :s
256
			ORDER BY
257
				s1.s_sort
258
		';
259
		$stmt = $this->pdo->query($query);
260
		$stmt->execute(['s' => $this->storeA]);
261
		while($row = $stmt->fetch(PDO::FETCH_NUM)) {
262
			$row = json_decode($row[0], true);
263
			$row = $this->instantiateRow($row, []);
264
			yield $row->getData();
265
		}
266
		$stmt->closeCursor();
267
	}
268
269
	/**
270
	 * @param array $data
271
	 * @param array $translation
272
	 * @return array
273
	 */
274
	private function translate(array $data, array $translation = null) {
275
		if($translation !== null) {
276
			$result = [];
277
			foreach($data as $key => $value) {
278
				if(array_key_exists($key, $translation)) {
279
					$key = $translation[$key];
280
				}
281
				$result[$key] = $value;
282
			}
283
			return $result;
284
		}
285
		return $data;
286
	}
287
288
	/**
289
	 * @return int
290
	 */
291
	public function count() {
292
		$query = '
293
			SELECT
294
				COUNT(*)
295
			FROM
296
				data_store AS s1
297
			WHERE
298
				s1.s_ab = :s
299
		';
300
		$stmt = $this->pdo->query($query);
301
		$stmt->execute(['s' => $this->storeA]);
302
		$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
303
		return $count;
304
	}
305
306
	/**
307
	 * @param array $localData
308
	 * @param array $foreignData
309
	 * @return DiffStorageStoreRow
310
	 */
311
	private function instantiateRow(array $localData = null, array $foreignData = null) {
312
		return new DiffStorageStoreRow($localData, $foreignData, $this->keys, $this->valueKeys, $this->converter);
313
	}
314
}
315