Passed
Push — master ( 6791f9...01e57e )
by Adam
02:08
created

Loader   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 43.84%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 6
dl 0
loc 221
ccs 32
cts 73
cp 0.4384
rs 8.3999
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
B load() 0 35 5
A getPackageClassByFile() 0 10 2
C getGlobalMetadata() 0 52 11
D getClassesFromFile() 0 38 19
1
<?php
2
/**
3
 * Loader.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        https://www.ipublikuj.eu
7
 * @author         Adam Kadlec https://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\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 1
final class Loader implements ILoader
38
{
39
	/**
40
	 * Implement nette smart magic
41
	 */
42 1
	use Nette\SmartObject;
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
	 * @var DI\Container
71
	 */
72
	private $container;
73
74
	/**
75
	 * @param array $packageFiles
76
	 * @param array $metadataSources
77
	 * @param $vendorDir
78
	 * @param DI\Container $container
79
	 */
80
	public function __construct(array $packageFiles = [], array $metadataSources = [], $vendorDir, DI\Container $container)
81
	{
82 1
		$this->packageFiles = $packageFiles;
83 1
		$this->metadataSources = $metadataSources;
84 1
		$this->vendorDir = $vendorDir;
85 1
		$this->versionParser = new Semver\VersionParser;
86
87 1
		$this->container = $container;
88 1
	}
89
90
	/**
91
	 * @param string $file
92
	 *
93
	 * @return Entities\IPackage
94
	 *
95
	 * @throws Exceptions\InvalidPackageDefinitionException
96
	 * @throws Exceptions\InvalidStateException
97
	 */
98
	public function load(string $file) : Entities\IPackage
99
	{
100 1
		$path = dirname($file);
101
102
		try {
103 1
			$data = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
104
105
		} catch (Utils\JsonException $ex) {
0 ignored issues
show
Bug introduced by
The class Nette\Utils\JsonException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
106
			throw new Exceptions\InvalidPackageDefinitionException(sprintf('The file "%s" has invalid JSON format.', $file));
107
		}
108
109 1
		$tmpPackage = new Entities\VirtualPackage($data, $path);
110
111 1
		if (($metadata = $this->getGlobalMetadata($tmpPackage)) !== []) {
112
			$data = Utils\Arrays::mergeTree($data, [
113
				'extra' => [
114
					'ipub' => $metadata,
115
				]
116
			]);
117
		}
118
119 1
		foreach ($this->packageFiles as $packageFile) {
120 1
			if (is_file($path . DIRECTORY_SEPARATOR . $packageFile)) {
121
				$class = $this->getPackageClassByFile($path . DIRECTORY_SEPARATOR . $packageFile);
122
123
				include_once $path . DIRECTORY_SEPARATOR . $packageFile;
124
125
				$package = $this->container->createInstance($class, [$data]);
126
127 1
				return $package;
128
			}
129
		}
130
131 1
		return new Entities\VirtualPackage($data, $path);
132
	}
133
134
	/**
135
	 * @param string $file
136
	 *
137
	 * @return string
138
	 */
139
	private function getPackageClassByFile(string $file) : string
140
	{
141
		$classes = $this->getClassesFromFile($file);
142
143
		if (count($classes) !== 1) {
144
			throw new Exceptions\InvalidArgumentException(sprintf('File \'%s\' must contain only one class.', $file));
145
		}
146
147
		return $classes[0];
148
	}
149
150
	/**
151
	 * @param Entities\IPackage $package
152
	 *
153
	 * @return array
154
	 *
155
	 * @throws Exceptions\InvalidMetadataSourceDefinitionException
156
	 * @throws Exceptions\InvalidStateException
157
	 */
158
	private function getGlobalMetadata(Entities\IPackage $package) : array
159
	{
160 1
		if ($this->globalMetadata === NULL) {
161 1
			$this->globalMetadata = [];
162
163 1
			foreach ($this->metadataSources as $source) {
164 1
				if (substr($source, 0, 7) === 'http://' || substr($source, 0, 8) === 'https://') {
165 1
					$ch = curl_init();
166
167 1
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
168 1
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
169 1
					curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
170 1
					curl_setopt($ch, CURLOPT_URL, $source);
171
172 1
					$data = curl_exec($ch);
173
174
				} else {
175
					$data = file_get_contents($source);
176
				}
177
178 1
				if (!$data) {
179
					throw new Exceptions\InvalidStateException(sprintf('Source \'$source\' is empty.', $source));
180
				}
181
182 1
				if ($data) {
183
					try {
184 1
						$data = Utils\Json::decode($data, Utils\Json::FORCE_ARRAY);
185
186
					} catch (Utils\JsonException $ex) {
0 ignored issues
show
Bug introduced by
The class Nette\Utils\JsonException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
187
						throw new Exceptions\InvalidMetadataSourceDefinitionException(sprintf('The global metadata source "%s" has invalid JSON format.', $source));
188
					}
189
190 1
					$this->globalMetadata = Utils\Arrays::mergeTree($this->globalMetadata, $data);
191
				}
192
			}
193
194
		}
195
196 1
		if (!isset($this->globalMetadata[$package->getName()])) {
197 1
			return [];
198
		}
199
200
		$versionProvide = new Semver\Constraint\Constraint('==', $package->getVersion());
201
202
		foreach ($this->globalMetadata[$package->getName()] as $data) {
203
			$versionRequire = $this->versionParser->parseConstraints($data['version']);
204
205
			if ($versionRequire->matches($versionProvide)) {
206
				return $data['metadata'];
207
			}
208
		}
209
	}
210
211
	/**
212
	 * Get class names from given file
213
	 * http://stackoverflow.com/a/11070559
214
	 *
215
	 * @param string $file
216
	 *
217
	 * @return array
218
	 */
219
	private function getClassesFromFile(string $file) : array
220
	{
221
		$classes = [];
222
223
		$namespace = 0;
224
		$tokens = token_get_all(file_get_contents($file));
225
		$count = count($tokens);
226
		$dlm = FALSE;
227
228
		for ($i = 2; $i < $count; $i++) {
229
			if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === "phpnamespace" || $tokens[$i - 2][1] === "namespace")) ||
230
				($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $tokens[$i][0] === T_STRING)
231
			) {
232
				if (!$dlm) {
233
					$namespace = 0;
234
				}
235
236
				if (isset($tokens[$i][1])) {
237
					$namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1];
238
					$dlm = TRUE;
239
				}
240
241
			} elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) {
242
				$dlm = FALSE;
243
			}
244
245
			if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === "phpclass"))
246
				&& $tokens[$i - 1][0] === T_WHITESPACE && $tokens[$i][0] === T_STRING
247
			) {
248
				$class_name = $tokens[$i][1];
249
250
251
				$classes[] = $namespace . '\\' . $class_name;
252
			}
253
		}
254
255
		return $classes;
256
	}
257
}
258