Kitsu   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 227
Duplicated Lines 18.5 %

Coupling/Cohesion

Components 0
Dependencies 0

Importance

Changes 0
Metric Value
dl 42
loc 227
rs 10
c 0
b 0
f 0
wmc 28
lcom 0
cbo 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getStatusToSelectMap() 10 10 1
A getStatusToMangaSelectMap() 10 10 1
A getAiringStatus() 0 21 4
B getServiceMetaData() 0 35 4
A parseStreamingLinks() 11 23 3
B parseListItemStreamingLinks() 11 28 5
A filterTitles() 0 18 4
B titleIsUnique() 0 21 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php declare(strict_types=1);
2
/**
3
 * Anime List Client
4
 *
5
 * An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
6
 *
7
 * PHP version 7
8
 *
9
 * @package     AnimeListClient
10
 * @author      Timothy J. Warren <[email protected]>
11
 * @copyright   2015 - 2017  Timothy J. Warren
12
 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13
 * @version     4.0
14
 * @link        https://github.com/timw4mail/HummingBirdAnimeClient
15
 */
16
17
namespace Aviat\AnimeClient\API;
18
19
use Aviat\AnimeClient\API\Kitsu\Enum\{
20
	AnimeAiringStatus,
21
	AnimeWatchingStatus,
22
	MangaReadingStatus
23
};
24
use DateTimeImmutable;
25
26
const AUTH_URL = 'https://kitsu.io/api/oauth/token';
27
const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
28
const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
29
30
/**
31
 * Data massaging helpers for the Kitsu API
32
 */
33
class Kitsu {
34
	const AUTH_URL = 'https://kitsu.io/api/oauth/token';
35
	const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
36
	const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
37
38
	/**
39
	 * Map of Kitsu status to label for select menus
40
	 *
41
	 * @return array
42
	 */
43 View Code Duplication
	public static function getStatusToSelectMap()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44
	{
45
		return [
46
			AnimeWatchingStatus::WATCHING => 'Currently Watching',
47
			AnimeWatchingStatus::PLAN_TO_WATCH => 'Plan to Watch',
48
			AnimeWatchingStatus::COMPLETED => 'Completed',
49
			AnimeWatchingStatus::ON_HOLD => 'On Hold',
50
			AnimeWatchingStatus::DROPPED => 'Dropped'
51
		];
52
	}
53
54
	/**
55
	 * Map of Kitsu Manga status to label for select menus
56
	 *
57
	 * @return array
58
	 */
59 View Code Duplication
	public static function getStatusToMangaSelectMap()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
60
	{
61
		return [
62
			MangaReadingStatus::READING => 'Currently Reading',
63
			MangaReadingStatus::PLAN_TO_READ => 'Plan to Read',
64
			MangaReadingStatus::COMPLETED => 'Completed',
65
			MangaReadingStatus::ON_HOLD => 'On Hold',
66
			MangaReadingStatus::DROPPED => 'Dropped'
67
		];
68
	}
69
70
	/**
71
	 * Determine whether an anime is airing, finished airing, or has not yet aired
72
	 *
73
	 * @param string $startDate
0 ignored issues
show
Documentation introduced by
Should the type for parameter $startDate not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
74
	 * @param string $endDate
0 ignored issues
show
Documentation introduced by
Should the type for parameter $endDate not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
75
	 * @return string
76
	 */
77
	public static function getAiringStatus(string $startDate = null, string $endDate = null): string
78
	{
79
		$startAirDate = new DateTimeImmutable($startDate ?? 'tomorrow');
80
		$endAirDate = new DateTimeImmutable($endDate ?? 'next year');
81
		$now = new DateTimeImmutable();
82
83
		$isDoneAiring = $now > $endAirDate;
84
		$isCurrentlyAiring = ($now > $startAirDate) && ! $isDoneAiring;
85
86
		switch (true)
87
		{
88
			case $isCurrentlyAiring:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
89
				return AnimeAiringStatus::AIRING;
90
91
			case $isDoneAiring:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
92
				return AnimeAiringStatus::FINISHED_AIRING;
93
94
			default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
95
				return AnimeAiringStatus::NOT_YET_AIRED;
96
		}
97
	}
98
99
	/**
100
	 * Get the name and logo for the streaming service of the current link
101
	 *
102
	 * @param string $hostname
0 ignored issues
show
Documentation introduced by
Should the type for parameter $hostname not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
103
	 * @return array
104
	 */
105
	protected static function getServiceMetaData(string $hostname = null): array
106
	{
107
		switch($hostname)
108
		{
109
			case 'www.crunchyroll.com':
110
				return [
111
					'name' => 'Crunchyroll',
112
					'link' => true,
113
					'logo' => '<svg class="streaming-logo" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><g fill="#F78B24" fill-rule="evenodd"><path d="M22.549 49.145c-.815-.077-2.958-.456-3.753-.663-6.873-1.79-12.693-6.59-15.773-13.009C1.335 31.954.631 28.807.633 24.788c.003-4.025.718-7.235 2.38-10.686 1.243-2.584 2.674-4.609 4.706-6.66 3.8-3.834 8.614-6.208 14.067-6.936 1.783-.239 5.556-.161 7.221.148 3.463.642 6.571 1.904 9.357 3.797 5.788 3.934 9.542 9.951 10.52 16.861.21 1.48.332 4.559.19 4.816-.077.14-.117-.007-.167-.615-.25-3.015-1.528-6.66-3.292-9.388C40.253 7.836 30.249 4.32 20.987 7.467c-7.15 2.43-12.522 8.596-13.997 16.06-.73 3.692-.51 7.31.658 10.882a21.426 21.426 0 0 0 13.247 13.518c1.475.515 3.369.944 4.618 1.047 1.496.122 1.119.239-.727.224-1.006-.008-2.013-.032-2.237-.053z"></path><path d="M27.685 46.1c-7.731-.575-14.137-6.455-15.474-14.204-.243-1.41-.29-4.047-.095-5.345 1.16-7.706 6.97-13.552 14.552-14.639 1.537-.22 4.275-.143 5.746.162 1.28.266 2.7.737 3.814 1.266l.865.411-.814.392c-2.936 1.414-4.748 4.723-4.323 7.892.426 3.173 2.578 5.664 5.667 6.56 1.112.322 2.812.322 3.925 0 1.438-.417 2.566-1.1 3.593-2.173.346-.362.652-.621.68-.576.027.046.106.545.176 1.11.171 1.395.07 4.047-.204 5.371-.876 4.218-3.08 7.758-6.463 10.374-3.2 2.476-7.434 3.711-11.645 3.399z"></path></g></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 1311 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
114
				];
115
116
			case 'www.funimation.com':
117
				return [
118
					'name' => 'Funimation',
119
					'link' => true,
120
					'logo' => '<svg class="streaming-logo" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M24.066.017a24.922 24.922 0 0 1 13.302 3.286 25.098 25.098 0 0 1 7.833 7.058 24.862 24.862 0 0 1 4.207 9.575c.82 4.001.641 8.201-.518 12.117a24.946 24.946 0 0 1-4.868 9.009 24.98 24.98 0 0 1-7.704 6.118 24.727 24.727 0 0 1-10.552 2.718A24.82 24.82 0 0 1 13.833 47.3c-5.815-2.872-10.408-8.107-12.49-14.25-2.162-6.257-1.698-13.375 1.303-19.28C5.483 8.07 10.594 3.55 16.602 1.435A24.94 24.94 0 0 1 24.066.017zm-8.415 33.31c.464 2.284 1.939 4.358 3.99 5.48 2.174 1.217 4.765 1.444 7.202 1.181 2.002-.217 3.986-.992 5.455-2.397 1.173-1.151 2.017-2.648 2.33-4.267-1.189-.027-2.378 0-3.566-.03-.568.082-1.137-.048-1.705.014-1.232.012-2.465.003-3.697-.01-.655.066-1.309-.035-1.963.013-1.166-.053-2.334.043-3.5-.025-1.515.08-3.03-.035-4.546.042z" fill="#411299" fill-rule="evenodd"></path></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 895 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
121
				];
122
123
			case 'www.hulu.com':
124
				return [
125
					'name' => 'Hulu',
126
					'link' => true,
127
					'logo' => '<svg class="streaming-logo" viewBox="0 0 34 50" xmlns="http://www.w3.org/2000/svg"><path d="M22.222 13.889h-11.11V0H0v50h11.111V27.778c0-1.39 1.111-2.778 2.778-2.778h5.555c1.39 0 2.778 1.111 2.778 2.778V50h11.111V25c0-6.111-5-11.111-11.11-11.111z" fill="#8BC34A" fill-rule="evenodd"></path></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 313 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
128
				];
129
130
			// Default to Netflix, because the API links are broken,
131
			// and there's no other real identifier for Netflix
132
			default:
133
				return [
134
					'name' => 'Netflix',
135
					'link' => false,
136
					'logo' => '<svg class="streaming-logo" viewBox="0 0 26 50" xmlns="http://www.w3.org/2000/svg"><path d="M.057.258C2.518.253 4.982.263 7.446.253c2.858 7.76 5.621 15.556 8.456 23.324.523 1.441 1.003 2.897 1.59 4.312.078-9.209.01-18.42.034-27.631h7.763v46.36c-2.812.372-5.637.627-8.457.957-1.203-3.451-2.396-6.902-3.613-10.348-1.796-5.145-3.557-10.302-5.402-15.428.129 8.954.015 17.912.057 26.871-2.603.39-5.227.637-7.815 1.119C.052 33.279.06 16.768.057.258z" fill="#E21221" fill-rule="evenodd"></path></svg>'
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 510 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
137
				];
138
		}
139
	}
140
141
	/**
142
	 * Reorganize streaming links
143
	 *
144
	 * @param array $included
145
	 * @return array
146
	 */
147
	public static function parseStreamingLinks(array $included): array
148
	{
149
		if ( ! array_key_exists('streamingLinks', $included))
150
		{
151
			return [];
152
		}
153
154
		$links = [];
155
156 View Code Duplication
		foreach ($included['streamingLinks'] as $streamingLink)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
157
		{
158
			$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
159
160
			$links[] = [
161
				'meta' => static::getServiceMetaData($host),
0 ignored issues
show
Security Bug introduced by
It seems like $host defined by parse_url($streamingLink['url'], \PHP_URL_HOST) on line 158 can also be of type false; however, Aviat\AnimeClient\API\Kitsu::getServiceMetaData() does only seem to accept null|string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
162
				'link' => $streamingLink['url'],
163
				'subs' => $streamingLink['subs'],
164
				'dubs' => $streamingLink['dubs']
165
			];
166
		}
167
168
		return $links;
169
	}
170
171
	/**
172
	 * Reorganize streaming links for the current list item
173
	 *
174
	 * @param array $included
175
	 * @return array
176
	 */
177
	public static function parseListItemStreamingLinks(array $included, string $animeId): array
178
	{
179
		// Anime lists have a different structure to search through
180
		if (array_key_exists('anime', $included) && ! array_key_exists('streamingLinks', $included))
181
		{
182
			$links = [];
183
			$anime = $included['anime'][$animeId];
184
185
			if (count($anime['relationships']['streamingLinks']) > 0)
186
			{
187 View Code Duplication
				foreach ($anime['relationships']['streamingLinks'] as $streamingLink)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
				{
189
					$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
190
191
					$links[] = [
192
						'meta' => static::getServiceMetaData($host),
0 ignored issues
show
Security Bug introduced by
It seems like $host defined by parse_url($streamingLink['url'], \PHP_URL_HOST) on line 189 can also be of type false; however, Aviat\AnimeClient\API\Kitsu::getServiceMetaData() does only seem to accept null|string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
193
						'link' => $streamingLink['url'],
194
						'subs' => $streamingLink['subs'],
195
						'dubs' => $streamingLink['dubs']
196
					];
197
				}
198
			}
199
200
			return $links;
201
		}
202
203
		return [];
204
	}
205
206
	/**
207
	 * Filter out duplicate and very similar names from
208
	 *
209
	 * @param array $data The 'attributes' section of the api data response
210
	 * @return array List of alternate titles
211
	 */
212
	public static function filterTitles(array $data): array
213
	{
214
		// The 'canonical' title is always returned
215
		$valid = [$data['canonicalTitle']];
216
217
		if (array_key_exists('titles', $data))
218
		{
219
			foreach($data['titles'] as $alternateTitle)
220
			{
221
				if (self::titleIsUnique($alternateTitle, $valid))
222
				{
223
					$valid[] = $alternateTitle;
224
				}
225
			}
226
		}
227
228
		return $valid;
229
	}
230
231
	/**
232
	 * Determine if an alternate title is unique enough to list
233
	 *
234
	 * @param string $title
0 ignored issues
show
Documentation introduced by
Should the type for parameter $title not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
235
	 * @param array $existingTitles
236
	 * @return bool
237
	 */
238
	private static function titleIsUnique(string $title = null, array $existingTitles): bool
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
239
	{
240
		if (empty($title))
241
		{
242
			return false;
243
		}
244
245
		foreach($existingTitles as $existing)
246
		{
247
			$isSubset = stripos($existing, $title) !== FALSE;
248
			$diff = levenshtein($existing, $title);
249
			$onlydifferentCase = (mb_strtolower($existing) === mb_strtolower($title));
250
251
			if ($diff < 3 || $isSubset || $onlydifferentCase)
252
			{
253
				return false;
254
			}
255
		}
256
257
		return true;
258
	}
259
}