Completed
Push — master ( 0bbdc9...71399c )
by Adam
01:51
created

Loader::getClassesFromFile()   D

Complexity

Conditions 19
Paths 17

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 0
cts 20
cp 0
rs 4.5166
c 0
b 0
f 0
cc 19
nc 17
nop 1
crap 380

How to fix   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        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(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
				if (substr($source, 0, 7) === 'http://' || substr($source, 0, 8) === 'https://') {
165
					$ch = curl_init();
166
167
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
168
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
169
					curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
170
					curl_setopt($ch, CURLOPT_URL, $source);
171
172
					$data = curl_exec($ch);
173
174
				} elseif (is_array($source)) {
175
					$data = $source;
176
177
				} else {
178
					$data = file_get_contents($source);
179
				}
180
181
				if (!$data) {
182
					throw new Exceptions\InvalidStateException(sprintf('Source \'$source\' is empty.', $source));
183
				}
184
185
				if ($data) {
186
					try {
187
						$data = Utils\Json::decode($data, Utils\Json::FORCE_ARRAY);
188
189
					} 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...
190
						throw new Exceptions\InvalidMetadataSourceDefinitionException(sprintf('The global metadata source "%s" has invalid JSON format.', $source));
191
					}
192
193
					$this->globalMetadata = Utils\Arrays::mergeTree($this->globalMetadata, $data);
194
				}
195
			}
196
197
		}
198
199 1
		if (!isset($this->globalMetadata[$package->getName()])) {
200 1
			return [];
201
		}
202
203
		$versionProvide = new Semver\Constraint\Constraint('==', $package->getVersion());
204
205
		foreach ($this->globalMetadata[$package->getName()] as $data) {
206
			$versionRequire = $this->versionParser->parseConstraints($data['version']);
207
208
			if ($versionRequire->matches($versionProvide)) {
209
				return $data['metadata'];
210
			}
211
		}
212
	}
213
214
	/**
215
	 * Get class names from given file
216
	 * http://stackoverflow.com/a/11070559
217
	 *
218
	 * @param string $file
219
	 *
220
	 * @return array
221
	 */
222
	private function getClassesFromFile(string $file) : array
223
	{
224
		$classes = [];
225
226
		$namespace = 0;
227
		$tokens = token_get_all(file_get_contents($file));
228
		$count = count($tokens);
229
		$dlm = FALSE;
230
231
		for ($i = 2; $i < $count; $i++) {
232
			if ((isset($tokens[$i - 2][1]) && ($tokens[$i - 2][1] === "phpnamespace" || $tokens[$i - 2][1] === "namespace")) ||
233
				($dlm && $tokens[$i - 1][0] === T_NS_SEPARATOR && $tokens[$i][0] === T_STRING)
234
			) {
235
				if (!$dlm) {
236
					$namespace = 0;
237
				}
238
239
				if (isset($tokens[$i][1])) {
240
					$namespace = $namespace ? $namespace . "\\" . $tokens[$i][1] : $tokens[$i][1];
241
					$dlm = TRUE;
242
				}
243
244
			} elseif ($dlm && ($tokens[$i][0] != T_NS_SEPARATOR) && ($tokens[$i][0] != T_STRING)) {
245
				$dlm = FALSE;
246
			}
247
248
			if (($tokens[$i - 2][0] === T_CLASS || (isset($tokens[$i - 2][1]) && $tokens[$i - 2][1] === "phpclass"))
249
				&& $tokens[$i - 1][0] === T_WHITESPACE && $tokens[$i][0] === T_STRING
250
			) {
251
				$class_name = $tokens[$i][1];
252
253
254
				$classes[] = $namespace . '\\' . $class_name;
255
			}
256
		}
257
258
		return $classes;
259
	}
260
}
261