Passed
Push — master ( d3429f...53215a )
by Pauli
02:01
created

BaseMapper::findAll()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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 declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2016 - 2021
11
 */
12
13
namespace OCA\Music\Db;
14
15
use \OCP\AppFramework\Db\DoesNotExistException;
16
use \OCP\AppFramework\Db\Entity;
17
use \OCP\AppFramework\Db\Mapper;
18
use \OCP\AppFramework\Db\MultipleObjectsReturnedException;
19
use \OCP\IDBConnection;
20
21
use \Doctrine\DBAL\Exception\UniqueConstraintViolationException;
22
23
/**
24
 * Common base class for data access classes of the Music app
25
 */
26
abstract class BaseMapper extends Mapper {
27
	const SQL_DATE_FORMAT = 'Y-m-d H:i:s.v';
28
29
	protected $nameColumn;
30
31
	public function __construct(IDBConnection $db, string $tableName, string $entityClass, string $nameColumn) {
32
		parent::__construct($db, $tableName, $entityClass);
33
		$this->nameColumn = $nameColumn;
34
	}
35
36
	/**
37
	 * Create an empty object of the entity class bound to this mapper
38
	 */
39
	public function createEntity() : Entity {
40
		return new $this->entityClass();
41
	}
42
43
	/**
44
	 * Find a single entity by id and user_id
45
	 * @throws DoesNotExistException if the entity does not exist
46
	 * @throws MultipleObjectsReturnedException if more than one entity exists
47
	 */
48
	public function find(int $id, string $userId) : Entity {
49
		$sql = $this->selectUserEntities("`{$this->getTableName()}`.`id` = ?");
50
		return $this->findEntity($sql, [$userId, $id]);
51
	}
52
53
	/**
54
	 * Find all entities matching the given IDs. Specifying the owning user is optional.
55
	 * @param integer[] $ids  IDs of the entities to be found
56
	 * @param string|null $userId
57
	 * @return Entity[]
58
	 */
59
	public function findById(array $ids, string $userId=null) : array {
60
		$count = \count($ids);
61
		$condition = "`{$this->getTableName()}`.`id` IN ". $this->questionMarks($count);
62
63
		if (empty($userId)) {
64
			$sql = $this->selectEntities($condition);
65
		} else {
66
			$sql = $this->selectUserEntities($condition);
67
			$ids = \array_merge([$userId], $ids);
68
		}
69
70
		return $this->findEntities($sql, $ids);
71
	}
72
73
	/**
74
	 * Find all user's entities
75
	 * @param string|null $createdMin Optional minimum `created` timestamp.
76
	 * @param string|null $createdMax Optional maximum `created` timestamp.
77
	 * @param string|null $updatedMin Optional minimum `updated` timestamp.
78
	 * @param string|null $updatedMax Optional maximum `updated` timestamp.
79
	 * @return Entity[]
80
	 */
81
	public function findAll(string $userId, int $sortBy=SortBy::None, int $limit=null, int $offset=null,
82
							?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
83
		if ($sortBy == SortBy::Name) {
84
			$sorting = "ORDER BY LOWER(`{$this->getTableName()}`.`{$this->nameColumn}`)";
85
		} elseif ($sortBy == SortBy::Newest) {
86
			$sorting = "ORDER BY `{$this->getTableName()}`.`id` DESC"; // abuse the fact that IDs are ever-incrementing values
87
		} else {
88
			$sorting = null;
89
		}
90
		[$condition, $params] = $this->formatTimestampConditions($createdMin, $createdMax, $updatedMin, $updatedMax);
91
		$sql = $this->selectUserEntities($condition, $sorting);
92
		\array_unshift($params, $userId);
93
		return $this->findEntities($sql, $params, $limit, $offset);
94
	}
95
96
	/**
97
	 * Find all user's entities matching the given name
98
	 * @param string|null $createdMin Optional minimum `created` timestamp.
99
	 * @param string|null $createdMax Optional maximum `created` timestamp.
100
	 * @param string|null $updatedMin Optional minimum `updated` timestamp.
101
	 * @param string|null $updatedMax Optional maximum `updated` timestamp.
102
	 * @return Entity[]
103
	 */
104
	public function findAllByName(
105
		?string $name, string $userId, bool $fuzzy=false, int $limit=null, int $offset=null,
106
		?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
107
108
		$nameCol = "`{$this->getTableName()}`.`{$this->nameColumn}`";
109
		if ($name === null) {
110
			$condition = "$nameCol IS NULL";
111
			$params = [$userId];
112
		} elseif ($fuzzy) {
113
			$condition = "LOWER($nameCol) LIKE LOWER(?)";
114
			$params = [$userId, "%$name%"];
115
		} else {
116
			$condition = "$nameCol = ?";
117
			$params = [$userId, $name];
118
		}
119
120
		[$timestampConds, $timestampParams] = $this->formatTimestampConditions($createdMin, $createdMax, $updatedMin, $updatedMax);
121
		if (!empty($timestampConds)) {
122
			$condition .= ' AND ' . $timestampConds;
123
			$params = \array_merge($params, $timestampParams);
124
		}
125
126
		$sql = $this->selectUserEntities($condition, "ORDER BY LOWER($nameCol)");
127
128
		return $this->findEntities($sql, $params, $limit, $offset);
129
	}
130
131
	/**
132
	 * Find all user's starred entities
133
	 * @return Entity[]
134
	 */
135
	public function findAllStarred(string $userId, int $limit=null, int $offset=null) : array {
136
		$sql = $this->selectUserEntities(
137
				"`{$this->getTableName()}`.`starred` IS NOT NULL",
138
				"ORDER BY LOWER(`{$this->getTableName()}`.`{$this->nameColumn}`)");
139
		return $this->findEntities($sql, [$userId], $limit, $offset);
140
	}
141
142
	/**
143
	 * Delete all entities with given IDs without specifying the user
144
	 * @param integer[] $ids  IDs of the entities to be deleted
145
	 */
146
	public function deleteById(array $ids) : void {
147
		$count = \count($ids);
148
		if ($count === 0) {
149
			return;
150
		}
151
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE `id` IN ". $this->questionMarks($count);
152
		$this->execute($sql, $ids);
153
	}
154
155
	/**
156
	 * Delete all entities of the given user
157
	 */
158
	public function deleteAll(string $userId) : void {
159
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE `user_id` = ?";
160
		$this->execute($sql, [$userId]);
161
	}
162
163
	/**
164
	 * Tests if entity with given ID and user ID exists in the database
165
	 */
166
	public function exists(int $id, string $userId) : bool {
167
		$sql = "SELECT 1 FROM `{$this->getTableName()}` WHERE `id` = ? AND `user_id` = ?";
168
		$result = $this->execute($sql, [$id, $userId]);
169
		return $result->rowCount() > 0;
170
	}
171
172
	/**
173
	 * Count all entities of a user
174
	 */
175
	public function count(string $userId) : int {
176
		$sql = "SELECT COUNT(*) AS count FROM `{$this->getTableName()}` WHERE `user_id` = ?";
177
		$result = $this->execute($sql, [$userId]);
178
		$row = $result->fetch();
179
		return \intval($row['count']);
180
	}
181
182
	/**
183
	 * {@inheritDoc}
184
	 * @see \OCP\AppFramework\Db\Mapper::insert()
185
	 */
186
	public function insert(Entity $entity) : Entity {
187
		$now = new \DateTime();
188
		$nowStr = $now->format(self::SQL_DATE_FORMAT);
189
		$entity->setCreated($nowStr);
190
		$entity->setUpdated($nowStr);
191
		return parent::insert($entity);
192
	}
193
194
	/**
195
	 * {@inheritDoc}
196
	 * @see \OCP\AppFramework\Db\Mapper::update()
197
	 */
198
	public function update(Entity $entity) : Entity {
199
		$now = new \DateTime();
200
		$entity->setUpdated($now->format(self::SQL_DATE_FORMAT));
201
		return parent::update($entity);
202
	}
203
204
	/**
205
	 * Insert an entity, or if an entity with the same identity already exists,
206
	 * update the existing entity.
207
	 * @return Entity The inserted or updated entity, containing also the id field
208
	 */
209
	public function insertOrUpdate(Entity $entity) : Entity {
210
		try {
211
			return $this->insert($entity);
212
		} catch (UniqueConstraintViolationException $ex) {
213
			$existingEntity = $this->findUniqueEntity($entity);
214
			$entity->setId($existingEntity->getId());
215
			$entity->setCreated($existingEntity->getCreated());
216
			return $this->update($entity);
217
		}
218
	}
219
220
	/**
221
	 * Set the "starred" column of the given entities
222
	 * @param \DateTime|null $date
223
	 * @param integer[] $ids
224
	 * @param string $userId
225
	 * @return int number of modified entities
226
	 */
227
	public function setStarredDate(?\DateTime $date, array $ids, string $userId) : int {
228
		$count = \count($ids);
229
		if (!empty($date)) {
230
			$date = $date->format(self::SQL_DATE_FORMAT);
231
		}
232
233
		$sql = "UPDATE `{$this->getTableName()}` SET `starred` = ?
234
				WHERE `id` IN {$this->questionMarks($count)} AND `user_id` = ?";
235
		$params = \array_merge([$date], $ids, [$userId]);
236
		return $this->execute($sql, $params)->rowCount();
237
	}
238
239
	public function latestInsertTime(string $userId) : ?\DateTime {
240
		$sql = "SELECT MAX(`{$this->getTableName()}`.`created`) FROM `{$this->getTableName()}` WHERE `user_id` = ?";
241
		$result = $this->execute($sql, [$userId]);
242
		$createdTime = $result->fetch(\PDO::FETCH_COLUMN);
243
244
		return ($createdTime === null) ? null : new \DateTime($createdTime);
245
	}
246
247
	public function latestUpdateTime(string $userId) : ?\DateTime {
248
		$sql = "SELECT MAX(`{$this->getTableName()}`.`updated`) FROM `{$this->getTableName()}` WHERE `user_id` = ?";
249
		$result = $this->execute($sql, [$userId]);
250
		$createdTime = $result->fetch(\PDO::FETCH_COLUMN);
251
252
		return ($createdTime === null) ? null : new \DateTime($createdTime);
253
	}
254
255
	/**
256
	 * helper creating a string like '(?,?,?)' with the specified number of elements
257
	 */
258
	protected function questionMarks(int $count) : string {
259
		$questionMarks = [];
260
		for ($i = 0; $i < $count; $i++) {
261
			$questionMarks[] = '?';
262
		}
263
		return '(' . \implode(',', $questionMarks) . ')';
264
	}
265
266
	/**
267
	 * Build a SQL SELECT statement which selects all entities of the given user,
268
	 * and optionally applies other conditions, too.
269
	 * This is built upon `selectEntities` which may be overridden by the derived class.
270
	 * @param string|null $condition Optional extra condition. This will get automatically
271
	 *                               prefixed with ' AND ', so don't include that.
272
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
273
	 *                               the conditions in the SQL statement
274
	 */
275
	protected function selectUserEntities(string $condition=null, string $extension=null) : string {
276
		$allConditions = "`{$this->getTableName()}`.`user_id` = ?";
277
278
		if (!empty($condition)) {
279
			$allConditions .= " AND $condition";
280
		}
281
282
		return $this->selectEntities($allConditions, $extension);
283
	}
284
285
	/**
286
	 * Build a SQL SELECT statement which selects all entities matching the given condition.
287
	 * The derived class may override this if necessary.
288
	 * @param string $condition This will get automatically prefixed with ' WHERE '
289
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
290
	 *                               the conditions in the SQL statement
291
	 */
292
	protected function selectEntities(string $condition, string $extension=null) : string {
293
		return "SELECT * FROM `{$this->getTableName()}` WHERE $condition $extension ";
294
	}
295
296
	/**
297
	 * @return array with two values: The SQL condition as string and the SQL parameters as string[]
298
	 */
299
	protected function formatTimestampConditions(?string $createdMin, ?string $createdMax, ?string $updatedMin, ?string $updatedMax) : array {
300
		$conditions = [];
301
		$params = [];
302
		
303
		if (!empty($createdMin)) {
304
			$conditions[] = "`{$this->getTableName()}`.`created` >= ?";
305
			$params[] = $createdMin;
306
		}
307
		
308
		if (!empty($createdMax)) {
309
			$conditions[] = "`{$this->getTableName()}`.`created` <= ?";
310
			$params[] = $createdMax;
311
		}
312
		
313
		if (!empty($updatedMin)) {
314
			$conditions[] = "`{$this->getTableName()}`.`updated` >= ?";
315
			$params[] = $updatedMin;
316
		}
317
		
318
		if (!empty($updatedMax)) {
319
			$conditions[] = "`{$this->getTableName()}`.`updated` <= ?";
320
			$params[] = $updatedMax;
321
		}
322
		
323
		return [\implode(' AND ', $conditions), $params];
324
	}
325
326
	/**
327
	 * Find an entity which has the same identity as the supplied entity.
328
	 * How the identity of the entity is defined, depends on the derived concrete class.
329
	 */
330
	abstract protected function findUniqueEntity(Entity $entity) : Entity;
331
}
332