Passed
Push — feature/786_podcasts ( 7b8be7...af6910 )
by Pauli
02:22
created

BaseMapper::findAllByName()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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