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

PackagesManager   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 633
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 4.46%

Importance

Changes 0
Metric Value
wmc 76
lcom 2
cbo 9
dl 0
loc 633
ccs 9
cts 202
cp 0.0446
rs 5.3378
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
B register() 0 28 2
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 17 3
B enableAvailable() 0 37 6
B disableAbsent() 0 23 4
C install() 0 46 8
C uninstall() 0 57 10
B enable() 0 32 6
B disable() 0 32 6
A testEnable() 0 9 1
A testDisable() 0 9 1
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   Complexity   

Complex Class

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        https://www.ipublikuj.eu
7
 * @author         Adam Kadlec https://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\Packages;
23
use IPub\Packages\DependencyResolver;
24
use IPub\Packages\Entities;
25
use IPub\Packages\Exceptions;
26
use IPub\Packages\Installers;
27
use IPub\Packages\Repository;
28
use IPub\Packages\Scripts;
29
30
/**
31
 * Packages manager
32
 *
33
 * @package        iPublikuj:Packages!
34
 * @subpackage     common
35
 *
36
 * @author         Adam Kadlec <[email protected]>
37
 *
38
 * @method onEnable(PackagesManager $manager, Entities\IPackage $package)
39
 * @method onDisable(PackagesManager $manager, Entities\IPackage $package)
40
 * @method onUpgrade(PackagesManager $manager, Entities\IPackage $package)
41
 * @method onInstall(PackagesManager $manager, Entities\IPackage $package)
42
 * @method onUninstall(PackagesManager $manager, Entities\IPackage $package)
43
 * @method onRegister(PackagesManager $manager, Entities\IPackage $package)
44
 * @method onUnregister(PackagesManager $manager, Entities\IPackage $package)
45
 */
46 1
final class PackagesManager implements IPackagesManager
47
{
48
	/**
49
	 * Implement nette smart magic
50
	 */
51 1
	use Nette\SmartObject;
52
53
	/**
54
	 * Define package metadata keys
55
	 */
56
	private const PACKAGE_STATUS = 'status';
57
	private const PACKAGE_METADATA = 'metadata';
58
59
	/**
60
	 * Define actions
61
	 */
62
	private const ACTION_ENABLE = 'enable';
63
	private const ACTION_DISABLE = 'disable';
64
	private const ACTION_REGISTER = 'register';
65
	private const ACTION_UNREGISTER = 'unregister';
66
67
	/**
68
	 * @var callable[]
69
	 */
70
	public $onEnable = [];
71
72
	/**
73
	 * @var callable[]
74
	 */
75
	public $onDisable = [];
76
77
	/**
78
	 * @var callable[]
79
	 */
80
	public $onUpgrade = [];
81
82
	/**
83
	 * @var callable[]
84
	 */
85
	public $onInstall = [];
86
87
	/**
88
	 * @var callable[]
89
	 */
90
	public $onUninstall = [];
91
92
	/**
93
	 * @var callable[]
94
	 */
95
	public $onRegister = [];
96
97
	/**
98
	 * @var callable[]
99
	 */
100
	public $onUnregister = [];
101
102
	/**
103
	 * @var string
104
	 */
105
	private $vendorDir;
106
107
	/**
108
	 * @var string
109
	 */
110
	private $configDir;
111
112
	/**
113
	 * @var Repository\IRepository
114
	 */
115
	private $repository;
116
117
	/**
118
	 * @var Installers\IInstaller|NULL
119
	 */
120
	private $installer;
121
122
	/**
123
	 * @var Nette\DI\Container
124
	 */
125
	private $container;
126
127
	/**
128
	 * @var DependencyResolver\Solver
129
	 */
130
	private $dependencySolver;
131
132
	/**
133
	 * @var Utils\ArrayHash
134
	 */
135
	private $packagesConfig;
136
137
	/**
138
	 * @var Scripts\IScript[]
139
	 */
140
	private $scripts = [];
141
142
	/**
143
	 * @param string $vendorDir
144
	 * @param string $configDir
145
	 * @param Repository\IRepository $repository
146
	 * @param Installers\IInstaller|NULL $installer
147
	 */
148
	public function __construct(
149
		string $vendorDir,
150
		string $configDir,
151
		Repository\IRepository $repository,
152
		Installers\IInstaller $installer = NULL
153
	) {
154 1
		$this->vendorDir = $vendorDir;
155 1
		$this->configDir = $configDir;
156
157 1
		$this->repository = $repository;
158 1
		$this->installer = $installer;
159 1
	}
160
161
	/**
162
	 * {@inheritdoc}
163
	 */
164
	public function getStatus(Entities\IPackage $package) : string
165
	{
166
		$packageConfig = $this->getPackagesConfig();
167
168
		if (!$packageConfig->offsetExists($package->getName()) || !isset($packageConfig[$package->getName()][self::PACKAGE_STATUS])) {
169
			return Entities\IPackage::STATE_UNREGISTERED;
170
		}
171
172
		return $packageConfig[$package->getName()][self::PACKAGE_STATUS];
173
	}
174
175
	/**
176
	 * {@inheritdoc}
177
	 */
178
	public function getVersion(Entities\IPackage $package) : string
179
	{
180
		if (!$path = $package->getPath()) {
181
			throw new \RuntimeException('Package path is missing.');
182
		}
183
184
		if (!file_exists($file = $path . DIRECTORY_SEPARATOR . 'composer.json')) {
185
			throw new \RuntimeException('\'composer.json\' is missing.');
186
		}
187
188
		$packageData = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
189
190
		if (isset($packageData['version'])) {
191
			return $packageData['version'];
192
		}
193
194
		if (file_exists($file = $this->vendorDir . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'installed.json')) {
195
			$installed = Utils\Json::decode(file_get_contents($file), Utils\Json::FORCE_ARRAY);
196
197
			foreach ($installed as $packageData) {
198
				if ($packageData['name'] === $package->getName()) {
199
					return $packageData['version'];
200
				}
201
			}
202
		}
203
204
		return '0.0.0';
205
	}
206
207
	/**
208
	 * {@inheritdoc}
209
	 */
210
	public function comparePackages(Entities\IPackage $first, Entities\IPackage $second, string $operator = '==') : bool
211
	{
212
		return strtolower($first->getName()) === strtolower($second->getName()) &&
213
			version_compare(strtolower($this->getVersion($first)), strtolower($this->getVersion($second)), $operator);
214
	}
215
216
	/**
217
	 * {@inheritdoc}
218
	 */
219
	public function addScript(string $name, Scripts\IScript $service)
220
	{
221 1
		$this->scripts[$name] = $service;
222 1
	}
223
224
	/**
225
	 * {@inheritdoc}
226
	 */
227
	public function registerAvailable() : array
228
	{
229
		$actions = [];
230
231
		$installedPackages = array_keys($this->repository->getPackages());
232
		$registeredPackages = array_keys((array) $this->getPackagesConfig());
233
234
		foreach (array_diff($installedPackages, $registeredPackages) as $name) {
235
			/** @var Entities\IPackage $package */
236
			if ($package = $this->repository->findPackage($name)) {
237
				$this->register($package);
238
				$actions[] = [$name => self::ACTION_REGISTER];
239
			}
240
		}
241
242
		return $actions;
243
	}
244
245
	/**
246
	 * {@inheritdoc}
247
	 */
248
	public function enableAvailable() : array
249
	{
250
		$actions = [];
251
252
		while (TRUE) {
253
			/** @var Entities\IPackage[] $packages */
254
			$packages = $this->repository->filterPackages(function (Entities\IPackage $package) : bool {
255
				return $this->getStatus($package) === Entities\IPackage::STATE_DISABLED;
256
			});
257
258
			if (!count($packages)) {
259
				break;
260
			}
261
262
			/** @var Entities\IPackage $package */
263
			$package = reset($packages);
264
265
			foreach ($this->testEnable($package)->getSolutions() as $job) {
266
				if ($job->getAction() === DependencyResolver\Job::ACTION_ENABLE) {
267
					$this->enable($job->getPackage());
268
					$actions[] = [$job->getPackage()->getName() => self::ACTION_ENABLE];
269
270
				} elseif ($job->getAction() === DependencyResolver\Job::ACTION_DISABLE) {
271
					$this->disable($job->getPackage());
272
					$actions[] = [$job->getPackage()->getName() => self::ACTION_DISABLE];
273
				}
274
			}
275
276
			$this->enable($package);
277
278
			$this->dependencySolver = NULL;
279
280
			$actions[] = [$package->getName() => self::ACTION_ENABLE];
281
		}
282
283
		return $actions;
284
	}
285
286
	/**
287
	 * {@inheritdoc}
288
	 */
289
	public function disableAbsent() : array
290
	{
291
		$actions = [];
292
293
		$installedPackages = array_keys($this->repository->getPackages());
294
		$registeredPackages = array_keys((array) $this->getPackagesConfig());
295
296
		foreach (array_diff($registeredPackages, $installedPackages) as $name) {
297
			/** @var Entities\IPackage $package */
298
			if ($package = $this->repository->findPackage($name)) {
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];
0 ignored issues
show
Bug introduced by
The method getName 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...
307
			}
308
		}
309
310
		return $actions;
311
	}
312
313
	/**
314
	 * {@inheritdoc}
315
	 */
316
	public function install(string $name) : void
317
	{
318
		// Check if installer service is created
319
		if ($this->installer === NULL) {
320
			new Exceptions\InvalidStateException('Packages installer service is not created.');
321
		}
322
323
		// Check if package is not already installed
324
		if ($this->repository->findPackage($name)) {
325
			throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" is already installed', $name));
326
		}
327
328
		$this->installer->install($name);
329
330
		// Reload repository after installation
331
		$this->repository->reload();
332
333
		// Get newly installed package
334
		if (!$package = $this->repository->findPackage($name)) {
335
			throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" could not be found.', $name));
336
		}
337
338
		// Process all package scripts
339
		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...
340
			try {
341
				$script = $this->getScript($class);
342
				$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...
343
344
			} catch (\Exception $e) {
345
				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...
346
					if ($class === $class2) {
347
						break;
348
					}
349
350
					$script = $this->getScript($class2);
351
					$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...
352
				}
353
354
				throw new Exceptions\InvalidStateException($e->getMessage());
355
			}
356
		}
357
358
		$this->register($package);
359
360
		$this->onInstall($this, $package);
361
	}
362
363
	/**
364
	 * {@inheritdoc}
365
	 */
366
	public function uninstall(string $name) : void
367
	{
368
		// Check if installer service is created
369
		if ($this->installer === NULL) {
370
			new Exceptions\InvalidStateException('Packages installer service is not created.');
371
		}
372
373
		// Check if package is installed
374
		if (!$package = $this->repository->findPackage($name)) {
375
			throw new Exceptions\InvalidArgumentException(sprintf('Package "%s" is already uninstalled', $name));
376
		}
377
378
		// If package is still enabled, disable it first
379
		if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) {
380
			$this->disable($package);
381
		}
382
383
		// Process all package scripts
384
		foreach ($package->getScripts() as $class) {
385
			try {
386
				$script = $this->getScript($class);
387
				$script->uninstall($package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 374 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...
388
389
			} catch (\Exception $e) {
390
				foreach ($package->getScripts() as $class2) {
391
					if ($class === $class2) {
392
						break;
393
					}
394
395
					$script = $this->getScript($class2);
396
					$script->install($package);
0 ignored issues
show
Bug introduced by
It seems like $package defined by $this->repository->findPackage($name) on line 374 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...
397
				}
398
399
				throw new Exceptions\InvalidStateException($e->getMessage());
400
			}
401
		}
402
403
		$this->unregister($package);
404
405
		if ($this->installer->isInstalled($package->getName())) {
406
			$this->installer->uninstall($package->getName());
407
408
		} else {
409
			if (!$path = $package->getPath()) {
410
				throw new Exceptions\InvalidStateException('Package path is missing.');
411
			}
412
413
			$this->output->writeln("Removing package folder.");
414
415
			Utils\FileSystem::delete($path);
416
		}
417
418
		// Reload repository after uninstallation
419
		$this->repository->reload();
420
421
		$this->onUninstall($this, $package);
422
	}
423
424
	/**
425
	 * {@inheritdoc}
426
	 */
427
	public function enable(Entities\IPackage $package) : void
428
	{
429
		if ($this->getStatus($package) === Entities\IPackage::STATE_ENABLED) {
430
			throw new Exceptions\InvalidArgumentException(sprintf('Package \'%s\' is already enabled', $package->getName()));
431
		}
432
433
		$dependencyResolver = $this->getDependencySolver();
434
		$dependencyResolver->testEnable($package);
435
436
		foreach ($package->getScripts() as $class) {
437
			try {
438
				$installer = $this->getScript($class);
439
				$installer->enable($package);
440
441
			} catch (\Exception $ex) {
442
				foreach ($package->getScripts() as $class2) {
443
					if ($class === $class2) {
444
						break;
445
					}
446
447
					$installer = $this->getScript($class2);
448
					$installer->disable($package);
449
				}
450
451
				throw new Exceptions\InvalidStateException($ex->getMessage());
452
			}
453
		}
454
455
		$this->setStatus($package, Entities\IPackage::STATE_ENABLED);
456
457
		$this->onEnable($this, $package);
458
	}
459
460
	/**
461
	 * {@inheritdoc}
462
	 */
463
	public function disable(Entities\IPackage $package) : void
464
	{
465
		if ($this->getStatus($package) === Entities\IPackage::STATE_DISABLED) {
466
			throw new Exceptions\InvalidArgumentException(sprintf('Package \'%s\' is already disabled', $package->getName()));
467
		}
468
469
		$dependencyResolver = $this->getDependencySolver();
470
		$dependencyResolver->testDisable($package);
471
472
		foreach ($package->getScripts() as $class) {
473
			try {
474
				$installer = $this->getScript($class);
475
				$installer->disable($package);
476
477
			} catch (\Exception $e) {
478
				foreach ($package->getScripts() as $class2) {
479
					if ($class === $class2) {
480
						break;
481
					}
482
483
					$installer = $this->getScript($class2);
484
					$installer->enable($package);
485
				}
486
487
				throw new Exceptions\InvalidStateException($e->getMessage());
488
			}
489
		}
490
491
		$this->setStatus($package, Entities\IPackage::STATE_DISABLED);
492
493
		$this->onDisable($this, $package);
494
	}
495
496
	/**
497
	 * {@inheritdoc}
498
	 */
499
	public function testEnable(Entities\IPackage $package) : DependencyResolver\Problem
500
	{
501
		$problem = new DependencyResolver\Problem;
502
503
		$dependencyResolver = $this->getDependencySolver();
504
		$dependencyResolver->testEnable($package, $problem);
505
506
		return $problem;
507
	}
508
509
	/**
510
	 * {@inheritdoc}
511
	 */
512
	public function testDisable(Entities\IPackage $package) : DependencyResolver\Problem
513
	{
514
		$problem = new DependencyResolver\Problem;
515
516
		$dependencyResolver = $this->getDependencySolver();
517
		$dependencyResolver->testDisable($package, $problem);
518
519
		return $problem;
520
	}
521
522
	/**
523
	 * @param Entities\IPackage $package
524
	 * @param string $state
525
	 *
526
	 * @return void
527
	 */
528
	private function register(Entities\IPackage $package, string $state = Entities\IPackage::STATE_DISABLED) : void
529
	{
530
		$packagesConfig = $this->getPackagesConfig();
531
532
		if (!$packagesConfig->offsetExists($package->getName())) {
533
			// Create package config metadata
534
			$packagesConfig[$package->getName()] = [
535
				self::PACKAGE_STATUS   => $state,
536
				self::PACKAGE_METADATA => [
537
					'authors'     => array_merge((array) $package->getAuthors()),
538
					'description' => $package->getDescription(),
539
					'keywords'    => array_merge((array) $package->getKeywords()),
540
					'license'     => array_merge((array) $package->getLicense()),
541
					'require'     => $package->getRequire(),
542
					'extra'       => [
543
						'ipub' => [
544
							'configuration' => array_merge((array) $package->getConfiguration()),
545
							'scripts'       => $package->getScripts(),
546
						],
547
					],
548
				],
549
			];
550
		}
551
552
		$this->savePackagesConfig($packagesConfig);
553
554
		$this->onRegister($this, $package);
555
	}
556
557
	/**
558
	 * @param Entities\IPackage $package
559
	 *
560
	 * @return void
561
	 */
562
	private function unregister(Entities\IPackage $package) : void
563
	{
564
		$packagesConfig = $this->getPackagesConfig();
565
566
		// Remove package info from configuration file
567
		unset($packagesConfig[$package->getName()]);
568
569
		$this->savePackagesConfig($packagesConfig);
570
571
		$this->onUnregister($this, $package);
572
	}
573
574
	/**
575
	 * @param Entities\IPackage $package
576
	 * @param string $status
577
	 *
578
	 * @return void
579
	 */
580
	private function setStatus(Entities\IPackage $package, string $status) : void
581
	{
582
		if (!in_array($status, Entities\IPackage::STATUSES, TRUE)) {
583
			throw new Exceptions\InvalidArgumentException(sprintf('Status \'%s\' not exists.', $status));
584
		}
585
586
		$packagesConfig = $this->getPackagesConfig();
587
588
		// Check if package is registered
589
		if (!$packagesConfig->offsetExists($package->getName())) {
590
			throw new Exceptions\InvalidStateException(sprintf('Package "%s" is not registered. Please call ' . get_called_class() . '::registerAvailable first.', $package->getName()));
591
		}
592
593
		$packagesConfig[$package->getName()][self::PACKAGE_STATUS] = $status;
594
595
		$this->savePackagesConfig($packagesConfig);
596
	}
597
598
	/**
599
	 * @param string $class
600
	 *
601
	 * @return Scripts\IScript
602
	 *
603
	 * @throws Exceptions\InvalidStateException
604
	 */
605
	private function getScript(string $class) : Scripts\IScript
606
	{
607
		if (isset($this->scripts[$class])) {
608
			return $this->scripts[$class];
609
		}
610
611
		throw new Exceptions\InvalidStateException(sprintf('Package script "%s" was not found.', $class));
612
	}
613
614
	/**
615
	 * @return DependencyResolver\Solver
616
	 */
617
	private function getDependencySolver() : DependencyResolver\Solver
618
	{
619
		if ($this->dependencySolver === NULL) {
620
			$this->createSolver();
621
		}
622
623
		return $this->dependencySolver;
624
	}
625
626
	/**
627
	 * @return void
628
	 */
629
	private function createSolver() : void
630
	{
631
		$this->dependencySolver = new DependencyResolver\Solver($this->repository, $this);
0 ignored issues
show
Documentation introduced by
$this->repository is of type object<IPub\Packages\Repository\IRepository>, but the function expects a object<IPub\Packages\Dep...Repository\IRepository>.

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...
632
	}
633
634
	/**
635
	 * @return Utils\ArrayHash
636
	 */
637
	private function getPackagesConfig() : Utils\ArrayHash
638
	{
639
		if ($this->packagesConfig === NULL) {
640
			$config = new Nette\DI\Config\Adapters\PhpAdapter;
641
642
			if (!is_file($this->getPackageConfigPath())) {
643
				file_put_contents($this->getPackageConfigPath(), $config->dump([]));
644
			}
645
646
			$this->packagesConfig = Utils\ArrayHash::from($config->load($this->getPackageConfigPath()));
647
		}
648
649
		return $this->packagesConfig;
650
	}
651
652
	/**
653
	 * @param Utils\ArrayHash $packagesConfig
654
	 *
655
	 * @return void
656
	 *
657
	 * @throws Exceptions\NotWritableException
658
	 */
659
	private function savePackagesConfig(Utils\ArrayHash $packagesConfig) : void
660
	{
661
		$config = new Nette\DI\Config\Adapters\PhpAdapter;
662
663
		if (file_put_contents($this->getPackageConfigPath(), $config->dump(array_merge((array) $packagesConfig))) === FALSE) {
664
			throw new Exceptions\NotWritableException(sprintf('Packages configuration file "%s" is not writable.', $this->getPackageConfigPath()));
665
		};
666
667
		// Refresh packages config data
668
		$this->packagesConfig = $packagesConfig;
669
	}
670
671
	/**
672
	 * @return string
673
	 */
674
	private function getPackageConfigPath() : string
675
	{
676
		return $this->configDir . DIRECTORY_SEPARATOR . 'packages.php';
677
	}
678
}
679