Completed
Push — master ( 60e59a...334051 )
by Adam
06:35
created

Loader::getGlobalMetadata()   C

Complexity

Conditions 11
Paths 12

Size

Total Lines 52
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 52
rs 5.9999
cc 11
eloc 28
nc 12
nop 1

How to fix   Long Method    Complexity   

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:

1
<?php
2
/**
3
 * Loader.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec http://www.ipublikuj.eu
8
 * @package        iPublikuj:Packages!
9
 * @subpackage     Loaders
10
 * @since          1.0.0
11
 *
12
 * @date           30.05.15
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\Packages\Loaders;
18
19
use Composer;
20
use Composer\Semver;
21
22
use Nette;
23
use Nette\Utils;
24
25
use IPub;
26
use IPub\Packages\Entities;
27
use IPub\Packages\Exceptions;
28
29
/**
30
 * Package loader
31
 *
32
 * @package        iPublikuj:Packages!
33
 * @subpackage     Loaders
34
 *
35
 * @author         Adam Kadlec <[email protected]>
36
 */
37
final class Loader implements ILoader
38
{
39
	/**
40
	 * Define class name
41
	 */
42
	const CLASS_NAME = __CLASS__;
43
44
	/**
45
	 * @var string[]
46
	 */
47
	private $packageFiles = [];
48
49
	/**
50
	 * @var array
51
	 */
52
	private $metadataSources = [];
53
54
	/**
55
	 * @var string
56
	 */
57
	private $vendorDir;
58
59
	/**
60
	 * @var array|NULL
61
	 */
62
	private $globalMetadata;
63
64
	/**
65
	 * @var Semver\VersionParser
66
	 */
67
	private $versionParser;
68
69
	/**
70
	 * @param array $packageFiles
71
	 * @param array $metadataSources
72
	 * @param string $vendorDir
73
	 */
74
	public function __construct(array $packageFiles = [], array $metadataSources = [], $vendorDir)
75
	{
76
		$this->packageFiles = $packageFiles;
77
		$this->metadataSources = $metadataSources;
78
		$this->vendorDir = $vendorDir;
79
		$this->versionParser = new Semver\VersionParser;
80
	}
81
82
	/**
83
	 * @param string $file
84
	 *
85
	 * @return Entities\IPackage
86
	 *
87
	 * @throws Exceptions\InvalidPackageDefinitionException
88
	 * @throws Exceptions\InvalidStateException
89
	 */
90
	public function load(string $file) : Entities\IPackage
91
	{
92
		$path = dirname($file);
93
94
		try {
95
			$data = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
96
97
		} catch (Utils\JsonException $ex) {
98
			throw new Exceptions\InvalidPackageDefinitionException(sprintf('The file "%s" has invalid JSON format.', $file));
99
		}
100
101
		$tmpPackage = new Entities\VirtualPackage($data, $path);
102
103
		if (($metadata = $this->getGlobalMetadata($tmpPackage)) !== []) {
104
			$data = Utils\Arrays::mergeTree($data, [
105
				'extra' => [
106
					'ipub' => $metadata,
107
				]
108
			]);
109
		}
110
111
		foreach ($this->packageFiles as $packageFile) {
112
			if (is_file($path . DIRECTORY_SEPARATOR . $packageFile)) {
113
				$class = $this->getPackageClassByFile($path . DIRECTORY_SEPARATOR . $packageFile);
114
115
				include_once $path . DIRECTORY_SEPARATOR . $packageFile;
116
117
				return new $class($data);
118
			}
119
		}
120
121
		return new Entities\VirtualPackage($data, $path);
122
	}
123
124
125
	/**
126
	 * @param string $file
127
	 *
128
	 * @return string
129
	 */
130
	private function getPackageClassByFile(string $file) : string
131
	{
132
		$classes = $this->getClassesFromFile($file);
133
134
		if (count($classes) !== 1) {
135
			throw new Exceptions\InvalidArgumentException(sprintf('File \'%s\' must contain only one class.', $file));
136
		}
137
138
		return $classes[0];
139
	}
140
141
	/**
142
	 * @param Entities\IPackage $package
143
	 *
144
	 * @return array
145
	 *
146
	 * @throws Exceptions\InvalidMetadataSourceDefinitionException
147
	 * @throws Exceptions\InvalidStateException
148
	 */
149
	private function getGlobalMetadata(Entities\IPackage $package) : array
150
	{
151
		if ($this->globalMetadata === NULL) {
152
			$this->globalMetadata = [];
153
154
			foreach ($this->metadataSources as $source) {
155
				if (substr($source, 0, 7) === 'http://' || substr($source, 0, 8) === 'https://') {
156
					$ch = curl_init();
157
158
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
159
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
160
					curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
161
					curl_setopt($ch, CURLOPT_URL, $source);
162
163
					$data = curl_exec($ch);
164
165
				} else {
166
					$data = file_get_contents($source);
167
				}
168
169
				if (!$data) {
170
					throw new Exceptions\InvalidStateException(sprintf('Source \'$source\' is empty.', $source));
171
				}
172
173
				if ($data) {
174
					try {
175
						$data = Utils\Json::decode($data, Utils\Json::FORCE_ARRAY);
176
177
					} catch (Utils\JsonException $ex) {
178
						throw new Exceptions\InvalidMetadataSourceDefinitionException(sprintf('The global metadata source "%s" has invalid JSON format.', $source));
179
					}
180
181
					$this->globalMetadata = Utils\Arrays::mergeTree($this->globalMetadata, $data);
182
				}
183
			}
184
185
		}
186
187
		if (!isset($this->globalMetadata[$package->getName()])) {
188
			return [];
189
		}
190
191
		$versionProvide = new Semver\Constraint\Constraint('==', $package->getVersion());
192
193
		foreach ($this->globalMetadata[$package->getName()] as $data) {
194
			$versionRequire = $this->versionParser->parseConstraints($data['version']);
195
196
			if ($versionRequire->matches($versionProvide)) {
197
				return $data['metadata'];
198
			}
199
		}
200
	}
201
202
	/**
203
	 * Get class names from given file
204
	 * http://stackoverflow.com/a/11070559
205
	 *
206
	 * @param string $file
207
	 *
208
	 * @return array
209
	 */
210
	private function getClassesFromFile(string $file) : array
211
	{
212
		$classes = [];
213
214
		$namespace = 0;
215
		$tokens = token_get_all(file_get_contents($file));
216
		$count = count($tokens);
217
		$dlm = FALSE;
218
219
		for ($i = 2; $i < $count; $i++) {
220
			if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === "phpnamespace" || $tokens[$i - 2][1] === "namespace")) ||
221
				($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $tokens[$i][0] === T_STRING)
222
			) {
223
				if (!$dlm) {
224
					$namespace = 0;
225
				}
226
227
				if (isset($tokens[$i][1])) {
228
					$namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1];
229
					$dlm = TRUE;
230
				}
231
232
			} elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) {
233
				$dlm = FALSE;
234
			}
235
236
			if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === "phpclass"))
237
				&& $tokens[$i - 1][0] === T_WHITESPACE && $tokens[$i][0] === T_STRING
238
			) {
239
				$class_name = $tokens[$i][1];
240
241
				if (!isset($classes[$namespace])) {
242
					$classes[$namespace] = [];
243
				}
244
245
				$classes[$namespace][] = $class_name;
246
			}
247
		}
248
249
		return $classes;
250
	}
251
}
252