Passed
Push — feature/786_podcasts ( 3917cd...37128b )
by Pauli
02:11
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 \OCA\Music\AppFramework\Db\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
	 * Find IDs of all user's entities of this kind
144
	 * @return int[]
145
	 */
146
	public function findAllIds(string $userId) : array {
147
		$sql = "SELECT `id` FROM `{$this->getTableName()}` WHERE `user_id` = ?";
148
		$result = $this->execute($sql, [$userId]);
149
150
		return \array_map('intval', $result->fetchAll(\PDO::FETCH_COLUMN));
151
	}
152
153
	/**
154
	 * Find IDs of all users owning any entities of this mapper
155
	 * @return string[]
156
	 */
157
	public function findAllUsers() : array {
158
		$sql = "SELECT DISTINCT(`user_id`) FROM `{$this->getTableName()}`";
159
		$result = $this->execute($sql);
160
161
		return $result->fetchAll(\PDO::FETCH_COLUMN);
162
	}
163
164
	/**
165
	 * Delete all entities with given IDs without specifying the user
166
	 * @param integer[] $ids  IDs of the entities to be deleted
167
	 */
168
	public function deleteById(array $ids) : void {
169
		$count = \count($ids);
170
		if ($count === 0) {
171
			return;
172
		}
173
		$this->deleteByCond('`id` IN ' . $this->questionMarks($count), $ids);
174
	}
175
176
	/**
177
	 * Delete all entities matching the given SQL condition
178
	 * @param string $condition SQL 'WHERE' condition (without the keyword 'WHERE')
179
	 * @param array $params SQL parameters for the condition
180
	 */
181
	protected function deleteByCond(string $condition, array $params) : void {
182
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE ". $condition;
183
		$this->execute($sql, $params);
184
	}
185
186
	/**
187
	 * Delete all entities of the given user
188
	 */
189
	public function deleteAll(string $userId) : void {
190
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE `user_id` = ?";
191
		$this->execute($sql, [$userId]);
192
	}
193
194
	/**
195
	 * Tests if entity with given ID and user ID exists in the database
196
	 */
197
	public function exists(int $id, string $userId) : bool {
198
		$sql = "SELECT 1 FROM `{$this->getTableName()}` WHERE `id` = ? AND `user_id` = ?";
199
		$result = $this->execute($sql, [$id, $userId]);
200
		return $result->rowCount() > 0;
201
	}
202
203
	/**
204
	 * Count all entities of a user
205
	 */
206
	public function count(string $userId) : int {
207
		$sql = "SELECT COUNT(*) AS count FROM `{$this->getTableName()}` WHERE `user_id` = ?";
208
		$result = $this->execute($sql, [$userId]);
209
		$row = $result->fetch();
210
		return \intval($row['count']);
211
	}
212
213
	/**
214
	 * {@inheritDoc}
215
	 * @see \OCP\AppFramework\Db\Mapper::insert()
216
	 */
217
	public function insert(Entity $entity) : Entity {
218
		$now = new \DateTime();
219
		$nowStr = $now->format(self::SQL_DATE_FORMAT);
220
		$entity->setCreated($nowStr);
221
		$entity->setUpdated($nowStr);
222
223
		try {
224
			return parent::insert($entity);
225
		} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
226
			throw new UniqueConstraintViolationException($e->getMessage(), $e->getCode(), $e);
227
		} catch (\OCP\DB\Exception $e) {
0 ignored issues
show
Bug introduced by
The type OCP\DB\Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
228
			// Nextcloud 21
229
			if ($e->getReason() == \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
230
				throw new UniqueConstraintViolationException($e->getMessage(), $e->getCode(), $e);
231
			} else {
232
				throw $e;
233
			}
234
		}
235
	}
236
237
	/**
238
	 * {@inheritDoc}
239
	 * @see \OCP\AppFramework\Db\Mapper::update()
240
	 */
241
	public function update(Entity $entity) : Entity {
242
		$now = new \DateTime();
243
		$entity->setUpdated($now->format(self::SQL_DATE_FORMAT));
244
		return parent::update($entity);
245
	}
246
247
	/**
248
	 * Insert an entity, or if an entity with the same identity already exists,
249
	 * update the existing entity.
250
	 * @return Entity The inserted or updated entity, containing also the id field
251
	 */
252
	public function insertOrUpdate(Entity $entity) : Entity {
253
		try {
254
			return $this->insert($entity);
255
		} catch (UniqueConstraintViolationException $ex) {
256
			$existingEntity = $this->findUniqueEntity($entity);
257
			$entity->setId($existingEntity->getId());
258
			$entity->setCreated($existingEntity->getCreated());
259
			return $this->update($entity);
260
		}
261
	}
262
263
	/**
264
	 * Set the "starred" column of the given entities
265
	 * @param \DateTime|null $date
266
	 * @param integer[] $ids
267
	 * @param string $userId
268
	 * @return int number of modified entities
269
	 */
270
	public function setStarredDate(?\DateTime $date, array $ids, string $userId) : int {
271
		$count = \count($ids);
272
		if (!empty($date)) {
273
			$date = $date->format(self::SQL_DATE_FORMAT);
274
		}
275
276
		$sql = "UPDATE `{$this->getTableName()}` SET `starred` = ?
277
				WHERE `id` IN {$this->questionMarks($count)} AND `user_id` = ?";
278
		$params = \array_merge([$date], $ids, [$userId]);
279
		return $this->execute($sql, $params)->rowCount();
280
	}
281
282
	public function latestInsertTime(string $userId) : ?\DateTime {
283
		$sql = "SELECT MAX(`{$this->getTableName()}`.`created`) FROM `{$this->getTableName()}` WHERE `user_id` = ?";
284
		$result = $this->execute($sql, [$userId]);
285
		$createdTime = $result->fetch(\PDO::FETCH_COLUMN);
286
287
		return ($createdTime === null) ? null : new \DateTime($createdTime);
288
	}
289
290
	public function latestUpdateTime(string $userId) : ?\DateTime {
291
		$sql = "SELECT MAX(`{$this->getTableName()}`.`updated`) FROM `{$this->getTableName()}` WHERE `user_id` = ?";
292
		$result = $this->execute($sql, [$userId]);
293
		$createdTime = $result->fetch(\PDO::FETCH_COLUMN);
294
295
		return ($createdTime === null) ? null : new \DateTime($createdTime);
296
	}
297
298
	/**
299
	 * helper creating a string like '(?,?,?)' with the specified number of elements
300
	 */
301
	protected function questionMarks(int $count) : string {
302
		$questionMarks = [];
303
		for ($i = 0; $i < $count; $i++) {
304
			$questionMarks[] = '?';
305
		}
306
		return '(' . \implode(',', $questionMarks) . ')';
307
	}
308
309
	/**
310
	 * Build a SQL SELECT statement which selects all entities of the given user,
311
	 * and optionally applies other conditions, too.
312
	 * This is built upon `selectEntities` which may be overridden by the derived class.
313
	 * @param string|null $condition Optional extra condition. This will get automatically
314
	 *                               prefixed with ' AND ', so don't include that.
315
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
316
	 *                               the conditions in the SQL statement
317
	 */
318
	protected function selectUserEntities(string $condition=null, string $extension=null) : string {
319
		$allConditions = "`{$this->getTableName()}`.`user_id` = ?";
320
321
		if (!empty($condition)) {
322
			$allConditions .= " AND $condition";
323
		}
324
325
		return $this->selectEntities($allConditions, $extension);
326
	}
327
328
	/**
329
	 * Build a SQL SELECT statement which selects all entities matching the given condition.
330
	 * The derived class may override this if necessary.
331
	 * @param string $condition This will get automatically prefixed with ' WHERE '
332
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
333
	 *                               the conditions in the SQL statement
334
	 */
335
	protected function selectEntities(string $condition, string $extension=null) : string {
336
		return "SELECT * FROM `{$this->getTableName()}` WHERE $condition $extension ";
337
	}
338
339
	/**
340
	 * @return array with two values: The SQL condition as string and the SQL parameters as string[]
341
	 */
342
	protected function formatTimestampConditions(?string $createdMin, ?string $createdMax, ?string $updatedMin, ?string $updatedMax) : array {
343
		$conditions = [];
344
		$params = [];
345
346
		if (!empty($createdMin)) {
347
			$conditions[] = "`{$this->getTableName()}`.`created` >= ?";
348
			$params[] = $createdMin;
349
		}
350
351
		if (!empty($createdMax)) {
352
			$conditions[] = "`{$this->getTableName()}`.`created` <= ?";
353
			$params[] = $createdMax;
354
		}
355
356
		if (!empty($updatedMin)) {
357
			$conditions[] = "`{$this->getTableName()}`.`updated` >= ?";
358
			$params[] = $updatedMin;
359
		}
360
361
		if (!empty($updatedMax)) {
362
			$conditions[] = "`{$this->getTableName()}`.`updated` <= ?";
363
			$params[] = $updatedMax;
364
		}
365
366
		return [\implode(' AND ', $conditions), $params];
367
	}
368
369
	/**
370
	 * Find an entity which has the same identity as the supplied entity.
371
	 * How the identity of the entity is defined, depends on the derived concrete class.
372
	 */
373
	abstract protected function findUniqueEntity(Entity $entity) : Entity;
374
}
375