Passed
Push — main ( e03448...8dc178 )
by Will
03:09
created

versions   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Test Coverage

Coverage 83.1%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 74
c 1
b 0
f 0
dl 0
loc 163
ccs 59
cts 71
cp 0.831
rs 9.2
wmc 40

4 Methods

Rating   Name   Duplication   Size   Complexity  
B load() 0 32 10
A latest() 0 14 4
B released() 0 23 10
C get() 0 68 16

How to fix   Complexity   

Complex Class

Complex classes like versions 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 versions, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types = 1);
3
namespace hexydec\agentzero;
4
5
class versions {
6
7
	/**
8
	 * @var array|false|null An array of browser version numbers
9
	 */
10
	protected static array|false|null $versions = null;
11
12
	/**
13
	 * Loads browser version information from an external source
14
	 * 
15
	 * @param string $source The URL of the source JSON containing the version information
16
	 * @param string $cache The absolute file address of the cache file
17
	 * @param ?int $life The maximum life of the cache file in seconds
18
	 * @return array<string,array<string,string>> An array of browser versioning information, or false if the data source not be retrieved
19
	 */
20 96
	protected static function load(string $source, string $cache, ?int $life = 604800) : array|false {
21
22
		// cache for this session
23 96
		$data = self::$versions;
24 96
		if ($data === null) {
25
26
			// fetch from cache
27 1
			if (\file_exists($cache) && \filemtime($cache) > \time() - $life && ($json = \file_get_contents($cache)) !== false) {
28
29
			// fetch from server
30 1
			} elseif (($json = \file_get_contents($source)) === false) {
31
32
				// get stale cache
33
				if ($cache !== null && ($json = \file_get_contents($cache)) !== false) {
34
					self::$versions = false;
35
					return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array<string,array<string,string>>.
Loading history...
36
				}
37
38
			// update cache
39
			} else {
40
41
				// create directory and cache file
42 1
				$dir = \dirname($cache);
43 1
				if (\is_dir($dir) || \mkdir($dir, 0755)) {
44 1
					\file_put_contents($cache, $json);
45
				}
46
			}
47
48
			// decode JSON
49 1
			self::$versions = $data = \json_decode($json, true);
50
		}
51 96
		return $data ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data ?? false could also return false which is incompatible with the documented return type array<string,array<string,string>>. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
52
	}
53
54
	/**
55
	 * Determines the latest version of a browser, optionally capped by the supplied date
56
	 * 
57
	 * @param array<string,string> $versions An array of browser versions, where the key is the version number and the value is the release date (In Ymd format)
58
	 */
59 91
	protected static function latest(array $versions, ?\DateTime $now = null) : ?string {
60
61
		// no date restriction
62 91
		if ($now === null) {
63 91
			return \strval(\array_key_first($versions));
64
		} else {
65
			$date = \intval($now->format('Ymd'));
66
			foreach ($versions AS $key => $item) {
67
				if ($date < $item) {
68
					return \strval($key);
69
				}
70
			}
71
		}
72
		return null;
73
	}
74
75 90
	protected static function released(array $data, string $version) : ?string {
76 90
		$major = \intval($version);
77 90
		$len = 0;
78 90
		$vlen = \strlen($version);
79 90
		$released = null;
80 90
		foreach ($data AS $ver => $date) {
81 90
			if (\intval($ver) === $major) {
82 90
				$ver = \strval($ver); // cast as string to get letters, string keys cast to int when array keys
83 90
				$match = 0;
84 90
				for ($n = 0; $n < $vlen; $n++) {
85 90
					if ($version[$n] === ($ver[$n] ?? null)) {
86 90
						$match++;
87
					} else {
88 83
						break;
89
					}
90
				}
91 90
				if ($match && $match > $len && ($released === null || $released > $date)) {
92 90
					$len = $match;
93 90
					$released = $date;
94
				}
95
			}
96
		}
97 90
		return !empty($released) ? (new \DateTime(\strval($released)))->format('Y-m-d') : null;
98
	}
99
100 96
	public static function get(string $browser, string $version, array $config) : array {
101 96
		$source = $config['versionssource'];
102 96
		$cache = $config['versionscache'];
103 96
		$life = $config['versionscachelife'];
104 96
		if ($cache !== null && ($versions = self::load($source, $cache, $life)) !== false) {
105 96
			$data = [];
106
107
			// get latest version of the browser
108 96
			if (isset($versions[$browser]) && ($data['browserlatest'] = self::latest($versions[$browser], $config['currentdate'])) !== null) {
109
				
110
				// check if version is greater than latest version
111 91
				$major = \intval($version);
112 91
				$latest = \intval($data['browserlatest']);
113 91
				$first = \intval(\array_key_last($versions[$browser]));
114
115
				// version is way out of bounds (This happens sometimes, for example if the safari engine version is reported instead of the browser version)
116 91
				if ($latest + 3 < $major) {
117 24
					return [];
118
119
				// nightly build?
120 91
				} elseif ($latest + 3 === $major) {
121
					$data['browserstatus'] = 'nightly';
122
123
				// canary build
124 91
				} elseif ($latest + 2 === $major) {
125
					$data['browserstatus'] = 'canary';
126
127
				// beta release
128 91
				} elseif ($latest + 1 === $major) {
129
					$data['browserstatus'] = 'beta';
130
131
				// so old we don't have data for it
132 91
				} elseif ($major < $first) {
133 22
					$data['browserstatus'] = 'legacy';
134
135
				// find closes match for version
136
				} else {
137
138
					// get current version
139 90
					$data['browserreleased'] = self::released($versions[$browser], $version);
140
141
					// calculate status
142 90
					if (isset($data['browserreleased'], $data['browserlatest'])) {
143 90
						$current = \explode('.', $data['browserlatest'])[0] === \explode('.', $version)[0];
144 90
						$released = new \DateTime($data['browserreleased']);
145
146
						// legacy
147 90
						if ($released < \date_create('-5 years')) {
148 75
							$data['browserstatus'] = 'legacy';
149
150
						// outdated
151 63
						} elseif ($released < \date_create('-2 years')) {
152 59
							$data['browserstatus'] = 'outdated';
153
154
						// current
155 13
						} elseif ($current && ($released >= \date_create('-1 year') || $data['browserlatest'] === $version)) {
156 1
							$data['browserstatus'] = 'current';
157
158
						// previous
159
						} else {
160 12
							$data['browserstatus'] = 'previous';
161
						}
162
					}
163
				}
164
			}
165 96
			return $data;
166
		}
167
		return [];
168
	}
169
}