Passed
Push — master ( eba474...e75eb9 )
by Kylian
01:49 queued 12s
created

BeatSaverAPI::searchMap()   F

Complexity

Conditions 18
Paths > 20000

Size

Total Lines 29
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 23
c 1
b 0
f 0
nc 131072
nop 19
dl 0
loc 29
rs 0.7

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
2
3
namespace KriKrixs;
4
5
class BeatSaverAPI
6
{
7
    const BEATSAVER_URL = "https://api.beatmaps.io/"; // Might Change when BeatSaver is up
8
    const MAPS_NUMBERS_PER_PAGE = 20;
9
10
    private string $userAgent;
11
12
    /**
13
     * BeatSaverAPI constructor
14
     * @param string $userAgent User Agent to provide to Beat Saver API
15
     */
16
    public function __construct(string $userAgent)
17
    {
18
        $this->userAgent = $userAgent;
19
    }
20
21
    /**
22
     * Private calling API function
23
     * @param string $endpoint
24
     * @return string|null
25
     */
26
    private function callAPI(string $endpoint): ?string
27
    {
28
        $curl = curl_init();
29
30
        curl_setopt_array($curl, [
31
            CURLOPT_URL => self::BEATSAVER_URL . $endpoint,
32
            CURLOPT_USERAGENT => $this->userAgent,
33
            CURLOPT_RETURNTRANSFER => true
34
        ]);
35
36
        $result = curl_exec($curl);
37
38
        curl_close($curl);
39
40
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type true which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
41
    }
42
43
    ////////////
44
    /// CALL ///
45
    ///////////
46
47
    /**
48
     * Private building response functions
49
     * @param string $endpoint
50
     * @return array
51
     */
52
    private function get(string $endpoint): array
53
    {
54
        $apiResult = $this->callAPI($endpoint);
55
56
        return [
57
            "error" => $apiResult === false,
58
            "map" => json_decode($apiResult, true)
59
        ];
60
    }
61
62
    ///////////////
63
    /// Get Map ///
64
    ///////////////
65
66
    /**
67
     * Get map by ID
68
     * @param int $id
69
     * @return array
70
     */
71
    public function getMapByID(int $id): array
72
    {
73
        return $this->get("/maps/id/" . $id);
74
    }
75
76
    /**
77
     * Get map by BSR Key
78
     * @param string $bsrKey BSR Key of the map
79
     * @return array
80
     */
81
    public function getMapByKey(string $bsrKey): array
82
    {
83
        return $this->get("/maps/beatsaver/" . $bsrKey);
84
    }
85
86
    /**
87
     * Get map by Hash
88
     * @param string $hash Hash of the map
89
     * @return array
90
     */
91
    public function getMapByHash(string $hash): array
92
    {
93
        return $this->get("/maps/hash/" . $hash);
94
    }
95
96
    ////////////////
97
    /// Get Maps ///
98
    ////////////////
99
100
    /**
101
     * Private building response functions
102
     * @param string $endpoint
103
     * @param int $limit
104
     * @return array
105
     */
106
    private function getMaps(string $endpoint, int $limit): array
107
    {
108
        $response = [
109
            "error" => false,
110
            "maps" => []
111
        ];
112
113
        for($page = 0; $page <= floor(($limit - 1) / self::MAPS_NUMBERS_PER_PAGE); $page++) {
114
            $apiResult = $this->callAPI(str_ireplace("page", $page, $endpoint));
0 ignored issues
show
Bug introduced by
It seems like str_ireplace('page', $page, $endpoint) can also be of type array; however, parameter $endpoint of KriKrixs\BeatSaverAPI::callAPI() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

114
            $apiResult = $this->callAPI(/** @scrutinizer ignore-type */ str_ireplace("page", $page, $endpoint));
Loading history...
115
116
            if($apiResult === false || $apiResult == "Not Found") {
117
                $response["error"] = true;
118
119
                if($apiResult == "Not Found")
120
                    break;
121
            } else {
122
                $apiResult = json_decode($apiResult, true);
123
124
                if(count($apiResult["docs"]) === 0)
125
                    break;
126
127
                if(($page + 1) * self::MAPS_NUMBERS_PER_PAGE <= $limit) {
128
                    $response["maps"] = array_merge($response["maps"], $apiResult["docs"]);
129
                } else {
130
                    $max = $limit <= self::MAPS_NUMBERS_PER_PAGE ? $limit : $limit - ($page * self::MAPS_NUMBERS_PER_PAGE);
131
132
                    for($i = 0; $i < $max; $i++) {
133
                        array_push($response["maps"], $apiResult["docs"][$i]);
134
                    }
135
                }
136
            }
137
138
            if(count($apiResult["docs"]) < ($page + 1) * self::MAPS_NUMBERS_PER_PAGE)
139
                break;
140
        }
141
142
        return $response;
143
    }
144
145
    /**
146
     * Get maps by Uploader ID! Not the uploader name!
147
     * @param int $uploaderID Uploader ID on BeatSaver
148
     * @param int $limit How many maps do you want to be returned
149
     * @return string|bool
150
     */
151
    public function getMapsByUploaderID(int $uploaderID, int $limit): array
152
    {
153
        return $this->getMaps("/maps/uploader/" . $uploaderID . "/page", $limit);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMaps('/...erID . '/page', $limit) returns the type array<string,array|false> which is incompatible with the documented return type boolean|string.
Loading history...
154
    }
155
156
    /**
157
     * Get 20 latest maps
158
     * @param bool $autoMapper Do you want automapper or not ?
159
     * @return array
160
     */
161
    public function getMapsSortedByLatest(bool $autoMapper): array
0 ignored issues
show
Unused Code introduced by
The parameter $autoMapper is not used and could be removed. ( Ignorable by Annotation )

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

161
    public function getMapsSortedByLatest(/** @scrutinizer ignore-unused */ bool $autoMapper): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
    {
163
        return $this->getMaps("/maps/latest?automapper=false", self::MAPS_NUMBERS_PER_PAGE);
164
    }
165
166
    /**
167
     * Get maps sorted by plays numbers
168
     * @param int $limit How many maps do you want to be returned
169
     * @return array
170
     */
171
    public function getMapsSortedByPlays(int $limit): array
172
    {
173
        return $this->getMaps("/maps/plays/page", $limit);
174
    }
175
176
    /**
177
     * Search a map (Set null to a parameter to not use it)
178
     * @param int $limit Limit of map you want
179
     * @param int $sortOrder (Default 1) 1 = Latest | 2 = Relevance | 3 = Rating
180
     * @param string|null $mapName (Optional) Map name
181
     * @param \DateTime|null $startDate (Optional) Map made from this date
182
     * @param \DateTime|null $endDate (Optional) Map made to this date
183
     * @param bool $ranked (Optional) Want ranked or not ?
184
     * @param bool $automapper (Optional) Want automapper or not ?
185
     * @param bool $chroma (Optional) Want chroma or not ?
186
     * @param bool $noodle (Optional) Want noodle or not ?
187
     * @param bool $cinema (Optional) Want cinema or not ?
188
     * @param bool $fullSpread (Optional) Want fullSpread or not ?
189
     * @param float|null $minBpm (Optional) Minimum BPM
190
     * @param float|null $maxBpm (Optional) Maximum BPM
191
     * @param float|null $minNps (Optional) Minimum NPS
192
     * @param float|null $maxNps (Optional) Maximum NPS
193
     * @param float|null $minRating (Optional) Minimum Rating
194
     * @param float|null $maxRating (Optional) Maximum Rating
195
     * @param int|null $minDuration (Optional) Minimum Duration
196
     * @param int|null $maxDuration (Optional) Maximum Duration
197
     * @return array
198
     */
199
    public function searchMap(int $limit, int $sortOrder = 1, string $mapName = null, \DateTime $startDate = null, \DateTime $endDate = null, bool $ranked = false, bool $automapper = false, bool $chroma = false, bool $noodle = false, bool $cinema = false, bool $fullSpread = false, float $minBpm = null, float $maxBpm = null, float $minNps = null, float $maxNps = null, float $minRating = null, float $maxRating = null, int $minDuration = null, int $maxDuration = null): array
200
    {
201
        $sort = [
202
            1 => "Latest",
203
            2 => "Relevance",
204
            3 => "Rating"
205
        ];
206
207
        $endpoint = "/search/text/page?sortOrder=" . $sort[$sortOrder];
208
209
        if($mapName)        $endpoint .= "&q=" . urlencode($mapName);
210
        if($startDate)      $endpoint .= "&from=" . $startDate->format("Y-m-d");
211
        if($endDate)        $endpoint .= "&to=" . $endDate->format("Y-m-d");
212
        if($ranked)         $endpoint .= "&ranked=" . $ranked;
0 ignored issues
show
Bug introduced by
Are you sure $ranked of type true can be used in concatenation? ( Ignorable by Annotation )

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

212
        if($ranked)         $endpoint .= "&ranked=" . /** @scrutinizer ignore-type */ $ranked;
Loading history...
213
        if($automapper)     $endpoint .= "&automapper=" . $automapper;
0 ignored issues
show
Bug introduced by
Are you sure $automapper of type true can be used in concatenation? ( Ignorable by Annotation )

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

213
        if($automapper)     $endpoint .= "&automapper=" . /** @scrutinizer ignore-type */ $automapper;
Loading history...
214
        if($chroma)         $endpoint .= "&chroma=" . $chroma;
0 ignored issues
show
Bug introduced by
Are you sure $chroma of type true can be used in concatenation? ( Ignorable by Annotation )

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

214
        if($chroma)         $endpoint .= "&chroma=" . /** @scrutinizer ignore-type */ $chroma;
Loading history...
215
        if($noodle)         $endpoint .= "&noodle=" . $noodle;
0 ignored issues
show
Bug introduced by
Are you sure $noodle of type true can be used in concatenation? ( Ignorable by Annotation )

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

215
        if($noodle)         $endpoint .= "&noodle=" . /** @scrutinizer ignore-type */ $noodle;
Loading history...
216
        if($cinema)         $endpoint .= "&cinema=" . $cinema;
0 ignored issues
show
Bug introduced by
Are you sure $cinema of type true can be used in concatenation? ( Ignorable by Annotation )

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

216
        if($cinema)         $endpoint .= "&cinema=" . /** @scrutinizer ignore-type */ $cinema;
Loading history...
217
        if($fullSpread)     $endpoint .= "&fullSpread=" . $fullSpread;
0 ignored issues
show
Bug introduced by
Are you sure $fullSpread of type true can be used in concatenation? ( Ignorable by Annotation )

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

217
        if($fullSpread)     $endpoint .= "&fullSpread=" . /** @scrutinizer ignore-type */ $fullSpread;
Loading history...
218
        if($minBpm)         $endpoint .= "&minBpm=" . $minBpm;
219
        if($maxBpm)         $endpoint .= "&maxBpm=" . $maxBpm;
220
        if($minNps)         $endpoint .= "&minNps=" . $minNps;
221
        if($maxNps)         $endpoint .= "&maxNps=" . $maxNps;
222
        if($minRating)      $endpoint .= "&minRating=" . $minRating;
223
        if($maxRating)      $endpoint .= "&maxRating=" . $maxRating;
224
        if($minDuration)    $endpoint .= "&minDuration=" . $minDuration;
0 ignored issues
show
Bug Best Practice introduced by
The expression $minDuration of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
225
        if($maxDuration)    $endpoint .= "&maxDuration=" . $maxDuration;
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxDuration of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
226
227
        return $this->getMaps($endpoint, $limit);
228
    }
229
230
    ////////////////
231
    /// Get User ///
232
    ////////////////
233
234
    /**
235
     * Get user's infos by UserID
236
     * @param int $id User ID
237
     * @return array
238
     */
239
    public function getUserByID(int $id): array
240
    {
241
        return $this->get("/users/id/" . $id);
242
    }
243
}