Passed
Push — feature/786_podcasts ( f026ee )
by Pauli
11:46
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
	 * 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
		$this->deleteByCond('`id` IN ' . $this->questionMarks($count), $ids);
152
	}
153
154
	/**
155
	 * Delete all entities matching the given SQL condition
156
	 * @param string $condition SQL 'WHERE' condition (without the keyword 'WHERE')
157
	 * @param array $params SQL parameters for the condition
158
	 */
159
	protected function deleteByCond(string $condition, array $params) : void {
160
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE ". $condition;
161
		$this->execute($sql, $params);
162
	}
163
164
	/**
165
	 * Delete all entities of the given user
166
	 */
167
	public function deleteAll(string $userId) : void {
168
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE `user_id` = ?";
169
		$this->execute($sql, [$userId]);
170
	}
171
172
	/**
173
	 * Tests if entity with given ID and user ID exists in the database
174
	 */
175
	public function exists(int $id, string $userId) : bool {
176
		$sql = "SELECT 1 FROM `{$this->getTableName()}` WHERE `id` = ? AND `user_id` = ?";
177
		$result = $this->execute($sql, [$id, $userId]);
178
		return $result->rowCount() > 0;
179
	}
180
181
	/**
182
	 * Count all entities of a user
183
	 */
184
	public function count(string $userId) : int {
185
		$sql = "SELECT COUNT(*) AS count FROM `{$this->getTableName()}` WHERE `user_id` = ?";
186
		$result = $this->execute($sql, [$userId]);
187
		$row = $result->fetch();
188
		return \intval($row['count']);
189
	}
190
191
	/**
192
	 * {@inheritDoc}
193
	 * @see \OCP\AppFramework\Db\Mapper::insert()
194
	 */
195
	public function insert(Entity $entity) : Entity {
196
		$now = new \DateTime();
197
		$nowStr = $now->format(self::SQL_DATE_FORMAT);
198
		$entity->setCreated($nowStr);
199
		$entity->setUpdated($nowStr);
200
201
		try {
202
			return parent::insert($entity);
203
		} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
204
			throw new UniqueConstraintViolationException($e->getMessage(), $e->getCode(), $e);
205
		} 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...
206
			// Nextcloud 21
207
			if ($e->getReason() == \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
208
				throw new UniqueConstraintViolationException($e->getMessage(), $e->getCode(), $e);
209
			} else {
210
				throw $e;
211
			}
212
		}
213
	}
214
215
	/**
216
	 * {@inheritDoc}
217
	 * @see \OCP\AppFramework\Db\Mapper::update()
218
	 */
219
	public function update(Entity $entity) : Entity {
220
		$now = new \DateTime();
221
		$entity->setUpdated($now->format(self::SQL_DATE_FORMAT));
222
		return parent::update($entity);
223
	}
224
225
	/**
226
	 * Insert an entity, or if an entity with the same identity already exists,
227
	 * update the existing entity.
228
	 * @return Entity The inserted or updated entity, containing also the id field
229
	 */
230
	public function insertOrUpdate(Entity $entity) : Entity {
231
		try {
232
			return $this->insert($entity);
233
		} catch (UniqueConstraintViolationException $ex) {
234
			$existingEntity = $this->findUniqueEntity($entity);
235
			$entity->setId($existingEntity->getId());
236
			$entity->setCreated($existingEntity->getCreated());
237
			return $this->update($entity);
238
		}
239
	}
240
241
	/**
242
	 * Set the "starred" column of the given entities
243
	 * @param \DateTime|null $date
244
	 * @param integer[] $ids
245
	 * @param string $userId
246
	 * @return int number of modified entities
247
	 */
248
	public function setStarredDate(?\DateTime $date, array $ids, string $userId) : int {
249
		$count = \count($ids);
250
		if (!empty($date)) {
251
			$date = $date->format(self::SQL_DATE_FORMAT);
252
		}
253
254
		$sql = "UPDATE `{$this->getTableName()}` SET `starred` = ?
255
				WHERE `id` IN {$this->questionMarks($count)} AND `user_id` = ?";
256
		$params = \array_merge([$date], $ids, [$userId]);
257
		return $this->execute($sql, $params)->rowCount();
258
	}
259
260
	public function latestInsertTime(string $userId) : ?\DateTime {
261
		$sql = "SELECT MAX(`{$this->getTableName()}`.`created`) FROM `{$this->getTableName()}` WHERE `user_id` = ?";
262
		$result = $this->execute($sql, [$userId]);
263
		$createdTime = $result->fetch(\PDO::FETCH_COLUMN);
264
265
		return ($createdTime === null) ? null : new \DateTime($createdTime);
266
	}
267
268
	public function latestUpdateTime(string $userId) : ?\DateTime {
269
		$sql = "SELECT MAX(`{$this->getTableName()}`.`updated`) FROM `{$this->getTableName()}` WHERE `user_id` = ?";
270
		$result = $this->execute($sql, [$userId]);
271
		$createdTime = $result->fetch(\PDO::FETCH_COLUMN);
272
273
		return ($createdTime === null) ? null : new \DateTime($createdTime);
274
	}
275
276
	/**
277
	 * helper creating a string like '(?,?,?)' with the specified number of elements
278
	 */
279
	protected function questionMarks(int $count) : string {
280
		$questionMarks = [];
281
		for ($i = 0; $i < $count; $i++) {
282
			$questionMarks[] = '?';
283
		}
284
		return '(' . \implode(',', $questionMarks) . ')';
285
	}
286
287
	/**
288
	 * Build a SQL SELECT statement which selects all entities of the given user,
289
	 * and optionally applies other conditions, too.
290
	 * This is built upon `selectEntities` which may be overridden by the derived class.
291
	 * @param string|null $condition Optional extra condition. This will get automatically
292
	 *                               prefixed with ' AND ', so don't include that.
293
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
294
	 *                               the conditions in the SQL statement
295
	 */
296
	protected function selectUserEntities(string $condition=null, string $extension=null) : string {
297
		$allConditions = "`{$this->getTableName()}`.`user_id` = ?";
298
299
		if (!empty($condition)) {
300
			$allConditions .= " AND $condition";
301
		}
302
303
		return $this->selectEntities($allConditions, $extension);
304
	}
305
306
	/**
307
	 * Build a SQL SELECT statement which selects all entities matching the given condition.
308
	 * The derived class may override this if necessary.
309
	 * @param string $condition This will get automatically prefixed with ' WHERE '
310
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
311
	 *                               the conditions in the SQL statement
312
	 */
313
	protected function selectEntities(string $condition, string $extension=null) : string {
314
		return "SELECT * FROM `{$this->getTableName()}` WHERE $condition $extension ";
315
	}
316
317
	/**
318
	 * @return array with two values: The SQL condition as string and the SQL parameters as string[]
319
	 */
320
	protected function formatTimestampConditions(?string $createdMin, ?string $createdMax, ?string $updatedMin, ?string $updatedMax) : array {
321
		$conditions = [];
322
		$params = [];
323
		
324
		if (!empty($createdMin)) {
325
			$conditions[] = "`{$this->getTableName()}`.`created` >= ?";
326
			$params[] = $createdMin;
327
		}
328
		
329
		if (!empty($createdMax)) {
330
			$conditions[] = "`{$this->getTableName()}`.`created` <= ?";
331
			$params[] = $createdMax;
332
		}
333
		
334
		if (!empty($updatedMin)) {
335
			$conditions[] = "`{$this->getTableName()}`.`updated` >= ?";
336
			$params[] = $updatedMin;
337
		}
338
		
339
		if (!empty($updatedMax)) {
340
			$conditions[] = "`{$this->getTableName()}`.`updated` <= ?";
341
			$params[] = $updatedMax;
342
		}
343
		
344
		return [\implode(' AND ', $conditions), $params];
345
	}
346
347
	/**
348
	 * Find an entity which has the same identity as the supplied entity.
349
	 * How the identity of the entity is defined, depends on the derived concrete class.
350
	 */
351
	abstract protected function findUniqueEntity(Entity $entity) : Entity;
352
}
353