Xml::getOptions()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace phpbu\App\Configuration\Loader;
3
4
use DOMElement;
5
use DOMXPath;
6
use phpbu\App\Configuration;
7
use phpbu\App\Configuration\Loader;
8
use phpbu\App\Exception;
9
use phpbu\App\Util\Str;
10
11
/**
12
 * Loader for a phpbu XML configuration file.
13
 *
14
 * Example XML configuration file:
15
 * <code>
16
 * <?xml version="1.0" encoding="UTF-8" ?>
17
 * <phpbu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
18
 *        xsi:noNamespaceSchemaLocation="http://schema.phpbu.de/1.1/phpbu.xsd"
19
 *        bootstrap="backup/bootstrap.php"
20
 *        verbose="true">
21
 *
22
 *   <logging>
23
 *     <log type="json" target="/tmp/logfile.json" />
24
 *   </logging>
25
 *
26
 *   <backups>
27
 *     <backup>
28
 *       <source type="mysql">
29
 *         <option name="databases" value="dbname" />
30
 *         <option name="tables" value="" />
31
 *         <option name="ignoreTables" value="" />
32
 *         <option name="structureOnly" value="dbname.table1,dbname.table2" />
33
 *       </source>
34
 *
35
 *       <target dirname="/tmp/backup" filename="mysqldump-%Y%m%d-%H%i.sql" compress="bzip2" />
36
 *
37
 *       <check type="sizemin" value="10MB" />
38
 *
39
 *       <crypt type="mcrypt">
40
 *         <option name="algorithm" value="blowfish"/>
41
 *         <option name="key" value="myKey"/>
42
 *       </crypt>
43
 *
44
 *       <sync type="sftp" skipOnFailure="true">
45
 *         <option name="host" value="example.com" />
46
 *         <option name="user" value="user.name" />
47
 *         <option name="password" value="topsecret" />
48
 *         <option name="path" value="backup" />
49
 *       </sync>
50
 *
51
 *       <cleanup type="Outdated" skipOnFailure="true">
52
 *         <option name="older" value="2W" />
53
 *       </cleanup>
54
 *     </backup>
55
 *   </backups>
56
 * </phpbu>
57
 * </code>
58
 *
59
 * @package    phpbu
60
 * @subpackage App
61
 * @author     Sebastian Feldmann <[email protected]>
62
 * @copyright  Sebastian Feldmann <[email protected]>
63
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
64
 * @link       http://phpbu.de/
65
 * @since      Class available since Release 2.0.0
66
 */
67
class Xml extends File implements Loader
68
{
69
    /**
70
     * Config file DOMDocument
71
     *
72
     * @var \DOMDocument
73
     */
74
    private $document;
75
76
    /**
77
     * Xpath to navigate the config DOM.
78
     *
79
     * @var \DOMXPath
80
     */
81
    private $xpath;
82
83
    /**
84
     * Xml constructor.
85
     *
86
     * @param  string                                $file
87
     * @param  \phpbu\App\Configuration\Bootstrapper $bootstrapper
88
     * @throws \phpbu\App\Exception
89
     */
90 19
    public function __construct(string $file, Configuration\Bootstrapper $bootstrapper)
91
    {
92 19
        parent::__construct($file, $bootstrapper);
93 19
        $this->document = $this->loadXmlFile($file);
94 17
        $this->xpath    = new DOMXPath($this->document);
95
96 17
        $this->validateConfigurationAgainstSchema();
97 17
    }
98
99
    /**
100
     * Return list of adapter configs.
101
     *
102
     * @return array
103
     * @throws \phpbu\App\Exception
104
     */
105 17
    protected function getAdapterConfigs()
106
    {
107 17
        $adapters = [];
108
        /** @var \DOMElement $adapterNode */
109 17
        foreach ($this->xpath->query('adapters/adapter') as $adapterNode) {
110 5
            $type    = $adapterNode->getAttribute('type');
111 5
            $name    = $adapterNode->getAttribute('name');
112 5
            $options = $this->getOptions($adapterNode);
113 5
            if (!$type) {
114 1
                throw new Exception('invalid adapter configuration: attribute type missing');
115
            }
116 4
            if (!$name) {
117 1
                throw new Exception('invalid adapter configuration: attribute name missing');
118
            }
119 3
            $adapters[] = new Configuration\Adapter($type, $name, $options);
120
        }
121 15
        return $adapters;
122
    }
123
124
    /**
125
     * Set the phpbu application settings.
126
     *
127
     * @param  \phpbu\App\Configuration $configuration
128
     */
129 17
    public function setAppSettings(Configuration $configuration)
130
    {
131 17
        $root = $this->document->documentElement;
132
133 17
        if ($root->hasAttribute('bootstrap')) {
134 17
            $configuration->setBootstrap($this->toAbsolutePath($root->getAttribute('bootstrap')));
135
        }
136 17
        if ($root->hasAttribute('verbose')) {
137 16
            $configuration->setVerbose(Str::toBoolean($root->getAttribute('verbose'), false));
138
        }
139 17
        if ($root->hasAttribute('colors')) {
140 16
            $configuration->setColors(Str::toBoolean($root->getAttribute('colors'), false));
141
        }
142 17
    }
143
144
    /**
145
     * Set the log configuration.
146
     *
147
     * @param  \phpbu\App\Configuration $configuration
148
     * @throws \phpbu\App\Exception
149
     */
150 15
    public function setLoggers(Configuration $configuration)
151
    {
152
        /** @var \DOMElement $logNode */
153 15
        foreach ($this->xpath->query('logging/log') as $logNode) {
154 13
            $type = $logNode->getAttribute('type');
155 13
            if (!$type) {
156 1
                throw new Exception('invalid logger configuration: attribute type missing');
157
            }
158 12
            $options = $this->getOptions($logNode);
159 12
            if (isset($options['target'])) {
160 1
                $options['target'] = $this->toAbsolutePath($options['target']);
161
            }
162
            // search for target attribute to convert to option
163 12
            $target = $logNode->getAttribute('target');
164 12
            if (!empty($target)) {
165 11
                $options['target'] = $this->getAdapterizedValue($this->toAbsolutePath($target));
166
            }
167 12
            $configuration->addLogger(new Configuration\Logger($type, $options));
168
        }
169 14
    }
170
171
    /**
172
     * Set the backup configurations.
173
     *
174
     * @param  \phpbu\App\Configuration $configuration
175
     * @throws \phpbu\App\Exception
176
     */
177 14
    public function setBackups(Configuration $configuration)
178
    {
179 14
        foreach ($this->xpath->query('backups/backup') as $backupNode) {
180 14
            $configuration->addBackup($this->getBackupConfig($backupNode));
181
        }
182 7
    }
183
184
    /**
185
     * Get the config for a single backup node.
186
     *
187
     * @param  \DOMElement $backupNode
188
     * @throws \phpbu\App\Exception
189
     * @return \phpbu\App\Configuration\Backup
190
     */
191 14
    private function getBackupConfig(DOMElement $backupNode)
192
    {
193 14
        $stopOnFailure = Str::toBoolean($backupNode->getAttribute('stopOnFailure'), false);
194 14
        $backupName    = $this->getAdapterizedValue($backupNode->getAttribute('name'));
195 14
        $backup        = new Configuration\Backup($backupName, $stopOnFailure);
196
197 14
        $backup->setSource($this->getSource($backupNode));
198 12
        $backup->setTarget($this->getTarget($backupNode));
199
200 11
        $this->setChecks($backup, $backupNode);
201 11
        $this->setCrypt($backup, $backupNode);
202 10
        $this->setSyncs($backup, $backupNode);
203 8
        $this->setCleanup($backup, $backupNode);
204
205 7
        return $backup;
206
    }
207
208
    /**
209
     * Get source configuration.
210
     *
211
     * @param  \DOMElement $node
212
     * @return \phpbu\App\Configuration\Backup\Source
213
     * @throws \phpbu\App\Exception
214
     */
215 14
    protected function getSource(DOMElement $node)
216
    {
217 14
        $sources = $node->getElementsByTagName('source');
218 14
        if ($sources->length !== 1) {
219 1
            throw new Exception('backup requires exactly one source config');
220
        }
221
        /** @var DOMElement $sourceNode */
222 13
        $sourceNode = $sources->item(0);
223 13
        $type       = $sourceNode->getAttribute('type');
224 13
        if (!$type) {
225 1
            throw new Exception('source requires type attribute');
226
        }
227
228 12
        return new Configuration\Backup\Source($type, $this->getOptions($sourceNode));
229
    }
230
231
    /**
232
     * Get Target configuration.
233
     *
234
     * @param  \DOMElement $node
235
     * @return \phpbu\App\Configuration\Backup\Target
236
     * @throws \phpbu\App\Exception
237
     */
238 12
    protected function getTarget(DOMElement $node)
239
    {
240 12
        $targets = $node->getElementsByTagName('target');
241 12
        if ($targets->length !== 1) {
242 1
            throw new Exception('backup requires exactly one target config');
243
        }
244
        /** @var DOMElement $targetNode */
245 11
        $targetNode = $targets->item(0);
246 11
        $compress   = $targetNode->getAttribute('compress');
247 11
        $filename   = $this->getAdapterizedValue($targetNode->getAttribute('filename'));
248 11
        $dirname    = $this->getAdapterizedValue($targetNode->getAttribute('dirname'));
249
250 11
        if ($dirname) {
251 11
            $dirname = $this->toAbsolutePath($dirname);
252
        }
253
254 11
        return new Configuration\Backup\Target($dirname, $filename, $compress);
255
    }
256
257
    /**
258
     * Set backup checks.
259
     *
260
     * @param \phpbu\App\Configuration\Backup $backup
261
     * @param \DOMElement                     $node
262
     */
263 11
    protected function setChecks(Configuration\Backup $backup, DOMElement $node)
264
    {
265
        /** @var DOMElement $checkNode */
266 11
        foreach ($node->getElementsByTagName('check') as $checkNode) {
267 10
            $type  = $checkNode->getAttribute('type');
268 10
            $value = $checkNode->getAttribute('value');
269
            // skip invalid sanity checks
270 10
            if (!$type || !$value) {
271 1
                continue;
272
            }
273 9
            $backup->addCheck(new Configuration\Backup\Check($type, $value));
274
        }
275 11
    }
276
277
    /**
278
     * Set the crypt configuration.
279
     *
280
     * @param  \phpbu\App\Configuration\Backup $backup
281
     * @param  \DOMElement                     $node
282
     * @throws \phpbu\App\Exception
283
     */
284 11
    protected function setCrypt(Configuration\Backup $backup, DOMElement $node)
285
    {
286
        /** @var \DOMNodeList $cryptNodes */
287 11
        $cryptNodes = $node->getElementsByTagName('crypt');
288 11
        if ($cryptNodes->length > 0) {
289
            /** @var \DOMElement $cryptNode */
290 8
            $cryptNode = $cryptNodes->item(0);
291 8
            $type = $cryptNode->getAttribute('type');
292 8
            if (!$type) {
293 1
                throw new Exception('invalid crypt configuration: attribute type missing');
294
            }
295 7
            $skip    = Str::toBoolean($cryptNode->getAttribute('skipOnFailure'), true);
296 7
            $options = $this->getOptions($cryptNode);
297 7
            $backup->setCrypt(new Configuration\Backup\Crypt($type, $skip, $options));
298
        }
299 10
    }
300
301
    /**
302
     * Set backup sync configurations.
303
     *
304
     * @param  \phpbu\App\Configuration\Backup $backup
305
     * @param  \DOMElement                     $node
306
     * @throws \phpbu\App\Exception
307
     */
308 10
    protected function setSyncs(Configuration\Backup $backup, DOMElement $node)
309
    {
310
        /** @var DOMElement $syncNode */
311 10
        foreach ($node->getElementsByTagName('sync') as $syncNode) {
312 9
            $type = $syncNode->getAttribute('type');
313 9
            if (!$type) {
314 1
                throw new Exception('invalid sync configuration: attribute type missing');
315
            }
316 8
            $skip    = Str::toBoolean($syncNode->getAttribute('skipOnFailure'), true);
317 8
            $options = $this->getOptions($syncNode);
318 7
            $backup->addSync(new Configuration\Backup\Sync($type, $skip, $options));
319
        }
320 8
    }
321
322
    /**
323
     * Set the cleanup configuration.
324
     *
325
     * @param  \phpbu\App\Configuration\Backup $backup
326
     * @param  \DOMElement                     $node
327
     * @throws \phpbu\App\Exception
328
     */
329 8
    protected function setCleanup(Configuration\Backup $backup, DOMElement $node)
330
    {
331
        /** @var \DOMNodeList $cleanupNodes */
332 8
        $cleanupNodes = $node->getElementsByTagName('cleanup');
333 8
        if ($cleanupNodes->length > 0) {
334
            /** @var \DOMElement $cleanupNode */
335 7
            $cleanupNode = $cleanupNodes->item(0);
336 7
            $type        = $cleanupNode->getAttribute('type');
337 7
            if (!$type) {
338 1
                throw new Exception('invalid cleanup configuration: attribute type missing');
339
            }
340 6
            $skip    = Str::toBoolean($cleanupNode->getAttribute('skipOnFailure'), true);
341 6
            $options = $this->getOptions($cleanupNode);
342 6
            $backup->setCleanup(new Configuration\Backup\Cleanup($type, $skip, $options));
343
        }
344 7
    }
345
346
    /**
347
     * Extracts all option tags.
348
     *
349
     * @param  \DOMElement $node
350
     * @return array
351
     * @throws \phpbu\App\Exception
352
     */
353 15
    protected function getOptions(DOMElement $node)
354
    {
355 15
        $options = [];
356
        /** @var \DOMElement $optionNode */
357 15
        foreach ($node->getElementsByTagName('option') as $optionNode) {
358 15
            $name           = $optionNode->getAttribute('name');
359 15
            $value          = $this->getAdapterizedValue($optionNode->getAttribute('value'));
360 15
            $options[$name] = $value;
361
        }
362 15
        return $options;
363
    }
364
365
    /**
366
     * Load the XML-File.
367
     *
368
     * @param  string $filename
369
     * @throws \phpbu\App\Exception
370
     * @return \DOMDocument
371
     */
372 19
    private function loadXmlFile($filename)
373
    {
374 19
        $contents  = $this->loadFile($filename);
375 18
        $document  = new \DOMDocument;
376 18
        $message   = '';
377 18
        $internal  = libxml_use_internal_errors(true);
378 18
        $reporting = error_reporting(0);
379
380 18
        $document->documentURI = $filename;
381 18
        $loaded                = $document->loadXML($contents);
382
383 18
        foreach (libxml_get_errors() as $error) {
384 1
            $message .= "\n" . $error->message;
385
        }
386
387 18
        libxml_use_internal_errors($internal);
388 18
        error_reporting($reporting);
389
390 18
        if ($loaded === false || $message !== '') {
391 1
            throw new Exception(
392 1
                sprintf(
393 1
                    'Error loading file "%s".%s',
394
                    $filename,
395 1
                    $message != '' ? "\n" . $message : ''
396
                )
397
            );
398
        }
399 17
        return $document;
400
    }
401
402
    /**
403
     * Validate xml configuration against phpbu.xsd schema
404
     *
405
     * @return void
406
     */
407 17
    private function validateConfigurationAgainstSchema()
408
    {
409 17
        $original    = \libxml_use_internal_errors(true);
410 17
        $xsdFilename = __DIR__ . '/../../../phpbu.xsd';
411 17
        if (\defined('__PHPBU_PHAR_ROOT__')) {
412
            $xsdFilename = __PHPBU_PHAR_ROOT__ . '/phpbu.xsd';
0 ignored issues
show
Bug introduced by
The constant __PHPBU_PHAR_ROOT__ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
413
        }
414 17
        $this->document->schemaValidate($xsdFilename);
415 17
        foreach (\libxml_get_errors() as $error) {
416 7
            if (!isset($this->errors[$error->line])) {
417 7
                $this->errors[$error->line] = [];
418
            }
419 7
            $this->errors[$error->line][] = \trim($error->message);
420
        }
421 17
        \libxml_clear_errors();
422 17
        \libxml_use_internal_errors($original);
423 17
    }
424
}
425