Completed
Push — master ( adc690...696e0c )
by Sebastian
04:42
created

Xml::loadXmlFile()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5

Importance

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