Completed
Push — master ( cfaeec...115a9a )
by Nazar
04:33
created

CRUD::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 1
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2013-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
use
10
	cs\DB\Accessor,
11
	cs\CRUD\Data_model_processing;
12
13
/**
14
 * CRUD trait
15
 *
16
 * Provides create/read/update/delete methods for faster development
17
 */
18
trait CRUD {
19
	use
20
		Accessor,
21
		Data_model_processing;
22
	/**
23
	 * Create item
24
	 *
25
	 * @param array $arguments First element `id` can be omitted if it is autoincrement field
26
	 *
27
	 * @return false|int|string Id of created item on success, `false` otherwise
28
	 */
29 34
	protected function create (...$arguments) {
30 34
		if (count($arguments) == 1 && !is_array(array_values($this->data_model)[1])) {
31 26
			$arguments = $arguments[0];
32
		}
33
		/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
34 34
		$this->db_prime()->transaction(
35
			function () use (&$id, $arguments) {
36 34
				$id = $this->create_internal($this->table, $this->data_model, $arguments);
37 34
			}
38
		);
39 34
		return $id;
40
	}
41
	/**
42
	 * Create item
43
	 *
44
	 * @param string              $table
45
	 * @param callable[]|string[] $data_model
46
	 * @param array               $arguments First element `id` can be omitted if it is autoincrement field
47
	 *
48
	 * @return false|int|string Id of created item on success (or specified primary key), `false` otherwise
49
	 */
50 34
	private function create_internal ($table, $data_model, $arguments) {
51 34
		$arguments = $this->fix_arguments_order($data_model, $arguments);
52 34
		$insert_id = count($data_model) == count($arguments) ? $arguments[0] : false;
53 34
		list($prepared_arguments, $joined_tables) = $this->crud_arguments_preparation(
54 34
			$insert_id !== false ? $data_model : array_slice($data_model, 1),
55
			$arguments,
56
			$insert_id,
57
			$update_needed
58
		);
59 34
		$columns = '`'.implode('`,`', array_keys($prepared_arguments)).'`';
60 34
		$values  = implode(',', array_fill(0, count($prepared_arguments), "'%s'"));
61 34
		$return  = $this->db_prime()->q(
62 34
			"INSERT IGNORE INTO `$table`
63
				(
64 34
					$columns
65
				) VALUES (
66 34
					$values
67 34
				)",
68
			$prepared_arguments
69
		);
70 34
		$id      = $insert_id !== false ? $insert_id : $this->db_prime()->id();
71
		/**
72
		 * Id might be 0 if insertion failed or if we insert duplicate entry (which is fine since we use 'INSERT IGNORE'
73
		 */
74 34
		if (!$return || $id === 0) {
75
			return false;
76
		}
77 34
		$this->update_joined_tables($id, $joined_tables);
78 34
		$this->find_update_files_tags($id, [], $arguments);
79
		/**
80
		 * If on creation request without specified primary key and multilingual fields present - update needed
81
		 * after creation (there is no id before creation)
82
		 */
83 34
		if ($update_needed) {
84 4
			$this->update_internal(
85
				$table,
86
				array_filter(
87
					$data_model,
88
					function ($item) {
89 4
						return !is_array($item) || !isset($item['data_model']);
90 4
					}
91
				),
92 4
				array_merge([array_keys($data_model)[0] => $id], $prepared_arguments),
93 4
				false
94
			);
95
		}
96 34
		return $id;
97
	}
98
	/**
99
	 * @param int|string $id
100
	 * @param array      $joined_tables
101
	 */
102 36
	private function update_joined_tables ($id, $joined_tables) {
103 36
		$clang = $this->db_prime()->s(Language::instance()->clang, false);
104
		/**
105
		 * At first we remove all old data
106
		 */
107 36
		foreach ($this->data_model as $table => $model) {
108 36
			if (!is_array($model) || !isset($model['data_model'])) {
109 36
				continue;
110
			}
111 8
			$id_field                 = array_keys($model['data_model'])[0];
112 8
			$language_field_condition = isset($model['language_field'])
113 4
				? "AND `$model[language_field]` = '$clang'"
114 8
				: '';
115 8
			$this->db_prime()->q(
116 8
				"DELETE FROM `{$this->table}_$table`
117
				WHERE
118 8
					`$id_field`	= '%s'
119 8
					$language_field_condition",
120
				$id
121
			);
122
		}
123 36
		$id = $this->db_prime()->s($id, false);
124
		/**
125
		 * Now walk through all tables and insert new valued
126
		 */
127 36
		foreach ($joined_tables as $table => $model) {
128 8
			if (!@$model['data']) {
129 6
				continue;
130
			}
131 8
			$fields = "`$model[id_field]`, ";
132
			/** @noinspection DisconnectedForeachInstructionInspection */
133 8
			$values = "'$id'";
134 8
			if (isset($model['language_field'])) {
135 4
				$fields .= "`$model[language_field]`, ";
136 4
				$values .= ",'$clang'";
137
			}
138 8
			$fields .= '`'.implode('`,`', array_keys($model['fields'])).'`';
139 8
			$values .= str_repeat(",'%s'", count($model['fields']));
140 8
			$this->db_prime()->insert(
141 8
				"INSERT INTO `{$this->table}_$table`
142
					(
143 8
						$fields
144
					) VALUES (
145 8
						$values
146 8
					)",
147 8
				$model['data']
148
			);
149
		}
150 36
	}
151
	/**
152
	 * Read item
153
	 *
154
	 * @param int|int[]|string|string[] $id
155
	 *
156
	 * @return array|false
157
	 */
158 20
	protected function read ($id) {
159
		/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
160 20
		$this->db()->transaction(
161
			function () use (&$result, $id) {
162 20
				$result = $this->read_internal($this->table, $this->data_model, $id);
163 20
			}
164
		);
165 20
		return $result;
166
	}
167
	/**
168
	 * Read item
169
	 *
170
	 * @param string                    $table
171
	 * @param callable[]|string[]       $data_model
172
	 * @param int|int[]|string|string[] $id
173
	 *
174
	 * @return array|false
175
	 */
176 30
	private function read_internal ($table, $data_model, $id) {
177 30
		if (is_array($id)) {
178 8
			foreach ($id as &$i) {
179 8
				$i = $this->read_internal($table, $data_model, $i);
180
			}
181 8
			return $id;
182
		}
183 30
		$columns      = array_filter(
184
			$data_model,
185
			function ($column) {
186 30
				return !is_array($column) || !isset($column['data_model']);
187 30
			}
188
		);
189 30
		$columns      = '`'.implode('`,`', array_keys($columns)).'`';
190 30
		$first_column = array_keys($data_model)[0];
191 30
		$data         = $this->db()->qf(
192 30
			"SELECT $columns
193 30
			FROM `$table`
194 30
			WHERE `$first_column` = '%s'
195 30
			LIMIT 1",
196
			$id
197
		);
198 30
		if (!$data) {
199 10
			return false;
200
		}
201 30
		foreach ($this->data_model as $field => $model) {
202 30
			if (is_array($model) && isset($model['data_model'])) {
203 6
				$data[$field] = $this->read_joined_table($id, $field, $model);
204
			} else {
205 30
				if (is_string($model)) {
206
					/**
207
					 * Handle multilingual fields automatically
208
					 */
209 30
					if (strpos($model, 'ml:') === 0) {
210 4
						$data[$field] = Text::instance()->process($this->cdb(), $data[$field], true);
211
					}
212
				}
213 30
				$data[$field] = $this->read_field_post_processing($data[$field], $model);
214
			}
215
		}
216 30
		return $data;
217
	}
218
	/**
219
	 * @param false|string|string[] $value
220
	 * @param callable|string       $model
221
	 *
222
	 * @return array|false|float|int|string
223
	 */
224 32
	private function read_field_post_processing ($value, $model) {
225 32
		if (is_array($value)) {
226 12
			foreach ($value as &$v) {
227 12
				$v = $this->read_field_post_processing($v, $model);
228
			}
229 12
			return $value;
230
		}
231 32
		if (is_callable($model)) {
232 6
			return $model($value);
233
		}
234
		/**
235
		 * Decode JSON fields
236
		 */
237 32
		if (in_array($model, ['json', 'ml:json'])) {
238 18
			return _json_decode($value);
239
		}
240 32
		if (strpos($model, 'int') === 0) {
241 28
			return (int)$value;
242
		}
243 30
		if (strpos($model, 'float') === 0) {
244
			return (float)$value;
245
		}
246 30
		return $value;
247
	}
248
	/**
249
	 * @param int|string  $id
250
	 * @param string      $table
251
	 * @param array       $model
252
	 * @param null|string $force_clang
253
	 *
254
	 * @return array
255
	 */
256 6
	private function read_joined_table ($id, $table, $model, $force_clang = null) {
257 6
		$clang                    = $force_clang ?: $this->db()->s(Language::instance()->clang, false);
258 6
		$id_field                 = array_keys($model['data_model'])[0];
259 6
		$language_field_condition = isset($model['language_field'])
260 4
			? "AND `$model[language_field]` = '$clang'"
261 6
			: '';
262 6
		$fields                   = '`'.implode('`,`', array_keys($model['data_model'])).'`';
263 6
		$rows                     = $this->db_prime()->qfa(
264 6
			"SELECT $fields
265 6
			FROM `{$this->table}_$table`
266
			WHERE
267 6
				`$id_field`	= '%s'
268 6
				$language_field_condition",
269
			$id
270 6
		) ?: [];
271 6
		$language_field           = isset($model['language_field']) ? $model['language_field'] : null;
272
		/**
273
		 * If no rows found for current language - find another language that should contain some rows
274
		 */
275 6
		if (!$rows && $language_field !== null) {
276 4
			$new_clang = $this->db_prime()->qfs(
277 4
				"SELECT `$language_field`
278 4
				FROM `{$this->table}_$table`
279 4
				WHERE `$id_field`	= '%s'
280 4
				LIMIT 1",
281
				$id
282
			);
283 4
			if ($new_clang && $new_clang != $clang) {
284 4
				return $this->read_joined_table($id, $table, $model, $new_clang);
285
			}
286
			return [];
287
		}
288 6
		foreach ($rows as &$row) {
289
			/**
290
			 * Drop language and id field since they are used internally, not specified by user
291
			 */
292
			unset(
293 6
				$row[$language_field],
294 6
				$row[$id_field]
295
			);
296 6
			foreach ($row as $field => &$value) {
297 6
				$value = $this->read_field_post_processing($value, $model['data_model'][$field]);
298
			}
299
			/**
300
			 * If row is array that contains only one item - lets make resulting array flat
301
			 */
302 6
			if (count($row) == 1) {
303 6
				$row = array_pop($row);
304
			}
305
		}
306 6
		return $rows;
307
	}
308
	/**
309
	 * Update item
310
	 *
311
	 * @param array $arguments
312
	 *
313
	 * @return bool
314
	 */
315 16
	protected function update (...$arguments) {
316 16
		if (count($arguments) == 1) {
317 10
			$arguments = $arguments[0];
318
		}
319
		/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
320 16
		$this->db_prime()->transaction(
321
			function () use (&$result, $arguments) {
322 16
				$result = $this->update_internal($this->table, $this->data_model, $arguments);
323 16
			}
324
		);
325 16
		return $result;
326
	}
327
	/**
328
	 * Update item
329
	 *
330
	 * @param string              $table
331
	 * @param callable[]|string[] $data_model
332
	 * @param array               $arguments
333
	 * @param bool                $files_update
334
	 *
335
	 * @return bool
336
	 */
337 16
	private function update_internal ($table, $data_model, $arguments, $files_update = true) {
338 16
		$arguments          = $this->fix_arguments_order($data_model, $arguments);
339 16
		$prepared_arguments = $arguments;
340 16
		$id                 = array_shift($prepared_arguments);
341 16
		$data               = $this->read_internal($table, $data_model, $id);
342 16
		if (!$data) {
343 2
			return false;
344
		}
345 16
		list($prepared_arguments, $joined_tables) = $this->crud_arguments_preparation(array_slice($data_model, 1), $prepared_arguments, $id);
346 16
		$columns              = implode(
347 16
			',',
348
			array_map(
349
				function ($column) {
350 16
					return "`$column` = '%s'";
351 16
				},
352
				array_keys($prepared_arguments)
353
			)
354
		);
355 16
		$prepared_arguments[] = $id;
356 16
		$first_column         = array_keys($data_model)[0];
357 16
		if (!$this->db_prime()->q(
358 16
			"UPDATE `$table`
359 16
			SET $columns
360 16
			WHERE `$first_column` = '%s'",
361
			$prepared_arguments
362
		)
363
		) {
364
			return false;
365
		}
366 16
		if ($files_update) {
367 16
			$this->update_joined_tables($id, $joined_tables);
368 16
			$this->find_update_files_tags($id, $data, $arguments);
369
		}
370 16
		return true;
371
	}
372
	/**
373
	 * Delete item
374
	 *
375
	 * @param int|int[]|string|string[] $id
376
	 *
377
	 * @return bool
378
	 */
379 14
	protected function delete ($id) {
380
		/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
381 14
		$this->db_prime()->transaction(
382 14
			function () use (&$result, $id) {
383 14
				$result = $this->delete_internal($this->table, $this->data_model, $id);
384 14
			}
385
		);
386 14
		return $result;
387
	}
388
	/**
389
	 * Delete item
390
	 *
391
	 * @param string                    $table
392
	 * @param callable[]|string[]       $data_model
393
	 * @param int|int[]|string|string[] $id
394
	 *
395
	 * @return bool
396
	 */
397 14
	private function delete_internal ($table, $data_model, $id) {
398 14
		$id           = (array)$id;
399 14
		$result       = true;
400 14
		$multilingual = $this->is_multilingual();
401 14
		$first_column = array_keys($data_model)[0];
402 14
		foreach ($id as $i) {
403
			$result =
404 14
				$result &&
405 14
				$this->read_internal($table, $data_model, $i) &&
406 14
				$this->db_prime()->q(
407 14
					"DELETE FROM `$table`
408 14
					WHERE `$first_column` = '%s'",
409
					$i
410
				);
411
			/**
412
			 * If there are multilingual fields - handle multilingual deleting of fields automatically
413
			 */
414 14
			if ($multilingual) {
415 2
				foreach (array_keys($this->data_model) as $field) {
416 2
					if (is_string($this->data_model[$field]) && strpos($this->data_model[$field], 'ml:') === 0) {
417 2
						Text::instance()->del($this->cdb(), "$this->data_model_ml_group/$field", $i);
418
					}
419
				}
420
			}
421 14
			$this->update_joined_tables($i, []);
422 14
			$this->delete_files_tags($i);
423
		}
424 14
		return $result;
425
	}
426
}
427