Passed
Push — master ( 89d351...c23d0c )
by Pauli
01:55
created

BaseMapper::questionMarks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
ccs 0
cts 5
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
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 - 2020
11
 */
12
13
namespace OCA\Music\Db;
14
15
use OCP\AppFramework\Db\Mapper;
0 ignored issues
show
Bug introduced by
The type OCP\AppFramework\Db\Mapper 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...
16
use OCP\IDBConnection;
0 ignored issues
show
Bug introduced by
The type OCP\IDBConnection 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...
17
18
use \Doctrine\DBAL\Exception\UniqueConstraintViolationException;
0 ignored issues
show
Bug introduced by
The type Doctrine\DBAL\Exception\...raintViolationException 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...
19
20
/**
21
 * Common base class for data access classes of the Music app
22
 */
23
abstract class BaseMapper extends Mapper {
24
25
	const SQL_DATE_FORMAT = 'Y-m-d H:i:s.v';
26
27
	protected $nameColumn;
28
29
	/**
30
	 * @param IDBConnection $db
31
	 * @param string $tableName
32
	 * @param string $entityClass
33
	 */
34
	public function __construct(IDBConnection $db, $tableName, $entityClass, $nameColumn) {
35
		parent::__construct($db, $tableName, $entityClass);
36
		$this->nameColumn = $nameColumn;
37
	}
38
39
	/**
40
	 * Create an empty object of the entity class bound to this mapper
41
	 * @return Entity
0 ignored issues
show
Bug introduced by
The type OCA\Music\Db\Entity 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...
42
	 */
43
	public function createEntity() {
44
		return new $this->entityClass();
45
	}
46
47
	/**
48
	 * Find a single entity by id and user_id
49
	 * @param integer $id
50
	 * @param string $userId
51
	 * @throws DoesNotExistException if the entity does not exist
52
	 * @throws MultipleObjectsReturnedException if more than one entity exists
53
	 * @return Entity
54
	 */
55
	public function find($id, $userId) {
56
		$sql = $this->selectUserEntities("`{$this->getTableName()}`.`id` = ?");
57
		return $this->findEntity($sql, [$userId, $id]);
58
	}
59
60
	/**
61
	 * Find all entities matching the given IDs. Specifying the owning user is optional.
62
	 * @param integer[] $ids  IDs of the entities to be found
63
	 * @param string|null $userId
64
	 * @return Entity[]
65
	 */
66
	public function findById($ids, $userId=null) {
67
		$count = \count($ids);
68
		$condition = "`{$this->getTableName()}`.`id` IN ". $this->questionMarks($count);
69
70
		if (empty($userId)) {
71
			$sql = $this->selectEntities($condition);
72
		} else {
73
			$sql = $this->selectUserEntities($condition);
74
			$ids = \array_merge([$userId], $ids);
75
		}
76
77
		return $this->findEntities($sql, $ids);
78
	}
79
80
	/**
81
	 * Find all user's entities
82
	 * 
83
	 * @param string $userId
84
	 * @param integer $sortBy sort order of the result set
85
	 * @param integer|null $limit
86
	 * @param integer|null $offset
87
	 * @return Entity[]
88
	 */
89
	public function findAll($userId, $sortBy=SortBy::None, $limit=null, $offset=null) {
90
		if ($sortBy == SortBy::Name) {
91
			$sorting = "ORDER BY LOWER(`{$this->getTableName()}`.`{$this->nameColumn}`)";
92
		} elseif ($sortBy == SortBy::Newest) {
93
			$sorting = "ORDER BY `{$this->getTableName()}`.`id` DESC"; // abuse the fact that IDs are ever-incrementing values
94
		} else {
95
			$sorting = null;
96
		}
97
		$sql = $this->selectUserEntities('', $sorting);
98
		$params = [$userId];
99
		return $this->findEntities($sql, $params, $limit, $offset);
100
	}
101
102
	/**
103
	 * Find all user's entities matching the given name
104
	 * 
105
	 * @param string|null $name
106
	 * @param string $userId
107
	 * @param bool $fuzzy
108
	 * @param integer|null $limit
109
	 * @param integer|null $offset
110
	 * @return Artist[]
111
	 */
112
	public function findAllByName($name, $userId, $fuzzy = false, $limit=null, $offset=null) {
113
		$nameCol = "`{$this->getTableName()}`.`{$this->nameColumn}`";
114
		if ($name === null) {
115
			$condition = "$nameCol IS NULL";
116
			$params = [$userId];
117
		} elseif ($fuzzy) {
118
			$condition = "LOWER($nameCol) LIKE LOWER(?)";
119
			$params = [$userId, "%$name%"];
120
		} else {
121
			$condition = "$nameCol = ?";
122
			$params = [$userId, $name];
123
		}
124
		$sql = $this->selectUserEntities($condition, "ORDER BY LOWER($nameCol)");
125
126
		return $this->findEntities($sql, $params, $limit, $offset);
127
	}
128
129
	/**
130
	 * Find all user's starred entities
131
	 * 
132
	 * @param string $userId
133
	 * @param integer|null $limit
134
	 * @param integer|null $offset
135
	 * @return Entity[]
136
	 */
137
	public function findAllStarred($userId, $limit=null, $offset=null) {
138
		$sql = $this->selectUserEntities(
139
				"`{$this->getTableName()}`.`starred` IS NOT NULL",
140
				"ORDER BY LOWER(`{$this->getTableName()}`.`{$this->nameColumn}`)");
141
		return $this->findEntities($sql, [$userId], $limit, $offset);
142
	}
143
144
	/**
145
	 * Delete all entities with given IDs without specifying the user
146
	 * @param integer[] $ids  IDs of the entities to be deleted
147
	 */
148
	public function deleteById($ids) {
149
		$count = \count($ids);
150
		if ($count === 0) {
151
			return;
152
		}
153
		$sql = "DELETE FROM `{$this->getTableName()}` WHERE `id` IN ". $this->questionMarks($count);
154
		$this->execute($sql, $ids);
155
	}
156
157
	/**
158
	 * Tests if entity with given ID and user ID exists in the database
159
	 * @param int $id
160
	 * @param string $userId
161
	 * @return bool
162
	 */
163
	public function exists($id, $userId) {
164
		$sql = "SELECT 1 FROM `{$this->getTableName()}` WHERE `id` = ? AND `user_id` = ?";
165
		$result = $this->execute($sql, [$id, $userId]);
166
		return $result->rowCount() > 0;
167
	}
168
169
	/**
170
	 * Count all entities of a user
171
	 * @param string $userId
172
	 */
173
	public function count($userId) {
174
		$sql = "SELECT COUNT(*) AS count FROM `{$this->getTableName()}` WHERE `user_id` = ?";
175
		$result = $this->execute($sql, [$userId]);
176
		$row = $result->fetch();
177
		return \intval($row['count']);
178
	}
179
180
	/**
181
	 * Insert an entity, or if an entity with the same identity already exists,
182
	 * update the existing entity.
183
	 * @param Entity $entity
184
	 * @return Entity The inserted or updated entity, containing also the id field
185
	 */
186
	public function insertOrUpdate($entity) {
187
		try {
188
			return $this->insert($entity);
189
		} catch (UniqueConstraintViolationException $ex) {
190
			$existingEntity = $this->findUniqueEntity($entity);
191
			$entity->setId($existingEntity->getId());
192
			return $this->update($entity);
193
		}
194
	}
195
196
	/**
197
	 * Set the "starred" column of the given entities
198
	 * @param DateTime|null $date
0 ignored issues
show
Bug introduced by
The type OCA\Music\Db\DateTime was not found. Did you mean DateTime? If so, make sure to prefix the type with \.
Loading history...
199
	 * @param integer[] $ids
200
	 * @param string $userId
201
	 * @return int number of modified entities
202
	 */
203
	public function setStarredDate($date, $ids, $userId) {
204
		$count = \count($ids);
205
		if (!empty($date)) {
206
			$date = $date->format(self::SQL_DATE_FORMAT);
207
		}
208
209
		$sql = "UPDATE `{$this->getTableName()}` SET `starred` = ?
210
				WHERE `id` IN {$this->questionMarks($count)} AND `user_id` = ?";
211
		$params = \array_merge([$date], $ids, [$userId]);
212
		return $this->execute($sql, $params)->rowCount();
213
	}
214
215
	/**
216
	 * helper creating a string like '(?,?,?)' with the specified number of elements
217
	 * @param int $count
218
	 */
219
	protected function questionMarks($count) {
220
		$questionMarks = [];
221
		for ($i = 0; $i < $count; $i++) {
222
			$questionMarks[] = '?';
223
		}
224
		return '(' . \implode(',', $questionMarks) . ')';
225
	}
226
227
	/**
228
	 * Build a SQL SELECT statement which selects all entities of the given user,
229
	 * and optionally applies other conditions, too.
230
	 * This is built upon `selectEntities` which may be overridden by the derived class.
231
	 * @param string|null $condition Optional extra condition. This will get automatically
232
	 *                               prefixed with ' AND ', so don't include that.
233
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
234
	 *                               the conditions in the SQL statement
235
	 */
236
	protected function selectUserEntities($condition=null, $extension=null) {
237
		$allConditions = "`{$this->getTableName()}`.`user_id` = ?";
238
239
		if (!empty($condition)) {
240
			$allConditions .= " AND $condition";
241
		}
242
243
		return $this->selectEntities($allConditions, $extension);
244
	}
245
246
	/**
247
	 * Build a SQL SELECT statement which selects all entities matching the given condition.
248
	 * The derived class may override this if necessary.
249
	 * @param string $condition This will get automatically prefixed with ' WHERE '
250
	 * @param string|null $extension Any extension (e.g. ORDER BY, LIMIT) to be added after
251
	 *                               the conditions in the SQL statement
252
	 */
253
	protected function selectEntities($condition, $extension=null) {
254
		return "SELECT * FROM `{$this->getTableName()}` WHERE $condition $extension ";
255
	}
256
257
	/**
258
	 * Find an entity which has the same identity as the supplied entity.
259
	 * How the identity of the entity is defined, depends on the derived concrete class.
260
	 * @param Entity $entity
261
	 * @return Entity
262
	 */
263
	abstract protected function findUniqueEntity($entity);
264
}
265