1
|
|
|
<?php |
2
|
|
|
namespace Composed; |
3
|
|
|
|
4
|
|
|
class PackageCollection implements \IteratorAggregate |
5
|
|
|
{ |
6
|
|
|
private $packages; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* @param Package[] $packages |
10
|
|
|
*/ |
11
|
|
|
public function __construct(array $packages) |
12
|
|
|
{ |
13
|
|
|
$this->packages = $packages; |
14
|
|
|
} |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @return \Iterator |
18
|
|
|
*/ |
19
|
|
|
public function getIterator() |
20
|
|
|
{ |
21
|
|
|
return new \ArrayIterator($this->packages); |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @return null|Package |
26
|
|
|
*/ |
27
|
|
|
public function getPackage(string $name) |
28
|
|
|
{ |
29
|
|
|
return isset($this->packages[$name]) ? $this->packages[$name] : null; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Get a config value from all packages |
34
|
|
|
* |
35
|
|
|
* @param string|array $keys Either a string, e.g. 'required' to get the dependencies by a package, or an array, |
36
|
|
|
* e.g. ['required', 'php'] to get the version of PHP required by a package |
37
|
|
|
* |
38
|
|
|
* @param mixed $default If this is not explicitly specified, packages which do not provide any config value will be |
39
|
|
|
* omitted from the result. Explicitly setting this to NULL is not the same as omitting it and will result in |
40
|
|
|
* packages which do not provide config being returned with a value of NULL |
41
|
|
|
* |
42
|
|
|
* @return array Keys are package names, values are the retrieved config as an array or scalar |
43
|
|
|
*/ |
44
|
|
|
public function getConfig($keys = [], $default = null) : array |
45
|
|
|
{ |
46
|
|
|
if (2 > func_num_args()) { |
47
|
|
|
$default = new \stdClass; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
$config = @array_map( |
51
|
|
|
function (AbstractPackage $package) use ($keys, $default) { |
52
|
|
|
return $package->getConfig($keys, $default); |
|
|
|
|
53
|
|
|
}, |
54
|
|
|
$this->packages |
55
|
|
|
); |
56
|
|
|
|
57
|
|
|
if (2 > func_num_args()) { |
58
|
|
|
$config = array_filter($config, function ($value) use ($default) { |
59
|
|
|
return $value !== $default; |
60
|
|
|
}); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
return $config; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
public function toArray() : array |
67
|
|
|
{ |
68
|
|
|
return $this->packages; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
public function getPackageNames() : array |
72
|
|
|
{ |
73
|
|
|
return array_keys($this->packages); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Returns all packages sorted based on their dependencies. Each package is guaranteed to appear after all of its |
78
|
|
|
* dependencies in the collection |
79
|
|
|
*/ |
80
|
|
|
public function sortByDependencies() : self |
81
|
|
|
{ |
82
|
|
|
/** @var $packages Package[] */ |
83
|
|
|
$packages = $this->packages; |
84
|
|
|
$sortedPackages = []; |
85
|
|
|
|
86
|
|
|
while (!empty($packages)) { |
87
|
|
|
foreach ($packages as $packageName => $package) { |
88
|
|
|
$dependentPackages = $package->getDependencies()->toArray(); |
89
|
|
|
if (empty(array_diff_key($dependentPackages, $sortedPackages))) { |
90
|
|
|
// all of this packages dependencies are already in the sorted array, so it can be appended |
91
|
|
|
$sortedPackages[$packageName] = $package; |
92
|
|
|
unset($packages[$packageName]); |
93
|
|
|
continue(2); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
throw new \LogicException('Packages have circular dependencies'); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return new static($sortedPackages); |
100
|
|
|
} |
101
|
|
|
} |
102
|
|
|
|
This check looks at variables that have been passed in as parameters and 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.