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

PackagesManager   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 620
Duplicated Lines 13.23 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 73
c 2
b 0
f 0
lcom 2
cbo 14
dl 82
loc 620
rs 3.5897

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A getStatus() 0 10 3
C getVersion() 0 28 7
A comparePackages() 0 5 2
A addScript() 0 4 1
A registerAvailable() 0 14 2
B enableAvailable() 0 37 6
A disableAbsent() 0 23 3
C install() 0 44 7
C uninstall() 0 57 10
B enable() 32 32 6
B disable() 32 32 6
A testEnable() 9 9 1
A testDisable() 9 9 1
B register() 0 28 2
A unregister() 0 11 1
A setStatus() 0 17 3
A getScript() 0 8 2
A getDependencySolver() 0 8 2
A createSolver() 0 4 1
A getPackagesConfig() 0 14 3
A savePackagesConfig() 0 11 2
A getPackageConfigPath() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like PackagesManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PackagesManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * PackagesManager.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     common
10
 * @since          1.0.0
11
 *
12
 * @date           30.05.15
13
 */
14
15
declare(strict_types = 1);
16
17
namespace IPub\Packages;
18
19
use Nette;
20
use Nette\Utils;
21
22
use IPub;
23
use IPub\Packages;
24
use IPub\Packages\DependencyResolver;
25
use IPub\Packages\Entities;
26
use IPub\Packages\Exceptions;
27
use IPub\Packages\Installers;
28
use IPub\Packages\Repository;
29
use IPub\Packages\Scripts;
30
use Tracy\Debugger;
31
32
/**
33
 * Packages manager
34
 *
35
 * @package        iPublikuj:Packages!
36
 * @subpackage     common
37
 *
38
 * @author         Adam Kadlec <[email protected]>
39
 *
40
 * @method onEnable(PackagesManager $manager, Entities\IPackage $package)
41
 * @method onDisable(PackagesManager $manager, Entities\IPackage $package)
42
 * @method onUpgrade(PackagesManager $manager, Entities\IPackage $package)
43
 * @method onInstall(PackagesManager $manager, Entities\IPackage $package)
44
 * @method onUninstall(PackagesManager $manager, Entities\IPackage $package)
45
 * @method onRegister(PackagesManager $manager, Entities\IPackage $package)
46
 * @method onUnregister(PackagesManager $manager, Entities\IPackage $package)
47
 */
48
final class PackagesManager extends Nette\Object implements IPackagesManager
49
{
50
	/**
51
	 * Define class name
52
	 */
53
	const CLASS_NAME = __CLASS__;
54
55
	/**
56
	 * Define package metadata keys
57
	 */
58
	const PACKAGE_STATUS = 'status';
59
	const PACKAGE_METADATA = 'metadata';
60
61
	/**
62
	 * Define actions
63
	 */
64
	const ACTION_ENABLE = 'enable';
65
	const ACTION_DISABLE = 'disable';
66
	const ACTION_REGISTER = 'register';
67
	const ACTION_UNREGISTER = 'unregister';
68
69
	/**
70
	 * @var callable[]
71
	 */
72
	public $onEnable = [];
73
74
	/**
75
	 * @var callable[]
76
	 */
77
	public $onDisable = [];
78
79
	/**
80
	 * @var callable[]
81
	 */
82
	public $onUpgrade = [];
83
84
	/**
85
	 * @var callable[]
86
	 */
87
	public $onInstall = [];
88
89
	/**
90
	 * @var callable[]
91
	 */
92
	public $onUninstall = [];
93
94
	/**
95
	 * @var callable[]
96
	 */
97
	public $onRegister = [];
98
99
	/**
100
	 * @var callable[]
101
	 */
102
	public $onUnregister = [];
103
104
	/**
105
	 * @var string
106
	 */
107
	private $vendorDir;
108
109
	/**
110
	 * @var string
111
	 */
112
	private $configDir;
113
114
	/**
115
	 * @var Repository\IRepository
116
	 */
117
	private $repository;
118
119
	/**
120
	 * @var Installers\IInstaller|NULL
121
	 */
122
	private $installer;
123
124
	/**
125
	 * @var Nette\DI\Container
126
	 */
127
	private $container;
128
129
	/**
130
	 * @var DependencyResolver\Solver
131
	 */
132
	private $dependencySolver;
133
134
	/**
135
	 * @var Utils\ArrayHash
136
	 */
137
	private $packagesConfig;
138
139
	/**
140
	 * @var Scripts\IScript[]
141
	 */
142
	private $scripts = [];
143
144
	/**
145
	 * @param string $vendorDir
146
	 * @param string $configDir
147
	 * @param Repository\IRepository $repository
148
	 * @param Installers\IInstaller|NULL $installer
149
	 */
150
	public function __construct(
151
		string $vendorDir,
152
		string $configDir,
153
		Repository\IRepository $repository,
154
		Installers\IInstaller $installer = NULL
155
	) {
156
		$this->vendorDir = $vendorDir;
157
		$this->configDir = $configDir;
158
159
		$this->repository = $repository;
160
		$this->installer = $installer;
161
	}
162
163
	/**
164
	 * {@inheritdoc}
165
	 */
166
	public function getStatus(Entities\IPackage $package) : string
167
	{
168
		$packageConfig = $this->getPackagesConfig();
169
170
		if (!$packageConfig->offsetExists($package->getName()) || !isset($packageConfig[$package->getName()][self::PACKAGE_STATUS])) {
171
			return Entities\IPackage::STATE_UNREGISTERED;
172
		}
173
174
		return $packageConfig[$package->getName()][self::PACKAGE_STATUS];
175
	}
176
177
	/**
178
	 * {@inheritdoc}
179
	 */
180
	public function getVersion(Entities\IPackage $package) : string
181
	{
182
		if (!$path = $package->getPath()) {
183
			throw new \RuntimeException('Package path is missing.');
184
		}
185
186
		if (!file_exists($file = $path . DIRECTORY_SEPARATOR . 'composer.json')) {
187
			throw new \RuntimeException('\'composer.json\' is missing.');
188
		}
189
190
		$packageData = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
191
192
		if (isset($packageData['version'])) {
193
			return $packageData['version'];
194
		}
195
196
		if (file_exists($file = $this->vendorDir . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'installed.json')) {
197
			$installed = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
198
199
			foreach ($installed as $packageData) {
200
				if ($packageData['name'] === $package->getName()) {
201
					return $packageData['version'];
202
				}
203
			}
204
		}
205
206
		return '0.0.0';
207
	}
208
209
	/**
210
	 * {@inheritdoc}
211
	 */
212
	public function comparePackages(Entities\IPackage $first, Entities\IPackage $second, string $operator = '==') : bool
213
	{
214
		return strtolower($first->getName()) === strtolower($second->getName()) &&
215
		version_compare(strtolower($this->getVersion($first)), strtolower($this->getVersion($second)), $operator);
216
	}
217
218
	/**
219
	 * {@inheritdoc}
220
	 */
221
	public function addScript(string $name, Scripts\IScript $service)
222
	{
223
		$this->scripts[$name] = $service;
224
	}
225
226
	/**
227
	 * {@inheritdoc}
228
	 */
229
	public function registerAvailable() : array
230
	{
231
		$actions = [];
232
233
		$installedPackages = array_keys($this->repository->getPackages());
234
		$registeredPackages = array_keys((array) $this->getPackagesConfig());
235
236
		foreach (array_diff($installedPackages, $registeredPackages) as $name) {
237
			$this->register($this->repository->findPackage($name));
0 ignored issues
show
Bug introduced by
It seems like $this->repository->findPackage($name) targeting IPub\Packages\Repository...pository::findPackage() can also be of type boolean; however, IPub\Packages\PackagesManager::register() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
238
			$actions[] = [$name => self::ACTION_REGISTER];
239
		}
240
241
		return $actions;
242
	}
243
244
	/**
245
	 * {@inheritdoc}
246
	 */
247
	public function enableAvailable() : array
248
	{
249
		$actions = [];
250
251
		while (TRUE) {
252
			/** @var Entities\IPackage[] $packages */
253
			$packages = $this->repository->filterPackages(function (Entities\IPackage $package) {
254
				return $this->getStatus($package) === Entities\IPackage::STATE_DISABLED;
255
			});
256
257
			if (!count($packages)) {
258
				break;
259
			}
260
261
			/** @var Entities\IPackage $package */
262
			$package = reset($packages);
263
264
			foreach ($this->testEnable($package)->getSolutions() as $job) {
265
				if ($job->getAction() === DependencyResolver\Job::ACTION_ENABLE) {
266
					$this->enable($job->getPackage());
267
					$actions[] = [$job->getPackage()->getName() => self::ACTION_ENABLE];
268
269
				} elseif ($job->getAction() === DependencyResolver\Job::ACTION_DISABLE) {
270
					$this->disable($job->getPackage());
271
					$actions[] = [$job->getPackage()->getName() => self::ACTION_DISABLE];
272
				}
273
			}
274
275
			$this->enable($package);
276
277
			$this->dependencySolver = NULL;
278
279
			$actions[] = [$package->getName() => self::ACTION_ENABLE];
280
		}
281
282
		return $actions;
283
	}
284
285
	/**
286
	 * {@inheritdoc}
287
	 */
288
	public function disableAbsent() : array
289
	{
290
		$actions = [];
291
292
		$installedPackages = array_keys($this->repository->getPackages());
293
		$registeredPackages = array_keys((array) $this->getPackagesConfig());
294
295
		foreach (array_diff($registeredPackages, $installedPackages) as $name) {
296
			/** @var Entities\IPackage $package */
297
			$package = $this->repository->findPackage($name);
298
299
			if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) {
300
				$this->disable($package);
301
				$actions[] = [$name => self::ACTION_DISABLE];
302
			}
303
304
			$this->disable($package);
305
306
			$actions[] = [$package->getName() => self::ACTION_DISABLE];
307
		}
308
309
		return $actions;
310
	}
311
312
	/**
313
	 * {@inheritdoc}
314
	 */
315
	public function install(string $name, bool $packagist = FALSE, bool $preferSource = TRUE)
316
	{
317
		// Check if installer service is created
318
		if ($this->installer === NULL) {
319
			new Exceptions\InvalidStateException('Packages installer service is not created.');
320
		}
321
322
		// Check if package is not already installed
323
		if ($this->repository->findPackage($name)) {
324
			throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" is already installed', $name));
325
		}
326
327
		$this->installer->install($name, $packagist, $preferSource);
0 ignored issues
show
Unused Code introduced by
The call to IInstaller::install() has too many arguments starting with $packagist.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
328
329
		// Reload repository after installation
330
		$this->repository->reload();
331
332
		// Get newly installed package
333
		$package = $this->repository->findPackage($name);
334
335
		// Process all package scripts
336
		foreach ($package->getScripts() as $class) {
0 ignored issues
show
Bug introduced by
The method getScripts cannot be called on $package (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
337
			try {
338
				$script = $this->getScript($class);
339
				$script->install($package);
0 ignored issues
show
Documentation introduced by
$package is of type boolean, but the function expects a object<IPub\Packages\Entities\IPackage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
340
341
			} catch (\Exception $e) {
342
				foreach ($package->getScripts() as $class2) {
0 ignored issues
show
Bug introduced by
The method getScripts cannot be called on $package (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
343
					if ($class === $class2) {
344
						break;
345
					}
346
347
					$script = $this->getScript($class2);
348
					$script->uninstall($package);
0 ignored issues
show
Documentation introduced by
$package is of type boolean, but the function expects a object<IPub\Packages\Entities\IPackage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
349
				}
350
351
				throw new Exceptions\InvalidStateException($e->getMessage());
352
			}
353
		}
354
355
		$this->register($package);
0 ignored issues
show
Documentation introduced by
$package is of type boolean, but the function expects a object<IPub\Packages\Entities\IPackage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
356
357
		$this->onInstall($this, $package);
0 ignored issues
show
Documentation introduced by
$package is of type boolean, but the function expects a object<IPub\Packages\Entities\IPackage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
358
	}
359
360
	/**
361
	 * {@inheritdoc}
362
	 */
363
	public function uninstall(string $name)
364
	{
365
		// Check if installer service is created
366
		if ($this->installer === NULL) {
367
			new Exceptions\InvalidStateException('Packages installer service is not created.');
368
		}
369
370
		// Check if package is installed
371
		if (!$package = $this->repository->findPackage($name)) {
372
			throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" is already uninstalled', $name));
373
		}
374
375
		// If package is still enabled, disable it first
376
		if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) {
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 371 can also be of type boolean; however, IPub\Packages\PackagesManager::getStatus() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
377
			$this->disable($package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 371 can also be of type boolean; however, IPub\Packages\PackagesManager::disable() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
378
		}
379
380
		// Process all package scripts
381
		foreach ($package->getScripts() as $class) {
382
			try {
383
				$script = $this->getScript($class);
384
				$script->uninstall($package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 371 can also be of type boolean; however, IPub\Packages\Scripts\IScript::uninstall() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
385
386
			} catch (\Exception $e) {
387
				foreach ($package->getScripts() as $class2) {
388
					if ($class === $class2) {
389
						break;
390
					}
391
392
					$script = $this->getScript($class2);
393
					$script->install($package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 371 can also be of type boolean; however, IPub\Packages\Scripts\IScript::install() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
394
				}
395
396
				throw new Exceptions\InvalidStateException($e->getMessage());
397
			}
398
		}
399
400
		$this->unregister($package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 371 can also be of type boolean; however, IPub\Packages\PackagesManager::unregister() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
401
402
		if ($this->installer->isInstalled($package->getName())) {
403
			$this->installer->uninstall($package->getName());
404
405
		} else {
406
			if (!$path = $package->getPath()) {
407
				throw new Exceptions\InvalidStateException('Package path is missing.');
408
			}
409
410
			$this->output->writeln("Removing package folder.");
0 ignored issues
show
Documentation introduced by
The property output does not exist on object<IPub\Packages\PackagesManager>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
411
412
			Utils\FileSystem::delete($path);
413
		}
414
415
		// Reload repository after uninstallation
416
		$this->repository->reload();
417
418
		$this->onUninstall($this, $package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 371 can also be of type boolean; however, IPub\Packages\PackagesManager::onUninstall() does only seem to accept object<IPub\Packages\Entities\IPackage>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
419
	}
420
421
	/**
422
	 * {@inheritdoc}
423
	 */
424 View Code Duplication
	public function enable(Entities\IPackage $package)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
425
	{
426
		if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) {
427
			throw new Exceptions\InvalidArgumentException(sprintf('Package \'%s\' is already enabled', $package->getName()));
428
		}
429
430
		$dependencyResolver = $this->getDependencySolver();
431
		$dependencyResolver->testEnable($package);
432
433
		foreach ($package->getScripts() as $class) {
434
			try {
435
				$installer = $this->getScript($class);
436
				$installer->enable($package);
437
438
			} catch (\Exception $e) {
439
				foreach ($package->getScripts() as $class2) {
440
					if ($class === $class2) {
441
						break;
442
					}
443
444
					$installer = $this->getScript($class2);
445
					$installer->disable($package);
446
				}
447
448
				throw new Exceptions\InvalidStateException($e->getMessage());
449
			}
450
		}
451
452
		$this->setStatus($package, Entities\IPackage::STATE_ENABLED);
453
454
		$this->onEnable($this, $package);
455
	}
456
457
	/**
458
	 * {@inheritdoc}
459
	 */
460 View Code Duplication
	public function disable(Entities\IPackage $package)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
461
	{
462
		if ($this->getStatus($package) === Entities\IPackage::STATE_DISABLED) {
463
			throw new Exceptions\InvalidArgumentException(sprintf('Package \'%s\' is already disabled', $package->getName()));
464
		}
465
466
		$dependencyResolver = $this->getDependencySolver();
467
		$dependencyResolver->testDisable($package);
468
469
		foreach ($package->getScripts() as $class) {
470
			try {
471
				$installer = $this->getScript($class);
472
				$installer->disable($package);
473
474
			} catch (\Exception $e) {
475
				foreach ($package->getScripts() as $class2) {
476
					if ($class === $class2) {
477
						break;
478
					}
479
480
					$installer = $this->getScript($class2);
481
					$installer->enable($package);
482
				}
483
484
				throw new Exceptions\InvalidStateException($e->getMessage());
485
			}
486
		}
487
488
		$this->setStatus($package, Entities\IPackage::STATE_DISABLED);
489
490
		$this->onDisable($this, $package);
491
	}
492
493
	/**
494
	 * {@inheritdoc}
495
	 */
496 View Code Duplication
	public function testEnable(Entities\IPackage $package) : DependencyResolver\Problem
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
497
	{
498
		$problem = new DependencyResolver\Problem;
499
500
		$dependencyResolver = $this->getDependencySolver();
501
		$dependencyResolver->testEnable($package, $problem);
502
503
		return $problem;
504
	}
505
506
	/**
507
	 * {@inheritdoc}
508
	 */
509 View Code Duplication
	public function testDisable(Entities\IPackage $package) : DependencyResolver\Problem
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
510
	{
511
		$problem = new DependencyResolver\Problem;
512
513
		$dependencyResolver = $this->getDependencySolver();
514
		$dependencyResolver->testDisable($package, $problem);
515
516
		return $problem;
517
	}
518
519
	/**
520
	 * @param Entities\IPackage $package
521
	 * @param string $state
522
	 */
523
	private function register(Entities\IPackage $package, string $state = Entities\IPackage::STATE_DISABLED)
524
	{
525
		$packagesConfig = $this->getPackagesConfig();
526
527
		if (!$packagesConfig->offsetExists($package->getName())) {
528
			// Create package config metadata
529
			$packagesConfig[$package->getName()] = [
530
				self::PACKAGE_STATUS   => $state,
531
				self::PACKAGE_METADATA => [
532
					'authors'     => array_merge((array) $package->getAuthors()),
533
					'description' => $package->getDescription(),
534
					'keywords'    => array_merge((array) $package->getKeywords()),
535
					'license'     => array_merge((array) $package->getLicense()),
536
					'require'     => $package->getRequire(),
537
					'extra'       => [
538
						'ipub' => [
539
							'configuration' => array_merge((array) $package->getConfiguration()),
540
							'scripts'       => $package->getScripts(),
541
						],
542
					],
543
				],
544
			];
545
		}
546
547
		$this->savePackagesConfig($packagesConfig);
548
549
		$this->onRegister($this, $package);
550
	}
551
552
	/**
553
	 * @param Entities\IPackage $package
554
	 */
555
	private function unregister(Entities\IPackage $package)
556
	{
557
		$packagesConfig = $this->getPackagesConfig();
558
559
		// Remove package info from configuration file
560
		unset($packagesConfig[$package->getName()]);
561
562
		$this->savePackagesConfig($packagesConfig);
563
564
		$this->onUnregister($this, $package);
565
	}
566
567
	/**
568
	 * @param Entities\IPackage $package
569
	 * @param string $status
570
	 */
571
	private function setStatus(Entities\IPackage $package, string $status)
572
	{
573
		if (!in_array($status, Entities\IPackage::STATUSES, TRUE)) {
574
			throw new Exceptions\InvalidArgumentException(sprintf('Status \'%s\' not exists.', $status));
575
		}
576
577
		$packagesConfig = $this->getPackagesConfig();
578
579
		// Check if package is registered
580
		if (!$packagesConfig->offsetExists($package->getName())) {
581
			throw new Exceptions\InvalidStateException(sprintf('Package "%s" is not registered. Please call ' . get_called_class() . '::registerAvailable first.', $package->getName()));
582
		}
583
584
		$packagesConfig[$package->getName()][self::PACKAGE_STATUS] = $status;
585
586
		$this->savePackagesConfig($packagesConfig);
587
	}
588
589
	/**
590
	 * @param string $class
591
	 *
592
	 * @return Scripts\IScript
593
	 * 
594
	 * @throws Exceptions\InvalidStateException
595
	 */
596
	private function getScript(string $class) : Scripts\IScript
597
	{
598
		if (isset($this->scripts[$class])) {
599
			return $this->scripts[$class];
600
		}
601
602
		throw new Exceptions\InvalidStateException(sprintf('Package script "%s" was not found.', $class));
603
	}
604
605
	/**
606
	 * @return DependencyResolver\Solver
607
	 */
608
	private function getDependencySolver() : DependencyResolver\Solver
609
	{
610
		if ($this->dependencySolver === NULL) {
611
			$this->createSolver();
612
		}
613
614
		return $this->dependencySolver;
615
	}
616
617
	/**
618
	 * @return void
619
	 */
620
	private function createSolver()
621
	{
622
		$this->dependencySolver = new DependencyResolver\Solver($this->repository, $this);
623
	}
624
625
	/**
626
	 * @return Utils\ArrayHash
627
	 */
628
	private function getPackagesConfig() : Utils\ArrayHash
629
	{
630
		if ($this->packagesConfig === NULL) {
631
			$config = new Nette\DI\Config\Adapters\PhpAdapter;
632
633
			if (!is_file($this->getPackageConfigPath())) {
634
				file_put_contents($this->getPackageConfigPath(), $config->dump([]));
635
			}
636
637
			$this->packagesConfig = Utils\ArrayHash::from($config->load($this->getPackageConfigPath()));
638
		}
639
640
		return $this->packagesConfig;
641
	}
642
643
	/**
644
	 * @param Utils\ArrayHash $packagesConfig
645
	 * 
646
	 * @throws Exceptions\NotWritableException
647
	 */
648
	private function savePackagesConfig(Utils\ArrayHash $packagesConfig)
649
	{
650
		$config = new Nette\DI\Config\Adapters\PhpAdapter;
651
652
		if (file_put_contents($this->getPackageConfigPath(), $config->dump(array_merge((array) $packagesConfig))) === FALSE) {
653
			throw new Exceptions\NotWritableException(sprintf('Packages configuration file "%s" is not writable.', $this->getPackageConfigPath()));
654
		};
655
656
		// Refresh packages config data
657
		$this->packagesConfig = $packagesConfig;
658
	}
659
660
	/**
661
	 * @return string
662
	 */
663
	private function getPackageConfigPath() : string
664
	{
665
		return $this->configDir . DIRECTORY_SEPARATOR . 'packages.php';
666
	}
667
}
668