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

ArtistMapper::findAllWithCondition()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 1
b 0
f 0
nc 4
nop 11
dl 0
loc 21
rs 9.9

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 Morris Jobke <[email protected]>
10
 * @author Pauli Järvinen <[email protected]>
11
 * @copyright Morris Jobke 2013, 2014
12
 * @copyright Pauli Järvinen 2016 - 2024
13
 */
14
15
namespace OCA\Music\Db;
16
17
use OCA\Music\Utility\Util;
18
19
use OCP\IConfig;
20
use OCP\IDBConnection;
21
22
/**
23
 * @phpstan-extends BaseMapper<Artist>
24
 */
25
class ArtistMapper extends BaseMapper {
26
	public function __construct(IDBConnection $db, IConfig $config) {
27
		parent::__construct($db, $config, 'music_artists', Artist::class, 'name');
28
	}
29
30
	/**
31
	 * @param string $userId
32
	 * @param integer $sortBy sort order of the result set
33
	 * @return Artist[]
34
	 */
35
	public function findAllHavingAlbums(string $userId, int $sortBy=SortBy::Name,
36
			?int $limit=null, ?int $offset=null, ?string $name=null, int $matchMode=MatchMode::Exact,
37
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
38
		
39
		$condition = $this->formatExcludeChildlessCondition();
40
		return $this->findAllWithCondition(
41
			$condition, $userId, $sortBy, $limit, $offset, $name, $matchMode, $createdMin, $createdMax, $updatedMin, $updatedMax);
42
	}
43
44
	/**
45
	 * @param string $userId
46
	 * @param integer $sortBy sort order of the result set
47
	 * @return Artist[]
48
	 */
49
	public function findAllHavingTracks(string $userId, int $sortBy=SortBy::Name,
50
			?int $limit=null, ?int $offset=null, ?string $name=null, int $matchMode=MatchMode::Exact,
51
			?string $createdMin=null, ?string $createdMax=null, ?string $updatedMin=null, ?string $updatedMax=null) : array {
52
		
53
		$condition = 'EXISTS (SELECT 1 FROM `*PREFIX*music_tracks` `track` WHERE `*PREFIX*music_artists`.`id` = `track`.`artist_id`)';
54
		return $this->findAllWithCondition(
55
			$condition, $userId, $sortBy, $limit, $offset, $name, $matchMode, $createdMin, $createdMax, $updatedMin, $updatedMax);
56
	}
57
58
	private function findAllWithCondition(
59
			string $condition, string $userId, int $sortBy, ?int $limit, ?int $offset,
60
			?string $name, int $matchMode, ?string $createdMin, ?string $createdMax,
61
			?string $updatedMin, ?string $updatedMax) : array {
62
		$params = [$userId];
63
64
		if ($name !== null) {
65
			[$nameCond, $nameParams] = $this->formatNameConditions($name, $matchMode);
66
			$condition .= " AND $nameCond";
67
			$params = \array_merge($params, $nameParams);
68
		}
69
70
		[$timestampConds, $timestampParams] = $this->formatTimestampConditions($createdMin, $createdMax, $updatedMin, $updatedMax);
71
		if (!empty($timestampConds)) {
72
			$condition .= " AND $timestampConds";
73
			$params = \array_merge($params, $timestampParams);
74
		}
75
76
		$sql = $this->selectUserEntities($condition, $this->formatSortingClause($sortBy));
77
78
		return $this->findEntities($sql, $params, $limit, $offset);
79
	}
80
81
	/**
82
	 * @param int $genreId
83
	 * @param string $userId
84
	 * @param int|null $limit
85
	 * @param int|null $offset
86
	 * @return Artist[]
87
	 */
88
	public function findAllByGenre(int $genreId, string $userId, ?int $limit=null, ?int $offset=null) : array {
89
		$sql = $this->selectUserEntities('EXISTS '.
90
				'(SELECT 1 FROM `*PREFIX*music_tracks` `track`
91
				  WHERE `*PREFIX*music_artists`.`id` = `track`.`artist_id`
92
				  AND `track`.`genre_id` = ?)');
93
94
		$params = [$userId, $genreId];
95
		return $this->findEntities($sql, $params, $limit, $offset);
96
	}
97
98
	/**
99
	 * returns summed track play counts of each artist of the user, omitting artists which have never been played
100
	 *
101
	 * @return array [int => int], keys are artist IDs and values are play count sums; ordered largest counts first
102
	 */
103
	public function getArtistTracksPlayCount(string $userId, ?int $limit=null, ?int $offset=null) : array {
104
		$sql = 'SELECT `artist_id`, SUM(`play_count`) AS `sum_count`
105
				FROM `*PREFIX*music_tracks`
106
				WHERE `user_id` = ? AND `play_count` > 0
107
				GROUP BY `artist_id`
108
				ORDER BY `sum_count` DESC, `artist_id`'; // the second criterion is just to make the order predictable on even counts
109
110
		$result = $this->execute($sql, [$userId], $limit, $offset);
111
		$playCountByArtist = [];
112
		while ($row = $result->fetch()) {
113
			$playCountByArtist[$row['artist_id']] = (int)$row['sum_count'];
114
		}
115
		return $playCountByArtist;
116
	}
117
118
	/**
119
	 * returns the latest play time of each artist of the user, omitting artists which have never been played
120
	 *
121
	 * @return array [int => string], keys are artist IDs and values are date-times; ordered latest times first
122
	 */
123
	public function getLatestArtistPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
124
		$sql = 'SELECT `artist_id`, MAX(`last_played`) AS `latest_time`
125
				FROM `*PREFIX*music_tracks`
126
				WHERE `user_id` = ? AND `last_played` IS NOT NULL
127
				GROUP BY `artist_id`
128
				ORDER BY `latest_time` DESC';
129
130
		$result = $this->execute($sql, [$userId], $limit, $offset);
131
		$latestTimeByArtist = [];
132
		while ($row = $result->fetch()) {
133
			$latestTimeByArtist[$row['artist_id']] = $row['latest_time'];
134
		}
135
		return $latestTimeByArtist;
136
	}
137
138
	/**
139
	 * returns the latest play time of each artist of the user, including artists which have never been played
140
	 *
141
	 * @return array [int => ?string], keys are artist IDs and values are date-times (or null for never played);
142
	 *									ordered furthest times first
143
	 */
144
	public function getFurthestArtistPlayTimes(string $userId, ?int $limit=null, ?int $offset=null) : array {
145
		$sql = 'SELECT `artist_id`, MAX(`last_played`) AS `latest_time`
146
				FROM `*PREFIX*music_tracks`
147
				WHERE `user_id` = ?
148
				GROUP BY `artist_id`
149
				ORDER BY `latest_time` ASC';
150
151
		$result = $this->execute($sql, [$userId], $limit, $offset);
152
		$latestTimeByArtist = [];
153
		while ($row = $result->fetch()) {
154
			$latestTimeByArtist[$row['artist_id']] = $row['latest_time'];
155
		}
156
		return $latestTimeByArtist;
157
	}
158
159
	/**
160
	 * @param integer[] $coverFileIds
161
	 * @param string[]|null $userIds the users whose music library is targeted; all users are targeted if omitted
162
	 * @return Artist[] artists which got modified (with incomplete data, only id and user are valid),
163
	 *         empty array if none
164
	 */
165
	public function removeCovers(array $coverFileIds, ?array $userIds=null) : array {
166
		// find albums using the given file as cover
167
		$sql = 'SELECT `id`, `user_id` FROM `*PREFIX*music_artists` WHERE `cover_file_id` IN ' .
168
		$this->questionMarks(\count($coverFileIds));
169
		$params = $coverFileIds;
170
		if ($userIds !== null) {
171
			$sql .= ' AND `user_id` IN ' . $this->questionMarks(\count($userIds));
172
			$params = \array_merge($params, $userIds);
173
		}
174
		$artists = $this->findEntities($sql, $params);
175
176
		// if any artists found, remove the cover from those
177
		$count = \count($artists);
178
		if ($count) {
179
			$sql = 'UPDATE `*PREFIX*music_artists`
180
					SET `cover_file_id` = NULL
181
					WHERE `id` IN ' . $this->questionMarks($count);
182
			$params = Util::extractIds($artists);
183
			$this->execute($sql, $params);
184
		}
185
186
		return $artists;
187
	}
188
189
	/**
190
	 * Overridden from the base implementation
191
	 *
192
	 * @see BaseMapper::formatExcludeChildlessCondition()
193
	 */
194
	protected function formatExcludeChildlessCondition() : string {
195
		return 'EXISTS (SELECT 1 FROM `*PREFIX*music_albums` `album` WHERE `*PREFIX*music_artists`.`id` = `album`.`album_artist_id`)';
196
	}
197
198
	/**
199
	 * Overridden from the base implementation to provide support for table-specific rules
200
	 *
201
	 * {@inheritdoc}
202
	 * @see BaseMapper::advFormatSqlCondition()
203
	 */
204
	protected function advFormatSqlCondition(string $rule, string $sqlOp, string $conv) : string {
205
		// The extra subquery "mysqlhack" seen around some nested queries is needed in order for these to not be insanely slow on MySQL.
206
		// In case of 'recent_played', the MySQL 5.5.62 errored with "1235 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" without the extra subquery.
207
		switch ($rule) {
208
			case 'album':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_albums` `a` ON `t`.`album_id` = `a`.`id` WHERE $conv(`a`.`name`) $sqlOp $conv(?))";
209
			case 'artist':			return parent::advFormatSqlCondition('name', $sqlOp, $conv); // alias
210
			case 'song':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE $conv(`t`.`title`) $sqlOp $conv(?))";
211
			case 'songrating':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`rating` $sqlOp ?)";
212
			case 'albumrating':		return "`*PREFIX*music_artists`.`id` IN (SELECT `album_artist_id` from `*PREFIX*music_albums` `al` WHERE `al`.`rating` $sqlOp ?)";
213
			case 'played_times':	return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING SUM(`play_count`) $sqlOp ?) mysqlhack)";
214
			case 'last_play':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING MAX(`last_played`) $sqlOp ?) mysqlhack)";
215
			case 'played':			// fall through, we give no access to other people's data; not part of the API spec but Ample uses this
216
			case 'myplayed':		return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` from `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING MAX(`last_played`) $sqlOp) mysqlhack)"; // operator "IS NULL" or "IS NOT NULL"
217
			case 'album_count':		return "`*PREFIX*music_artists`.`id` IN (SELECT `id` FROM `*PREFIX*music_artists` JOIN (SELECT `*PREFIX*music_artists`.`id` AS `id2`, " . $this->sqlCoalesce('`count1`', '0') . " `count2` FROM `*PREFIX*music_artists` LEFT JOIN (SELECT `*PREFIX*music_albums`.`album_artist_id` AS `id1`, COUNT(`*PREFIX*music_albums`.`id`) AS `count1` FROM `*PREFIX*music_albums` GROUP BY `*PREFIX*music_albums`.`album_artist_id`) `sub1` ON `*PREFIX*music_artists`.`id` = `id1`) `sub2` ON `*PREFIX*music_artists`.`id` = `id2` WHERE `count2` $sqlOp ?)";
218
			case 'song_count':		return "`*PREFIX*music_artists`.`id` IN (SELECT `id` FROM `*PREFIX*music_artists` JOIN (SELECT `*PREFIX*music_artists`.`id` AS `id2`, " . $this->sqlCoalesce('`count1`', '0') . " `count2` FROM `*PREFIX*music_artists` LEFT JOIN (SELECT `*PREFIX*music_tracks`.`artist_id` AS `id1`, COUNT(`*PREFIX*music_tracks`.`id`) AS `count1` FROM `*PREFIX*music_tracks` GROUP BY `*PREFIX*music_tracks`.`artist_id`) `sub1` ON `*PREFIX*music_artists`.`id` = `id1`) `sub2` ON `*PREFIX*music_artists`.`id` = `id2` WHERE `count2` $sqlOp ?)";
219
			case 'time':			return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` GROUP BY `artist_id` HAVING SUM(`length`) $sqlOp ?) mysqlhack)";
220
			case 'genre':			return "`*PREFIX*music_artists`.`id` IN (SELECT * FROM (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` GROUP BY `artist_id` HAVING $conv(" . $this->sqlGroupConcat('`g`.`name`') . ") $sqlOp $conv(?)) mysqlhack)";
221
			case 'song_genre':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` WHERE $conv(`g`.`name`) $sqlOp $conv(?))";
222
			case 'no_genre':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*music_genres` `g` ON `t`.`genre_id` = `g`.`id` WHERE `g`.`name` " . (($sqlOp == 'IS NOT NULL') ? '=' : '!=') . ' "")';
223
			case 'playlist':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE $sqlOp EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE `p`.`id` = ? AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`t`.`id`", "'|%'") . '))';
224
			case 'playlist_name':	return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE EXISTS (SELECT 1 from `*PREFIX*music_playlists` `p` WHERE $conv(`p`.`name`) $sqlOp $conv(?) AND `p`.`track_ids` LIKE " . $this->sqlConcat("'%|'", "`t`.`id`", "'|%'") . '))';
225
			case 'file':			return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` JOIN `*PREFIX*filecache` `f` ON `t`.`file_id` = `f`.`fileid` WHERE $conv(`f`.`name`) $sqlOp $conv(?))";
226
			case 'recent_played':	return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM (SELECT `artist_id`, MAX(`last_played`) FROM `*PREFIX*music_tracks` WHERE `user_id` = ? GROUP BY `artist_id` ORDER BY MAX(`last_played`) DESC LIMIT $sqlOp) mysqlhack)";
227
			case 'mbid_artist':		return parent::advFormatSqlCondition('mbid', $sqlOp, $conv); // alias
228
			case 'mbid_song':		return "`*PREFIX*music_artists`.`id` IN (SELECT `artist_id` FROM `*PREFIX*music_tracks` `t` WHERE `t`.`mbid` $sqlOp ?)";
229
			case 'mbid_album':		return "`*PREFIX*music_artists`.`id` IN (SELECT `album_artist_id` from `*PREFIX*music_albums` `al` WHERE `al`.`mbid` $sqlOp ?)";
230
			case 'has_image':		return "`cover_file_id` $sqlOp"; // operator "IS NULL" or "IS NOT NULL"
231
			default:				return parent::advFormatSqlCondition($rule, $sqlOp, $conv);
232
		}
233
	}
234
235
	/**
236
	 * {@inheritdoc}
237
	 * @see \OCA\Music\Db\BaseMapper::findUniqueEntity()
238
	 * @param Artist $artist
239
	 * @return Artist
240
	 */
241
	protected function findUniqueEntity(Entity $artist) : Entity {
242
		$sql = $this->selectUserEntities('`hash` = ?');
243
		return $this->findEntity($sql, [$artist->getUserId(), $artist->getHash()]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity...), $artist->getHash())) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\Music\Db\Entity.
Loading history...
Bug introduced by
The method getHash() does not exist on OCA\Music\Db\Entity. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

243
		return $this->findEntity($sql, [$artist->getUserId(), $artist->/** @scrutinizer ignore-call */ getHash()]);
Loading history...
244
	}
245
}
246