Completed
Push — master ( 665429...116aa7 )
by Adam
07:10
created

Loader::getGlobalMetadata()   C

Complexity

Conditions 11
Paths 12

Size

Total Lines 52
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 74.7581

Importance

Changes 0
Metric Value
dl 0
loc 52
ccs 5
cts 26
cp 0.1923
rs 5.9999
c 0
b 0
f 0
cc 11
eloc 28
nc 12
nop 1
crap 74.7581

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