Completed
Push — develop ( 96b89c...125390 )
by Kevin
06:48 queued 03:10
created

Builder::build()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 12
cts 12
cp 1
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 12
nc 6
nop 2
crap 4
1
<?php
2
3
namespace Magium\Configuration\Config;
4
5
use Interop\Container\ContainerInterface;
6
use Magium\Configuration\Config\Repository\ConfigInterface;
7
use Magium\Configuration\Config\Repository\ConfigurationRepository;
8
use Magium\Configuration\Config\Storage\CallbackInterface;
9
use Magium\Configuration\Config\Storage\StorageInterface as ConfigurationStorageInterface;
10
use Magium\Configuration\Container\GenericContainer;
11
use Magium\Configuration\File\AdapterInterface;
12
use Magium\Configuration\File\Configuration\ConfigurationFileRepository;
13
use Magium\Configuration\File\InvalidFileException;
14
use Magium\Configuration\InvalidConfigurationException;
15
use Zend\Cache\Storage\StorageInterface;
16
17
class Builder implements BuilderInterface
18
{
19
20
    protected $repository;
21
    protected $cache;
22
    protected $container;
23
    protected $hashAlgo;
24
    protected $storage;
25
26 21
    public function __construct(
27
        StorageInterface $cache,
28
        ConfigurationStorageInterface $storage,
29
        ConfigurationFileRepository $repository,
30
        ContainerInterface $container = null,
31
        $hashAlgo = 'sha1'
32
    )
33
    {
34 21
        $this->cache = $cache;
35 21
        $this->storage = $storage;
36 21
        $this->hashAlgo = $hashAlgo;
37 21
        $this->repository = $repository;
38 21
        $this->container = $container;
39 21
    }
40
41 3
    public function getConfigurationRepository()
42
    {
43 3
        return $this->repository;
44
    }
45
46 1
    public function getStorage()
47
    {
48 1
        return $this->storage;
49
    }
50
51 6
    public function setValue($path, $value, $requestedContext = ConfigurationRepository::CONTEXT_DEFAULT)
52
    {
53 6
        $parts = explode('/', $path);
54 6
        if (count($parts) != 3) {
55 3
            throw new InvalidArgumentException('Path must be in the structure of section/group/element');
56
        }
57
58 3
        $merged = $this->getMergedStructure();
59 3
        $merged->registerXPathNamespace('s', 'http://www.magiumlib.com/Configuration');
60
61 3
        $xpath = sprintf('//s:section[@identifier="%s"]/s:group[@identifier="%s"]/s:element[@identifier="%s"]/s:permittedValues/s:value',
62 3
            $parts[0],
63 3
            $parts[1],
64 3
            $parts[2]
65
        );
66
67 3
        $elements = $merged->xpath($xpath);
68 3
        if ($elements) {
69 3
            $check = [];
70 3
            foreach ($elements as $element) {
71 3
                $check[] = (string)$element;
72
            }
73 3
            if (!in_array($value, $check)) {
74 1
                throw new InvalidArgumentException('The value must be one of: ' . implode(', ', $check));
75
            }
76
        }
77
78 2
        return $this->getStorage()->setValue($path, $value, $requestedContext);
79
    }
80
81 8
    public function getContainer()
82
    {
83 8
        if (!$this->container instanceof ContainerInterface) {
84 8
            $this->container = new GenericContainer();
85 8
            $this->container->set($this);
86 8
            $this->container->set($this->cache);
87 8
            $this->container->set($this->repository);
88 8
            $this->container->set($this->storage);
89
        }
90 8
        return $this->container;
91
    }
92
93
    /**
94
     * Retrieves a list of files that have been registered
95
     *
96
     * @return ConfigurationFileRepository
97
     */
98
99 5
    public function getRegisteredConfigurationFiles()
100
    {
101 5
        return $this->repository;
102
    }
103
104
    /**
105
     * @param ConfigurationRepository|null $config
106
     * @return ConfigurationRepository
107
     * @throws InvalidConfigurationLocationException
108
     * @throws InvalidFileException
109
     * @throws
110
     */
111
112 6
    public function build($context = ConfigurationRepository::CONTEXT_DEFAULT, ConfigInterface $config = null)
113
    {
114
115 6
        if (!$config instanceof ConfigurationRepository) {
116 6
            $config = new ConfigurationRepository('<config />');
117
        }
118
119 6
        $structure = $this->getMergedStructure();
120
121 4
        if (!$structure instanceof MergedStructure) {
122 1
            throw new InvalidConfigurationException('No configuration files provided');
123
        }
124
125 3
        $container = $this->getContainer();
126 3
        if ($container instanceof GenericContainer) {
127 3
            $container->set($config);
128 3
            $container->set($structure);
129
        }
130
131 3
        $this->buildConfigurationObject($structure, $config, $context);
132
133 3
        return $config;
134
    }
135
136
    /**
137
     * @return \SimpleXMLElement
138
     * @throws InvalidFileException
139
     * @throws MissingConfigurationException
140
     */
141
142 7
    public function getMergedStructure()
143
    {
144 7
        $files = $this->getRegisteredConfigurationFiles();
145 7
        if (!count($files)) {
146 1
            throw new MissingConfigurationException('No configuration files have been provided.  Please add via registerConfigurationFile()');
147
        }
148
149 6
        $structure = null;
150 6
        foreach ($files as $file) {
151 5
            if (!$file instanceof AdapterInterface) {
152 1
                throw new InvalidFileException('Configuration file object must implement ' . AdapterInterface::class);
153
            }
154
155 4
            $simpleXml = $file->toXml();
156 4
            if (!$structure instanceof MergedStructure) {
157 4
                $structure = $simpleXml;
158
            } else {
159 4
                $this->mergeStructure($structure, $simpleXml);
160
            }
161
        }
162 4
        return $structure;
163
    }
164
165 6
    protected function executeCallback($callbackString, $value)
166
    {
167 6
        if (function_exists($callbackString)) {
168 1
            $execute = $callbackString;
169
        } else {
170 5
            $container = $this->getContainer();
171
            try {
172 5
                $callbackObject = $this->getContainer()->get($callbackString);
173 3
                if (!$callbackObject instanceof CallbackInterface) {
174 1
                    throw new UncallableCallbackException('Callback must implement ' . CallbackInterface::class);
175
                }
176
                $execute = [
177 2
                    $callbackObject,
178 2
                    'filter'
179
                ];
180
181 2
                if (!is_callable($execute)) {
182 2
                    throw new UncallableCallbackException('Unable to execute callback: ' . $callbackString);
183
                }
184 3
            } catch (\Exception $e) {
185
                /*
186
                 * This is slightly a hack but the purpose is to throw the insufficient exception if it's an actual
187
                 * problem with the generic container we provide.
188
                 */
189 3
                if ($container instanceof GenericContainer && !$e instanceof UncallableCallbackException) {
190 2
                    throw new InsufficientContainerException(
191
                        'You are using functionality that requires either a fully functional DI Container or Service Locator.  '
192 2
                        . 'The container, or container adapter, must implement Interop\Container\ContainerInterface.'
193
                    );
194
                }
195 1
                throw $e;
196
            }
197
        }
198
199 3
        return call_user_func($execute, $value);
200
    }
201
202
203
    /**
204
     * @param \SimpleXMLElement $structure The object representing the merged configuration structure
205
     * @param \SimpleXmlElement $config An empty config object to be populated
206
     * @param string $context The name of the context
207
     * @return ConfigurationRepository The resulting configuration object
208
     */
209
210 14
    public function buildConfigurationObject(
211
        MergedStructure $structure,
212
        ConfigInterface $config,
213
        $context = ConfigurationRepository::CONTEXT_DEFAULT
214
    )
215
    {
216 14
        $structure->registerXPathNamespace('s', 'http://www.magiumlib.com/Configuration');
217 14
        $elements = $structure->xpath('/*/s:section/s:group/s:element');
218 14
        foreach ($elements as $element) {
219 14
            if ($element instanceof MergedStructure) {
220 14
                $elementId = $element['identifier'];
221 14
                $group = $element->xpath('..')[0];
222 14
                $groupId = $group['identifier'];
223 14
                $section = $group->xpath('..')[0];
224 14
                $sectionId = $section['identifier'];
225 14
                $configPath = sprintf('%s/%s/%s', $sectionId, $groupId, $elementId);
226 14
                $value = $this->storage->getValue($configPath, $context);
227 14
                if (!$value) {
228 7
                    $xpath = sprintf('/*/s:section[@identifier="%s"]/s:group[@identifier="%s"]/s:element[@identifier="%s"]/s:value',
229
                        $sectionId,
230
                        $groupId,
231
                        $elementId
232
                    );
233 7
                    $result = $structure->xpath($xpath);
234 7
                    if (!empty($result)) {
235 7
                        $value = trim((string)$result[0]);
236
                    }
237
                }
238 14
                if (isset($element['callbackFromStorage'])) {
239 6
                    $callbackString = (string)$element['callbackFromStorage'];
240
241 6
                    $value = $this->executeCallback($callbackString, $value);
242
                }
243
244 11
                if ($value) {
245 11
                    $config->$sectionId->$groupId->$elementId = $value;
246
                }
247
            }
248
        }
249 11
    }
250
251 4 View Code Duplication
    public function mergeStructure(MergedStructure $base, \SimpleXMLElement $new)
252
    {
253 4
        $base->registerXPathNamespace('s', 'http://www.magiumlib.com/Configuration');
254 4
        foreach ($new as $item) {
255 4
            if ($item instanceof \SimpleXMLElement) {
256 4
                $xpath = sprintf('/*/s:section[@identifier="%s"]', $item['identifier']);
257 4
                $sectionExists = $base->xpath($xpath);
258
259 4
                if (!empty($sectionExists) && $sectionExists[0] instanceof \SimpleXMLElement) {
260 3
                    $section = $sectionExists[0];
261
                } else {
262 1
                    $section = $base->addChild('section');
263
                }
264
265 4
                foreach ($item->attributes() as $name => $value) {
266 4
                    $section[$name] = (string)$value;
267
                }
268 4
                if ($item->group) {
269 4
                    $this->mergeGroup($section, $item->group);
270
                }
271
            }
272
        }
273 4
    }
274
275 4 View Code Duplication
    protected function mergeGroup(\SimpleXMLElement $section, \SimpleXMLElement $newGroups)
276
    {
277 4
        $section->registerXPathNamespace('s', 'http://www.magiumlib.com/Configuration');
278 4
        foreach ($newGroups as $newGroup) {
279 4
            if ($newGroup instanceof \SimpleXMLElement) {
280 4
                $xpath = sprintf('./s:group[@identifier="%s"]', $newGroup['identifier']);
281 4
                $groupExists = $section->xpath($xpath);
282
283 4
                if (!empty($groupExists) && $groupExists[0] instanceof \SimpleXMLElement) {
284 3
                    $group = $groupExists[0];
285
                } else {
286 2
                    $group = $section->addChild('group');
287
                }
288 4
                foreach ($newGroup->attributes() as $name => $value) {
289 4
                    $group[$name] = (string)$value;
290
                }
291 4
                $this->mergeElements($group, $newGroup->element);
292
            }
293
        }
294 4
    }
295
296 4
    protected function mergeElements(\SimpleXMLElement $group, \SimpleXMLElement $newElements)
297
    {
298 4
        $group->registerXPathNamespace('s', 'http://www.magiumlib.com/Configuration');
299 4
        foreach ($newElements as $newElement) {
300 4
            if ($newElement instanceof \SimpleXMLElement) {
301 4
                $xpath = sprintf('./s:element[@identifier="%s"]', $newElement['identifier']);
302 4
                $elementExists = $group->xpath($xpath);
303
304 4
                if (!empty($elementExists) && $elementExists[0] instanceof \SimpleXMLElement) {
305 1
                    $element = $elementExists[0];
306
                } else {
307 4
                    $element = $group->addChild('element');
308
                }
309 4
                foreach ($newElement->attributes() as $name => $value) {
310 4
                    $element[$name] = (string)$value;
311
                }
312 4
                foreach ($newElement->children() as $key => $item) {
313 4
                    $element->$key = (string)$item;
314
                }
315
            }
316
        }
317 4
    }
318
319
}
320