Passed
Push — master ( 2adfe5...ed0bae )
by Pauli
04:04
created

BusinessLayer::findAllIdsAndNames()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
c 0
b 0
f 0
nc 4
nop 11
dl 0
loc 16
rs 9.9332

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
 * ownCloud
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Alessandro Cosentino <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Alessandro Cosentino 2012
12
 * @copyright Bernhard Posselt 2012, 2014
13
 * @copyright Pauli Järvinen 2017 - 2024
14
 */
15
16
namespace OCA\Music\AppFramework\BusinessLayer;
17
18
use OCA\Music\Db\BaseMapper;
19
use OCA\Music\Db\Entity;
20
use OCA\Music\Db\MatchMode;
21
use OCA\Music\Db\SortBy;
22
use OCA\Music\Utility\Util;
23
use OCA\Music\Utility\Random;
24
25
use OCP\AppFramework\Db\DoesNotExistException;
26
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
27
use OCP\IL10N;
28
29
/**
30
 * @phpstan-template EntityType of Entity
31
 */
32
abstract class BusinessLayer {
33
	protected $mapper;
34
35
	// Some SQLite installations can't handle more than 999 query args. Remember that `user_id` takes one slot in most queries.
36
	public const MAX_SQL_ARGS = 999;
37
38
	/**
39
	 * @phpstan-param BaseMapper<EntityType> $mapper
40
	 */
41
	public function __construct(BaseMapper $mapper) {
42
		$this->mapper = $mapper;
43
	}
44
45
	/**
46
	 * Update an entity in the database
47
	 * @phpstan-param EntityType $entity
48
	 * @phpstan-return EntityType
49
	 */
50
	public function update(Entity $entity) : Entity {
51
		return $this->mapper->update($entity);
52
	}
53
54
	/**
55
	 * Delete an entity
56
	 * @param int $id the id of the entity
57
	 * @param string $userId the name of the user for security reasons
58
	 * @throws BusinessLayerException if the entity does not exist or more than one entity exists
59
	 * @phpstan-return EntityType
60
	 */
61
	public function delete(int $id, string $userId) : Entity {
62
		$entity = $this->find($id, $userId);
63
		return $this->mapper->delete($entity);
64
	}
65
66
	/**
67
	 * Deletes entities without specifying the owning user.
68
	 * This should never be called directly from the HTML API, but only in case
69
	 * we can actually trust the passed IDs (e.g. file deleted hook).
70
	 * @param array $ids the ids of the entities which should be deleted
71
	 */
72
	public function deleteById(array $ids) : void {
73
		if (\count($ids) > 0) {
74
			$this->mapper->deleteById($ids);
75
		}
76
	}
77
78
	/**
79
	 * Delete all entities of the given user
80
	 */
81
	public function deleteAll(string $userId) : void {
82
		$this->mapper->deleteAll($userId);
83
	}
84
85
	/**
86
	 * Finds an entity by id
87
	 * @param int $id the id of the entity
88
	 * @param string $userId the name of the user for security reasons
89
	 * @throws BusinessLayerException if the entity does not exist or more than one entity exists
90
	 * @phpstan-return EntityType
91
	 */
92
	public function find(int $id, string $userId) : Entity {
93
		try {
94
			return $this->mapper->find($id, $userId);
95
		} catch (DoesNotExistException $ex) {
96
			throw new BusinessLayerException($ex->getMessage());
97
		} catch (MultipleObjectsReturnedException $ex) {
98
			throw new BusinessLayerException($ex->getMessage());
99
		}
100
	}
101
102
	/**
103
	 * Finds an entity by id, or returns an empty entity instance if the requested one is not found
104
	 * @param int $id the id of the entity
105
	 * @param string $userId the name of the user for security reasons
106
	 * @phpstan-return EntityType
107
	 */
108
	public function findOrDefault(int $id, string $userId) : Entity {
109
		try {
110
			return $this->find($id, $userId);
111
		} catch (BusinessLayerException $ex) {
112
			return $this->mapper->createEntity();
113
		}
114
	}
115
116
	/**
117
	 * Find all entities matching the given IDs.
118
	 * Specifying the user is optional; if omitted, the caller should make sure that
119
	 * user's data is not leaked to unauthorized users.
120
	 * @param integer[] $ids  IDs of the entities to be found
121
	 * @param string|null $userId
122
	 * @param bool $preserveOrder If true, then the result will be in the same order as @a $ids
123
	 * @return Entity[]
124
	 * @phpstan-return EntityType[]
125
	 */
126
	public function findById(array $ids, string $userId=null, bool $preserveOrder=false) : array {
127
		$entities = [];
128
		if (\count($ids) > 0) {
129
			// don't use more than 999 SQL args in one query since that may be a problem for SQLite
130
			$idChunks = \array_chunk($ids, 998);
131
			foreach ($idChunks as $idChunk) {
132
				$entities = \array_merge($entities, $this->mapper->findById($idChunk, $userId));
133
			}
134
		}
135
136
		if ($preserveOrder) {
137
			$lut = Util::createIdLookupTable($entities);
138
			$result = [];
139
			foreach ($ids as $id) {
140
				$result[] = $lut[$id];
141
			}
142
		} else {
143
			$result = $entities;
144
		}
145
146
		return $result;
147
	}
148
149
	/**
150
	 * Finds all entities
151
	 * @param string $userId the name of the user
152
	 * @param integer $sortBy sort order of the result set
153
	 * @param integer|null $limit
154
	 * @param integer|null $offset
155
	 * @param string|null $createdMin Optional minimum `created` timestamp.
156
	 * @param string|null $createdMax Optional maximum `created` timestamp.
157
	 * @param string|null $updatedMin Optional minimum `updated` timestamp.
158
	 * @param string|null $updatedMax Optional maximum `updated` timestamp.
159
	 * @return Entity[]
160
	 * @phpstan-return EntityType[]
161
	 */
162
	public function findAll(
163
			string $userId, int $sortBy=SortBy::Name, ?int $limit=null, ?int $offset=null,
164
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
165
		return $this->mapper->findAll($userId, $sortBy, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax);
166
	}
167
168
	/**
169
	 * Return all entities with name matching the search criteria
170
	 * @param string|null $createdMin Optional minimum `created` timestamp.
171
	 * @param string|null $createdMax Optional maximum `created` timestamp.
172
	 * @param string|null $updatedMin Optional minimum `updated` timestamp.
173
	 * @param string|null $updatedMax Optional maximum `updated` timestamp.
174
	 * @return Entity[]
175
	 * @phpstan-return EntityType[]
176
	 */
177
	public function findAllByName(
178
			?string $name, string $userId, int $matchMode=MatchMode::Exact, ?int $limit=null, ?int $offset=null,
179
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
180
		if ($name !== null) {
181
			$name = \trim($name);
182
		}
183
		return $this->mapper->findAllByName($name, $userId, $matchMode, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax);
184
	}
185
186
	/**
187
	 * Find all starred entities
188
	 * @return Entity[]
189
	 * @phpstan-return EntityType[]
190
	 */
191
	public function findAllStarred(string $userId, ?int $limit=null, ?int $offset=null) : array {
192
		return $this->mapper->findAllStarred($userId, $limit, $offset);
193
	}
194
195
	/**
196
	 * Find IDSs of all starred entities
197
	 * @return int[]
198
	 */
199
	public function findAllStarredIds(string $userId) : array {
200
		return $this->mapper->findAllStarredIds($userId);
201
	}
202
203
	/**
204
	 * Find all entities with user-given rating 1-5
205
	 * @return Entity[]
206
	 * @phpstan-return EntityType[]
207
	 */
208
	public function findAllRated(string $userId, ?int $limit=null, ?int $offset=null) : array {
209
		return $this->mapper->findAllRated($userId, $limit, $offset);
210
	}
211
212
	/**
213
	 * Find all entities matching multiple criteria, as needed for the Ampache API method `advanced_search`
214
	 * @param string $conjunction Operator to use between the rules, either 'and' or 'or'
215
	 * @param array $rules Array of arrays: [['rule' => string, 'operator' => string, 'input' => string], ...]
216
	 * 				Here, 'rule' has dozens of possible values depending on the business layer in question,
217
	 * 				(see https://ampache.org/api/api-advanced-search#available-search-rules, alias names not supported here),
218
	 * 				'operator' is one of 
219
	 * 				['contain', 'notcontain', 'start', 'end', 'is', 'isnot', 'sounds', 'notsounds', 'regexp', 'notregexp',
220
	 * 				 '>=', '<=', '=', '!=', '>', '<', 'before', 'after', 'true', 'false', 'equal', 'ne', 'limit'],
221
	 * 				'input' is the right side value of the 'operator' (disregarded for the operators 'true' and 'false')
222
	 * @param Random $random When the randomization utility is passed, the result set will be in random order (still supporting proper paging).
223
	 * 						 In this case, the argument $sortBy is ignored.
224
	 * @return Entity[]
225
	 * @phpstan-return EntityType[]
226
	 */
227
	public function findAllAdvanced(
228
			string $conjunction, array $rules, string $userId, int $sortBy=SortBy::Name,
229
			?Random $random=null, ?int $limit=null, ?int $offset=null) : array {
230
231
		if ($conjunction !== 'and' && $conjunction !== 'or') {
232
			throw new BusinessLayerException("Bad conjunction '$conjunction'");
233
		}
234
		try {
235
			if ($random !== null) {
236
				// in case the random order is requested, the limit/offset handling happens after the DB query
237
				$entities = $this->mapper->findAllAdvanced($conjunction, $rules, $userId, SortBy::Name);
238
				$indices = $random->getIndices(\count($entities), $offset, $limit, $userId, 'adv_search_'.$this->mapper->unprefixedTableName());
239
				$entities = Util::arrayMultiGet($entities, $indices);
240
			} else {
241
				$entities = $this->mapper->findAllAdvanced($conjunction, $rules, $userId, $sortBy, $limit, $offset);
242
			}
243
			return $entities;
244
		} catch (\Exception $e) {
245
			// catch everything as many kinds of DB exceptions are possible on various cloud versions
246
			throw new BusinessLayerException($e->getMessage());
247
		}
248
	}
249
250
	/**
251
	 * Find IDs of all user's entities of this kind.
252
	 * Optionally, limit to given IDs which may be used to check the validity of those IDs.
253
	 * @return int[]
254
	 */
255
	public function findAllIds(string $userId, ?array $ids = null) : array {
256
		if ($ids === null || \count($ids) > 0) {
257
			return $this->mapper->findAllIds($userId, $ids);
258
		} else {
259
			return [];
260
		}
261
	}
262
263
	/**
264
	 * Find all entity IDs grouped by the given parent entity IDs. Not applicable on all entity types.
265
	 * @param int[] $parentIds
266
	 * @return array like [parentId => childIds[]]; some parents may have an empty array of children
267
	 * @throws BusinessLayerException if the entity type handled by this business layer doesn't have a parent relation
268
	 */
269
	public function findAllIdsByParentIds(string $userId, array $parentIds) : ?array {
270
		try {
271
			return $this->mapper->findAllIdsByParentIds($userId, $parentIds);
272
		} catch (\DomainException $ex) {
273
			throw new BusinessLayerException($ex->getMessage());
274
		}
275
	}
276
277
	/**
278
	 * Find all IDs and names of user's entities of this kind.
279
	 * Optionally, limit results based on a parent entity (not applicable for all entity types) or update/insert times or name
280
	 * @param bool $excludeChildless Exclude entities having no child-entities if applicable for this business layer (eg. artists without albums)
281
	 * @return array of arrays like ['id' => string, 'name' => string]
282
	 */
283
	public function findAllIdsAndNames(string $userId, IL10N $l10n, ?int $parentId=null, ?int $limit=null, ?int $offset=null,
284
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null,
285
			bool $excludeChildless=false, ?string $name=null) : array {
286
		try {
287
			$idsAndNames = $this->mapper->findAllIdsAndNames(
288
				$userId, $parentId, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax, $excludeChildless, $name);
289
		} catch (\DomainException $ex) {
290
			throw new BusinessLayerException($ex->getMessage());
291
		}
292
		foreach ($idsAndNames as &$idAndName) {
293
			if (empty($idAndName['name'])) {
294
				$emptyEntity = $this->mapper->createEntity();
295
				$idAndName['name'] = $emptyEntity->getNameString($l10n);
296
			}
297
		}
298
		return $idsAndNames;
299
	}
300
301
	/**
302
	 * Find IDs of all users owning any entities of this business layer
303
	 * @return string[]
304
	 */
305
	public function findAllUsers() : array {
306
		return $this->mapper->findAllUsers();
307
	}
308
309
	/**
310
	 * Set the given entities as "starred" on this date
311
	 * @param int[] $ids
312
	 * @param string $userId
313
	 * @return int number of modified entities
314
	 */
315
	public function setStarred(array $ids, string $userId) : int {
316
		if (\count($ids) > 0) {
317
			return $this->mapper->setStarredDate(new \DateTime(), $ids, $userId);
318
		} else {
319
			return 0;
320
		}
321
	}
322
323
	/**
324
	 * Remove the "starred" status of the given entities
325
	 * @param integer[] $ids
326
	 * @param string $userId
327
	 * @return int number of modified entities
328
	 */
329
	public function unsetStarred(array $ids, string $userId) : int {
330
		if (\count($ids) > 0) {
331
			return $this->mapper->setStarredDate(null, $ids, $userId);
332
		} else {
333
			return 0;
334
		}
335
	}
336
337
	/**
338
	 * Tests if entity with given ID and user ID exists in the database
339
	 */
340
	public function exists(int $id, string $userId) : bool {
341
		return $this->mapper->exists($id, $userId);
342
	}
343
344
	/**
345
	 * Get the number of entities
346
	 */
347
	public function count(string $userId) : int {
348
		return $this->mapper->count($userId);
349
	}
350
351
	/**
352
	 * Get the largest entity ID of the user
353
	 */
354
	public function maxId(string $userId) : ?int {
355
		return $this->mapper->maxId($userId);
356
	}
357
358
	/**
359
	 * Get the timestamp of the latest insert operation on the entity type in question
360
	 */
361
	public function latestInsertTime(string $userId) : \DateTime {
362
		return $this->mapper->latestInsertTime($userId) ?? new \DateTime('1970-01-01');
363
	}
364
365
	/**
366
	 * Get the timestamp of the latest update operation on the entity type in question
367
	 */
368
	public function latestUpdateTime(string $userId) : \DateTime {
369
		return $this->mapper->latestUpdateTime($userId) ?? new \DateTime('1970-01-01');
370
	}
371
}
372