Completed
Push — master ( 32eaad...6cdc53 )
by Nazar
04:10
created

CRUD::delete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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