Loader   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 28%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 6
dl 0
loc 228
ccs 21
cts 75
cp 0.28
rs 9.28
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A load() 0 35 5
A getPackageClassByFile() 0 10 2
C getGlobalMetadata() 0 55 12
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 <[email protected]>
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(
81
		array $packageFiles = [],
82
		array $metadataSources = [],
83
		$vendorDir,
84
		DI\Container $container
85
	) {
86 1
		$this->packageFiles = $packageFiles;
87 1
		$this->metadataSources = $metadataSources;
88 1
		$this->vendorDir = $vendorDir;
89 1
		$this->versionParser = new Semver\VersionParser;
90
91 1
		$this->container = $container;
92 1
	}
93
94
	/**
95
	 * @param string $file
96
	 *
97
	 * @return Entities\IPackage
98
	 *
99
	 * @throws Exceptions\InvalidPackageDefinitionException
100
	 * @throws Exceptions\InvalidStateException
101
	 */
102
	public function load(string $file) : Entities\IPackage
103
	{
104 1
		$path = dirname($file);
105
106
		try {
107 1
			$data = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
108
109
		} 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...
110
			throw new Exceptions\InvalidPackageDefinitionException(sprintf('The file "%s" has invalid JSON format.', $file));
111
		}
112
113 1
		$tmpPackage = new Entities\VirtualPackage($data, $path);
114
115 1
		if (($metadata = $this->getGlobalMetadata($tmpPackage)) !== []) {
116
			$data = Utils\Arrays::mergeTree($data, [
117
				'extra' => [
118
					'ipub' => $metadata,
119
				],
120
			]);
121
		}
122
123 1
		foreach ($this->packageFiles as $packageFile) {
124 1
			if (is_file($path . DIRECTORY_SEPARATOR . $packageFile)) {
125
				$class = $this->getPackageClassByFile($path . DIRECTORY_SEPARATOR . $packageFile);
126
127
				include_once $path . DIRECTORY_SEPARATOR . $packageFile;
128
129
				$package = $this->container->createInstance($class, [$data]);
130
131 1
				return $package;
132
			}
133
		}
134
135 1
		return new Entities\VirtualPackage($data, $path);
136
	}
137
138
	/**
139
	 * @param string $file
140
	 *
141
	 * @return string
142
	 */
143
	private function getPackageClassByFile(string $file) : string
144
	{
145
		$classes = $this->getClassesFromFile($file);
146
147
		if (count($classes) !== 1) {
148
			throw new Exceptions\InvalidArgumentException(sprintf('File \'%s\' must contain only one class.', $file));
149
		}
150
151
		return $classes[0];
152
	}
153
154
	/**
155
	 * @param Entities\IPackage $package
156
	 *
157
	 * @return array
158
	 *
159
	 * @throws Exceptions\InvalidMetadataSourceDefinitionException
160
	 * @throws Exceptions\InvalidStateException
161
	 */
162
	private function getGlobalMetadata(Entities\IPackage $package) : array
163
	{
164 1
		if ($this->globalMetadata === NULL) {
165 1
			$this->globalMetadata = [];
166
167 1
			foreach ($this->metadataSources as $source) {
168
				if (substr($source, 0, 7) === 'http://' || substr($source, 0, 8) === 'https://') {
169
					$ch = curl_init();
170
171
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
172
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
173
					curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
174
					curl_setopt($ch, CURLOPT_URL, $source);
175
176
					$data = curl_exec($ch);
177
178
				} elseif (is_array($source)) {
179
					$data = $source;
180
181
				} else {
182
					$data = file_get_contents($source);
183
				}
184
185
				if (!$data) {
186
					throw new Exceptions\InvalidStateException(sprintf('Source \'$source\' is empty.', $source));
187
				}
188
189
				if ($data) {
190
					try {
191
						$data = Utils\Json::decode($data, Utils\Json::FORCE_ARRAY);
192
193
					} 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...
194
						throw new Exceptions\InvalidMetadataSourceDefinitionException(sprintf('The global metadata source "%s" has invalid JSON format.', $source));
195
					}
196
197
					$this->globalMetadata = Utils\Arrays::mergeTree($this->globalMetadata, $data);
198
				}
199
			}
200
201
		}
202
203 1
		if (!isset($this->globalMetadata[$package->getName()])) {
204 1
			return [];
205
		}
206
207
		$versionProvide = new Semver\Constraint\Constraint('==', $package->getVersion());
208
209
		foreach ($this->globalMetadata[$package->getName()] as $data) {
210
			$versionRequire = $this->versionParser->parseConstraints($data['version']);
211
212
			if ($versionRequire->matches($versionProvide)) {
213
				return $data['metadata'];
214
			}
215
		}
216
	}
217
218
	/**
219
	 * Get class names from given file
220
	 * http://stackoverflow.com/a/11070559
221
	 *
222
	 * @param string $file
223
	 *
224
	 * @return array
225
	 */
226
	private function getClassesFromFile(string $file) : array
227
	{
228
		$classes = [];
229
230
		$namespace = 0;
231
		$tokens = token_get_all(file_get_contents($file));
232
		$count = count($tokens);
233
		$dlm = FALSE;
234
235
		for ($i = 2; $i < $count; $i++) {
236
			if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === "phpnamespace" || $tokens[$i - 2][1] === "namespace")) ||
237
				($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $tokens[$i][0] === T_STRING)
238
			) {
239
				if (!$dlm) {
240
					$namespace = 0;
241
				}
242
243
				if (isset($tokens[$i][1])) {
244
					$namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1];
245
					$dlm = TRUE;
246
				}
247
248
			} elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) {
249
				$dlm = FALSE;
250
			}
251
252
			if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === "phpclass"))
253
				&& $tokens[$i - 1][0] === T_WHITESPACE && $tokens[$i][0] === T_STRING
254
			) {
255
				$class_name = $tokens[$i][1];
256
257
258
				$classes[] = $namespace . '\\' . $class_name;
259
			}
260
		}
261
262
		return $classes;
263
	}
264
}
265