Completed
Push — master ( 6ffbf3...199fb5 )
by Adam
03:25
created

ContainerFactory   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 94.74%

Importance

Changes 9
Bugs 2 Features 5
Metric Value
wmc 53
c 9
b 2
f 5
lcom 1
cbo 3
dl 0
loc 234
ccs 144
cts 152
cp 0.9474
rs 7.4757

13 Methods

Rating   Name   Duplication   Size   Complexity  
A addConfig() 0 4 1
A getConfigs() 0 4 1
A addContainerToMerge() 0 4 1
A getContainersToMerge() 0 4 1
A getWorkingDirectory() 0 4 1
A setWorkingDirectory() 0 4 1
B resolveFiles() 0 16 5
A readConfigs() 0 10 3
A readFile() 0 11 3
D parseValues() 0 35 9
C parseValue() 0 22 7
A resolveUnmergables() 0 11 3
C create() 0 68 17

How to fix   Complexity   

Complex Class

Complex classes like ContainerFactory 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 ContainerFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
4
namespace Genesis\Config;
5
6
7
use Nette\Neon\Decoder;
8
9
10
/**
11
 * @author Adam Bisek <[email protected]>
12
 */
13
class ContainerFactory
14
{
15
16
	private $configs;
17
18
	/** @var Container[] */
19
	private $containersToMerge;
20
21
	private $workingDirectory;
22
23
24 7
	public function addConfig($file)
25
	{
26 7
		$this->configs[] = $file;
27 7
	}
28
29
30 1
	public function getConfigs()
31
	{
32 1
		return $this->configs;
33
	}
34
35
36 3
	public function addContainerToMerge(Container $container)
37
	{
38 3
		$this->containersToMerge[] = $container;
39 3
	}
40
41
42 1
	public function getContainersToMerge()
43
	{
44 1
		return $this->containersToMerge;
45
	}
46
47
48 1
	public function getWorkingDirectory()
49
	{
50 1
		return $this->workingDirectory;
51
	}
52
53
54 8
	public function setWorkingDirectory($workingDirectory)
55
	{
56 8
		$this->workingDirectory = $workingDirectory;
57 8
	}
58
59
60 7
	public function create()
61
	{
62 7
		if (!class_exists('Nette\Neon\Decoder', TRUE)) {
63
			throw new \RuntimeException("Neon is not loaded.");
64
		}
65 7
		if (empty($this->configs)) {
66 1
			throw new \RuntimeException("No config added.");
67
		}
68
		$config = [
69
			'parameters' => [
70 6
				'workingDirectory' => $this->workingDirectory,
71
			]
72 6
		];
73 6
		if($this->containersToMerge !== NULL){
74 2
			foreach ($this->containersToMerge as $containerToMerge) {
75 2
				foreach ($containerToMerge->getParameters() as $k => $v) {
76 2
					$config['parameters'][$k] = $v;
77 2
				}
78 2
				foreach ($containerToMerge->getServices() as $k => $v) {
79 1
					$config['services'][$k] = $v;
80 2
				}
81 2
			}
82 2
		}
83 6
		$configs = $this->resolveFiles($this->configs);
84 4
		$config = $this->readConfigs($configs, $config);
85 4
		$config = $this->parseValues($config);
86
87
		// BC break check
88 4
		$mainSections = ['includes', 'class', 'parameters', 'services'];
89 4
		foreach($config as $key => $val){
90 4
			if(!in_array($key, $mainSections)) {
91 1
				throw new \RuntimeException("Since version 2.0 are supported main only these sections: " . implode(", ", $mainSections) . ". Section '$key' found. Move your variables into parameters section.");
92
			}
93 4
		}
94
95 3
		$container = new Container();
96 3
		$container->setClass($config['class']);
97 3
		if(isset($config['parameters'])){
98 3
			$container->setParameters($config['parameters']);
99 3
		}
100 3
		if(isset($config['services'])){
101 3
			foreach($config['services'] as $name => $config){
102 3
				if(!is_array($config)){
103 1
					$container->addService($name, $config); // is directly service object from merged container
104 1
					continue;
105
				}
106 3
				$class = $config['class'];
107 3
				$arguments = [];
108 3
				if($config['class'] instanceof \Nette\Neon\Entity){
109 3
					$class = $config['class']->value;
110 3
					$arguments = $config['class']->attributes;
111 3
				}
112 3
				$reflectionClass = new \ReflectionClass($class);
113 3
				$service = $reflectionClass->newInstanceArgs($arguments);
114 3
				if(isset($config['setup'])){
115 3
					foreach($config['setup'] as $neonEntity){
116 3
						if(!method_exists($service, $neonEntity->value)){
117 1
							throw new \RuntimeException("Class $class does not have method $neonEntity->value().");
118
						}
119 2
						call_user_func_array(array($service, $neonEntity->value), $neonEntity->attributes);
120 2
					}
121 2
				}
122 2
				$container->addService($name, $service);
123 2
			}
124 2
		}
125
126 2
		return $container;
127
	}
128
129
130 6
	private function resolveFiles(array $files)
131
	{
132 6
		$return = [];
133 6
		foreach ($files as $file) {
134 6
			$array = $this->readFile($file);
135 4
			if($array !== NULL){
136 4
				if(isset($array['includes'])){
137 2
					foreach($array['includes'] as $include){
138 2
						$return[] = dirname($file) . DIRECTORY_SEPARATOR . $include;
139 2
					}
140 2
				}
141 4
				$return[] = $file;
142 4
			}
143 4
		}
144 4
		return $return;
145
	}
146
147
148 4
	private function readConfigs($files, $config)
149
	{
150 4
		foreach ($files as $file) {
151 4
			$array = $this->readFile($file);
152 4
			if($array !== NULL) {
153 4
				$config = array_replace_recursive($config, $array);
154 4
			}
155 4
		}
156 4
		return $config;
157
	}
158
159
160 6
	private function readFile($file)
161
	{
162 6
		$neonDecoder = new Decoder;
163 6
		if (!is_file($file)) {
164 1
			throw new \RuntimeException("Config file '$file' not found.");
165
		}
166 5
		if (!is_readable($file)) {
167
			throw new \RuntimeException("Config file '$file' not readable.");
168
		}
169 5
		return $neonDecoder->decode(file_get_contents($file));
170
	}
171
172
173 4
	private function parseValues($config, & $allConfig = [], $keysPath = [])
174
	{
175 4
		$config = $this->resolveUnmergables($config);
176 4
		foreach ($config as $key => $value) {
177 4
			if($value instanceof \Nette\Neon\Entity){
178 3
				$value->value = $this->parseValue($value->value, $allConfig);
179 3
				foreach($value->attributes as $k => $v){
180 3
					if(is_array($v)){
181
						$value->attributes[$k] = $this->parseValues($v, $allConfig, array_merge($keysPath, [$key]));
182
					}else{
183 3
						$value->attributes[$k] = $this->parseValue($v, $allConfig);
184
					}
185 3
				}
186 4
			}elseif (is_array($value)) {
187 4
				$value = $this->parseValues($value, $allConfig, array_merge($keysPath, [$key]));
188 4
			} elseif(!is_object($value)) {
189 4
				$value = $this->parseValue($value, $allConfig);
190 4
			}
191
192
			// get new key name, and replace it
193 4
			$newKey = $this->parseValue($key, $allConfig);
194 4
			unset($config[$key]);
195 4
			$config[$newKey] = $value;
196
197
			// write to global config
198 4
			$v =& $allConfig;
199 4
			foreach ($keysPath as $kp) {
200 4
				$v =& $v[$kp];
201 4
			}
202 4
			if(!($value instanceof \Nette\Neon\Entity)) {
203 4
				$v[$newKey] = $value;
204 4
			}
205 4
		}
206 4
		return $config;
207
	}
208
209
210 4
	private function parseValue($value, $config)
211
	{
212 4
		if (preg_match_all('#%([^%]+)%#', $value, $matches)) {
213 2
			foreach ($matches[1] as $match) {
214 2
				$parameter = $config['parameters'];
215 2
				foreach(explode(".", $match) as $m){
216 2
					if (!array_key_exists($m, $parameter)) {
217
						throw new \RuntimeException("Cannot find variable '$match', part '$m'.");
218
					}
219 2
					$parameter = $parameter[$m];
220 2
				}
221 2
				if(is_array($parameter)){
222
					if("%$match%" !== $value){ // if is variable value an array, must not be part of a string
223
						throw new \RuntimeException("Array value cannot be part of a string.");
224
					}
225
					return $parameter;
226
				}
227 2
				$value = str_replace("%$match%", $parameter, $value);
228 2
			}
229 2
		}
230 4
		return $value;
231
	}
232
233
234 4
	private function resolveUnmergables($config)
235
	{
236 4
		foreach ($config as $key => $value) {
237 4
			if(preg_match('#!$#', $key)){
238 2
				$newKey = substr($key, 0, strlen($key) - 1);
239 2
				$config[$newKey] = $value;
240 2
				unset($config[$key]);
241 2
			}
242 4
		}
243 4
		return $config;
244
	}
245
246
}