Passed
Push — master ( 91b14a...fc89b7 )
by Pauli
02:49
created

BusinessLayer::findAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 8
dl 0
loc 4
rs 10
c 0
b 0
f 0

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 all entities with user-given rating 1-5
197
	 * @return Entity[]
198
	 * @phpstan-return EntityType[]
199
	 */
200
	public function findAllRated(string $userId, ?int $limit=null, ?int $offset=null) : array {
201
		return $this->mapper->findAllRated($userId, $limit, $offset);
202
	}
203
204
	/**
205
	 * Find all entities matching multiple criteria, as needed for the Ampache API method `advanced_search`
206
	 * @param string $conjunction Operator to use between the rules, either 'and' or 'or'
207
	 * @param array $rules Array of arrays: [['rule' => string, 'operator' => string, 'input' => string], ...]
208
	 * 				Here, 'rule' has dozens of possible values depending on the business layer in question,
209
	 * 				(see https://ampache.org/api/api-advanced-search#available-search-rules, alias names not supported here),
210
	 * 				'operator' is one of 
211
	 * 				['contain', 'notcontain', 'start', 'end', 'is', 'isnot', 'sounds', 'notsounds', 'regexp', 'notregexp',
212
	 * 				 '>=', '<=', '=', '!=', '>', '<', 'before', 'after', 'true', 'false', 'equal', 'ne', 'limit'],
213
	 * 				'input' is the right side value of the 'operator' (disregarded for the operators 'true' and 'false')
214
	 * @param Random $random When the randomization utility is passed, the result set will be in random order (still supporting proper paging).
215
	 * 						 In this case, the argument $sortBy is ignored.
216
	 * @return Entity[]
217
	 * @phpstan-return EntityType[]
218
	 */
219
	public function findAllAdvanced(
220
			string $conjunction, array $rules, string $userId, int $sortBy=SortBy::Name,
221
			?Random $random=null, ?int $limit=null, ?int $offset=null) : array {
222
223
		if ($conjunction !== 'and' && $conjunction !== 'or') {
224
			throw new BusinessLayerException("Bad conjunction '$conjunction'");
225
		}
226
		try {
227
			if ($random !== null) {
228
				// in case the random order is requested, the limit/offset handling happens after the DB query
229
				$entities = $this->mapper->findAllAdvanced($conjunction, $rules, $userId, SortBy::Name);
230
				$indices = $random->getIndices(\count($entities), $offset, $limit, $userId, 'adv_search_'.$this->mapper->unprefixedTableName());
231
				$entities = Util::arrayMultiGet($entities, $indices);
232
			} else {
233
				$entities = $this->mapper->findAllAdvanced($conjunction, $rules, $userId, $sortBy, $limit, $offset);
234
			}
235
			return $entities;
236
		} catch (\Exception $e) {
237
			// catch everything as many kinds of DB exceptions are possible on various cloud versions
238
			throw new BusinessLayerException($e->getMessage());
239
		}
240
	}
241
242
	/**
243
	 * Find IDs of all user's entities of this kind.
244
	 * Optionally, limit to given IDs which may be used to check the validity of those IDs.
245
	 * @return int[]
246
	 */
247
	public function findAllIds(string $userId, ?array $ids = null) : array {
248
		if ($ids === null || \count($ids) > 0) {
249
			return $this->mapper->findAllIds($userId, $ids);
250
		} else {
251
			return [];
252
		}
253
	}
254
255
	/**
256
	 * Find all entity IDs grouped by the given parent entity IDs. Not applicable on all entity types.
257
	 * @param int[] $parentIds
258
	 * @return array like [parentId => childIds[]]; some parents may have an empty array of children
259
	 * @throws BusinessLayerException if the entity type handled by this business layer doesn't have a parent relation
260
	 */
261
	public function findAllIdsByParentIds(string $userId, array $parentIds) : ?array {
262
		try {
263
			return $this->mapper->findAllIdsByParentIds($userId, $parentIds);
264
		} catch (\DomainException $ex) {
265
			throw new BusinessLayerException($ex->getMessage());
266
		}
267
	}
268
269
	/**
270
	 * Find all IDs and names of user's entities of this kind.
271
	 * Optionally, limit results based on a parent entity (not applicable for all entity types) or update/insert times or name
272
	 * @param bool $excludeChildless Exclude entities having no child-entities if applicable for this business layer (eg. artists without albums)
273
	 * @return array of arrays like ['id' => string, 'name' => string]
274
	 */
275
	public function findAllIdsAndNames(string $userId, IL10N $l10n, ?int $parentId=null, ?int $limit=null, ?int $offset=null,
276
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null,
277
			bool $excludeChildless=false, ?string $name=null) : array {
278
		try {
279
			$idsAndNames = $this->mapper->findAllIdsAndNames(
280
				$userId, $parentId, $limit, $offset, $createdMin, $createdMax, $updatedMin, $updatedMax, $excludeChildless, $name);
281
		} catch (\DomainException $ex) {
282
			throw new BusinessLayerException($ex->getMessage());
283
		}
284
		foreach ($idsAndNames as &$idAndName) {
285
			if (empty($idAndName['name'])) {
286
				$emptyEntity = $this->mapper->createEntity();
287
				$idAndName['name'] = $emptyEntity->getNameString($l10n);
288
			}
289
		}
290
		return $idsAndNames;
291
	}
292
293
	/**
294
	 * Find IDs of all users owning any entities of this business layer
295
	 * @return string[]
296
	 */
297
	public function findAllUsers() : array {
298
		return $this->mapper->findAllUsers();
299
	}
300
301
	/**
302
	 * Set the given entities as "starred" on this date
303
	 * @param int[] $ids
304
	 * @param string $userId
305
	 * @return int number of modified entities
306
	 */
307
	public function setStarred(array $ids, string $userId) : int {
308
		if (\count($ids) > 0) {
309
			return $this->mapper->setStarredDate(new \DateTime(), $ids, $userId);
310
		} else {
311
			return 0;
312
		}
313
	}
314
315
	/**
316
	 * Remove the "starred" status of the given entities
317
	 * @param integer[] $ids
318
	 * @param string $userId
319
	 * @return int number of modified entities
320
	 */
321
	public function unsetStarred(array $ids, string $userId) : int {
322
		if (\count($ids) > 0) {
323
			return $this->mapper->setStarredDate(null, $ids, $userId);
324
		} else {
325
			return 0;
326
		}
327
	}
328
329
	/**
330
	 * Tests if entity with given ID and user ID exists in the database
331
	 * @param int $id
332
	 * @param string $userId
333
	 * @return bool
334
	 */
335
	public function exists(int $id, string $userId) : bool {
336
		return $this->mapper->exists($id, $userId);
337
	}
338
339
	/**
340
	 * Get the number of entities
341
	 * @param string $userId
342
	 */
343
	public function count(string $userId) : int {
344
		return $this->mapper->count($userId);
345
	}
346
347
	/**
348
	 * Get the timestamp of the latest insert operation on the entity type in question
349
	 */
350
	public function latestInsertTime(string $userId) : \DateTime {
351
		return $this->mapper->latestInsertTime($userId) ?? new \DateTime('1970-01-01');
352
	}
353
354
	/**
355
	 * Get the timestamp of the latest update operation on the entity type in question
356
	 */
357
	public function latestUpdateTime(string $userId) : \DateTime {
358
		return $this->mapper->latestUpdateTime($userId) ?? new \DateTime('1970-01-01');
359
	}
360
}
361