Passed
Push — master ( 60b6ca...163cac )
by Kylian
06:19
created

BeatSaverAPI   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Importance

Changes 14
Bugs 0 Features 0
Metric Value
eloc 89
c 14
b 0
f 0
dl 0
loc 283
rs 8.48
wmc 49

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getUserByID() 0 3 1
A getMapByID() 0 3 1
A getMapByHash() 0 3 1
A getMapsByUploaderID() 0 3 1
B getMaps() 0 37 11
A getMapsSortedByPlays() 0 3 1
A autoload() 0 10 5
A getMapByKey() 0 3 1
F searchMap() 0 29 18
A getUser() 0 14 3
A callAPI() 0 15 1
A getMapsSortedByLatest() 0 3 1
A getMap() 0 14 3
A __construct() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like BeatSaverAPI often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BeatSaverAPI, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace KriKrixs;
4
5
use KriKrixs\functions\MultiQuery;
6
use KriKrixs\object\beatmap\BeatMap;
7
use KriKrixs\object\response\ResponseDownload;
8
use KriKrixs\object\response\ResponseMap;
9
use KriKrixs\object\response\ResponseMaps;
10
use KriKrixs\object\response\ResponseUser;
11
use KriKrixs\object\user\User;
12
13
class BeatSaverAPI
14
{
15
    const BEATSAVER_URL = "https://api.beatsaver.com/";
16
17
    private string $userAgent;
18
    private MultiQuery $multiQuery;
19
20
    /**
21
     * BeatSaverAPI constructor
22
     * @param string $userAgent User Agent to provide to Beat Saver API
23
     */
24
    public function __construct(string $userAgent)
25
    {
26
        $this->autoload("./");
27
28
        $this->userAgent = $userAgent;
29
        $this->multiQuery = new MultiQuery(self::BEATSAVER_URL, $userAgent);
30
    }
31
32
    private function autoload($directory) {
33
        if(is_dir($directory)) {
34
            $scan = scandir($directory);
35
            unset($scan[0], $scan[1]); //unset . and ..
36
            foreach($scan as $file) {
37
                if(is_dir($directory."/".$file)) {
38
                    $this->autoload($directory."/".$file);
39
                } else {
40
                    if(strpos($file, '.php') !== false) {
41
                        include_once($directory."/".$file);
42
                    }
43
                }
44
            }
45
        }
46
    }
47
48
    /**
49
     * Private calling API function
50
     * @param string $endpoint
51
     * @return string|null
52
     */
53
    private function callAPI(string $endpoint): ?string
54
    {
55
        $curl = curl_init();
56
57
        curl_setopt_array($curl, [
58
            CURLOPT_URL => self::BEATSAVER_URL . $endpoint,
59
            CURLOPT_USERAGENT => $this->userAgent,
60
            CURLOPT_RETURNTRANSFER => true
61
        ]);
62
63
        $result = curl_exec($curl);
64
65
        curl_close($curl);
66
67
        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...
68
    }
69
70
    ////////////
71
    /// CALL ///
72
    ///////////
73
74
    /**
75
     * Private building response functions
76
     * @param string $endpoint
77
     * @return ResponseMap
78
     */
79
    private function getMap(string $endpoint): ResponseMap
80
    {
81
        $response = new ResponseMap();
82
83
        $apiResult = $this->callAPI($endpoint);
84
85
        if($apiResult === false || $apiResult == "Not Found") {
86
            $response->setErrorStatus(true)->setErrorStatus("[getMap] Something went wrong with the API call.");
0 ignored issues
show
Bug introduced by
'[getMap] Something went...ong with the API call.' of type string is incompatible with the type boolean expected by parameter $errorStatus of KriKrixs\object\response...ponse::setErrorStatus(). ( Ignorable by Annotation )

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

86
            $response->setErrorStatus(true)->setErrorStatus(/** @scrutinizer ignore-type */ "[getMap] Something went wrong with the API call.");
Loading history...
87
            return $response;
88
        }
89
90
        $response->setBeatMap(new BeatMap(json_decode($apiResult)));
91
92
        return $response;
93
    }
94
95
    ///////////////
96
    /// Get Map ///
97
    ///////////////
98
99
    /**
100
     * Get map by ID (Same as BSR Key)
101
     * @param string $id Map ID
102
     * @return ResponseMap
103
     */
104
    public function getMapByID(string $id): ResponseMap
105
    {
106
        return $this->getMap("/maps/id/" . $id);
107
    }
108
109
    /**
110
     * Get map by BSR Key (Same as ID)
111
     * @param string $bsrKey Map BSR key
112
     * @return ResponseMap
113
     */
114
    public function getMapByKey(string $bsrKey): ResponseMap
115
    {
116
        return $this->getMap("/maps/id/" . $bsrKey);
117
    }
118
119
    /**
120
     * Get map by Hash
121
     * @param string $hash Hash of the map
122
     * @return ResponseMap
123
     */
124
    public function getMapByHash(string $hash): ResponseMap
125
    {
126
        return $this->getMap("/maps/hash/" . $hash);
127
    }
128
129
    ////////////////
130
    /// Get Maps ///
131
    ////////////////
132
133
    /**
134
     * Private building response functions
135
     * @param string $endpoint
136
     * @param int $limit
137
     * @return array
138
     */
139
    private function getMaps(string $endpoint, int $limit): array
0 ignored issues
show
Unused Code introduced by
The parameter $limit 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

139
    private function getMaps(string $endpoint, /** @scrutinizer ignore-unused */ int $limit): 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...
140
    {
141
        $response = new ResponseMaps();
142
        $maps = [];
143
144
        // Latest
145
        if($numberOfPage === 0 && $startPage === 0){
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $startPage seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $numberOfPage seems to be never defined.
Loading history...
146
            $apiResult = json_decode($this->callAPI($endpoint));
147
148
            if($apiResult === false || $apiResult == "Not Found") {
149
                $response->setErrorStatus(true)->setErrorMessage("[getMaps] Something went wrong with the API call while calling the first page.");
150
                return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type KriKrixs\object\response\ResponseMaps which is incompatible with the type-hinted return array.
Loading history...
151
            } else{
152
                foreach ($apiResult->docs as $beatmap) {
153
                    $maps[] = new BeatMap($beatmap);
154
                }
155
            }
156
        } else {
157
            for($i = $startPage; $i < ($i + $numberOfPage); $i++){
158
                $apiResult = json_decode($this->callAPI(str_ireplace("page", $i, $endpoint)));
0 ignored issues
show
Bug introduced by
It seems like str_ireplace('page', $i, $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

158
                $apiResult = json_decode($this->callAPI(/** @scrutinizer ignore-type */ str_ireplace("page", $i, $endpoint)));
Loading history...
159
160
                if($apiResult === false || $apiResult == "Not Found") {
161
                    $response->setErrorStatus(true)->setErrorMessage("[getMaps] Something went wrong with the API call while calling page number " . $i . ".");
162
163
                    if($apiResult == "Not Found")
164
                        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type KriKrixs\object\response\ResponseMaps which is incompatible with the type-hinted return array.
Loading history...
165
                }
166
167
                foreach ($apiResult->docs as $beatmap) {
168
                    $maps[] = new BeatMap($beatmap);
169
                }
170
            }
171
        }
172
173
        $response->setBeatMaps($maps);
174
175
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type KriKrixs\object\response\ResponseMaps which is incompatible with the type-hinted return array.
Loading history...
176
    }
177
178
    /**
179
     * Get maps by Uploader ID! Not the uploader name!
180
     * @param int $uploaderID Uploader ID on BeatSaver
181
     * @param int $limit How many maps do you want to be returned
182
     * @return array
183
     */
184
    public function getMapsByUploaderID(int $uploaderID, int $limit): array
185
    {
186
        return $this->getMaps("/maps/uploader/" . $uploaderID . "/page", $limit);
187
    }
188
189
    /**
190
     * Get 20 latest maps
191
     * @param bool $autoMapper Do you want automapper or not ?
192
     * @return ResponseMaps
193
     */
194
    public function getMapsSortedByLatest(bool $autoMapper): ResponseMaps
195
    {
196
        return $this->getMaps("/maps/latest?automapper=" . $autoMapper, self::MAPS_NUMBERS_PER_PAGE);
0 ignored issues
show
Bug introduced by
The constant KriKrixs\BeatSaverAPI::MAPS_NUMBERS_PER_PAGE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug Best Practice introduced by
The expression return $this->getMaps('/...:MAPS_NUMBERS_PER_PAGE) returns the type array which is incompatible with the type-hinted return KriKrixs\object\response\ResponseMaps.
Loading history...
197
    }
198
199
    /**
200
     * Get maps sorted by plays numbers
201
     * @param int $limit How many maps do you want to be returned
202
     * @return array
203
     */
204
    public function getMapsSortedByPlays(int $limit): array
205
    {
206
        return $this->getMaps("/maps/plays/page", $limit);
207
    }
208
209
    /**
210
     * Search a map (Set null to a parameter to not use it)
211
     * @param int $limit Limit of map you want
212
     * @param int $sortOrder (Default 1) 1 = Latest | 2 = Relevance | 3 = Rating
213
     * @param string|null $mapName (Optional) Map name
214
     * @param \DateTime|null $startDate (Optional) Map made from this date
215
     * @param \DateTime|null $endDate (Optional) Map made to this date
216
     * @param bool $ranked (Optional) Want ranked or not ?
217
     * @param bool $automapper (Optional) Want automapper or not ?
218
     * @param bool $chroma (Optional) Want chroma or not ?
219
     * @param bool $noodle (Optional) Want noodle or not ?
220
     * @param bool $cinema (Optional) Want cinema or not ?
221
     * @param bool $fullSpread (Optional) Want fullSpread or not ?
222
     * @param float|null $minBpm (Optional) Minimum BPM
223
     * @param float|null $maxBpm (Optional) Maximum BPM
224
     * @param float|null $minNps (Optional) Minimum NPS
225
     * @param float|null $maxNps (Optional) Maximum NPS
226
     * @param float|null $minRating (Optional) Minimum Rating
227
     * @param float|null $maxRating (Optional) Maximum Rating
228
     * @param int|null $minDuration (Optional) Minimum Duration
229
     * @param int|null $maxDuration (Optional) Maximum Duration
230
     * @return ResponseMaps
231
     */
232
    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): ResponseMaps
233
    {
234
        $sort = [
235
            1 => "Latest",
236
            2 => "Relevance",
237
            3 => "Rating"
238
        ];
239
240
        $endpoint = "/search/text/page?sortOrder=" . $sort[$sortOrder];
241
242
        if($mapName)                $endpoint .= "&q=" . urlencode($mapName);
243
        if($startDate)              $endpoint .= "&from=" . $startDate->format("Y-m-d");
244
        if($endDate)                $endpoint .= "&to=" . $endDate->format("Y-m-d");
245
        if($ranked)                 $endpoint .= "&ranked=" . /** @scrutinizer ignore-type */ var_export($ranked, true);
246
        if($automapper)             $endpoint .= "&automapper=" . /** @scrutinizer ignore-type */ var_export($automapper, true);
247
        if($chroma)                 $endpoint .= "&chroma=" . /** @scrutinizer ignore-type */ var_export($chroma, true);
248
        if($noodle)                 $endpoint .= "&noodle=" . /** @scrutinizer ignore-type */ var_export($noodle, true);
249
        if($cinema)                 $endpoint .= "&cinema=" . /** @scrutinizer ignore-type */ var_export($cinema, true);
250
        if($fullSpread)             $endpoint .= "&fullSpread=" . /** @scrutinizer ignore-type */ var_export($fullSpread, true);
251
        if($minBpm)                 $endpoint .= "&minBpm=" . /** @scrutinizer ignore-type */ $minBpm;
252
        if($maxBpm)                 $endpoint .= "&maxBpm=" . /** @scrutinizer ignore-type */ $maxBpm;
253
        if($minNps)                 $endpoint .= "&minNps=" . /** @scrutinizer ignore-type */ $minNps;
254
        if($maxNps)                 $endpoint .= "&maxNps=" . /** @scrutinizer ignore-type */ $maxNps;
255
        if($minRating)              $endpoint .= "&minRating=" . /** @scrutinizer ignore-type */ $minRating;
256
        if($maxRating)              $endpoint .= "&maxRating=" . /** @scrutinizer ignore-type */ $maxRating;
257
        if($minDuration !== null)   $endpoint .= "&minDuration=" . /** @scrutinizer ignore-type */ $minDuration;
258
        if($maxDuration !== null)   $endpoint .= "&maxDuration=" . /** @scrutinizer ignore-type */ $maxDuration;
259
260
        return $this->getMaps($endpoint, $limit);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMaps($endpoint, $limit) returns the type array which is incompatible with the type-hinted return KriKrixs\object\response\ResponseMaps.
Loading history...
261
    }
262
263
    ////////////////
264
    /// Get User ///
265
    ////////////////
266
267
    /**
268
     * Private building response functions
269
     * @param string $endpoint
270
     * @return ResponseUser
271
     */
272
    private function getUser(string $endpoint): ResponseUser
273
    {
274
        $response = new ResponseUser();
275
276
        $apiResult = $this->callAPI($endpoint);
277
278
        if($apiResult === false || $apiResult == "Not Found") {
279
            $response->setErrorStatus(true)->setErrorStatus("[getMap] Something went wrong with the API call.");
0 ignored issues
show
Bug introduced by
'[getMap] Something went...ong with the API call.' of type string is incompatible with the type boolean expected by parameter $errorStatus of KriKrixs\object\response...ponse::setErrorStatus(). ( Ignorable by Annotation )

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

279
            $response->setErrorStatus(true)->setErrorStatus(/** @scrutinizer ignore-type */ "[getMap] Something went wrong with the API call.");
Loading history...
280
            return $response;
281
        }
282
283
        $response->setUser(new User(json_decode($apiResult)));
284
285
        return $response;
286
    }
287
288
    /**
289
     * Get user's infos by UserID
290
     * @param int $id User ID
291
     * @return array
292
     */
293
    public function getUserByID(int $id): array
294
    {
295
        return $this->getUser("/users/id/" . $id);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getUser('/users/id/' . $id) returns the type KriKrixs\object\response\ResponseUser which is incompatible with the type-hinted return array.
Loading history...
296
    }
297
}