ContainerFactory::parseValues()   D
last analyzed

Complexity

Conditions 9
Paths 17

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 9

Importance

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