Completed
Push — master ( a1fd42...b75635 )
by Nazar
04:14
created

CRUD::read()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

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