Completed
Push — master ( 5d3bf9...5839aa )
by Sebastian
03:18
created

Xml::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 1
cts 1
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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
    public function __construct($file)
91
    {
92
        parent::__construct($file);
93
        $this->document = $this->loadXmlFile($file);
94
        $this->xpath    = new DOMXPath($this->document);
95 15
    }
96
97 15
    /**
98 15
     * Return list of adapter configs.
99 13
     *
100 13
     * @return array
101
     * @throws \phpbu\App\Exception
102
     */
103
    protected function getAdapterConfigs()
104
    {
105
        $adapters = [];
106
        /** @var \DOMElement $adapterNode */
107 13
        foreach ($this->xpath->query('adapters/adapter') as $adapterNode) {
108
            $type    = $adapterNode->getAttribute('type');
109 13
            $name    = $adapterNode->getAttribute('name');
110
            $options = $this->getOptions($adapterNode);
111 13
            if (!$type) {
112 13
                throw new Exception('invalid adapter configuration: attribute type missing');
113 13
            }
114 13
            if (!$name) {
115 13
                throw new Exception('invalid adapter configuration: attribute name missing');
116 13
            }
117 13
            $adapters[] = new Configuration\Adapter($type, $name, $options);
118 13
        }
119 13
        return $adapters;
120 13
    }
121
122
    /**
123
     * Set the phpbu application settings.
124
     *
125
     * @param  \phpbu\App\Configuration $configuration
126
     */
127
    public function setAppSettings(Configuration $configuration)
128 13
    {
129
        $root = $this->document->documentElement;
130 13
131 11
        if ($root->hasAttribute('bootstrap')) {
132 11
            $configuration->setBootstrap($this->toAbsolutePath($root->getAttribute('bootstrap')));
133 11
        }
134 11
        if ($root->hasAttribute('verbose')) {
135 13
            $configuration->setVerbose(Str::toBoolean($root->getAttribute('verbose'), false));
136 13
        }
137
        if ($root->hasAttribute('colors')) {
138 11
            $configuration->setColors(Str::toBoolean($root->getAttribute('colors'), false));
139 11
        }
140
    }
141 11
142 13
    /**
143 13
     * Set the log configuration.
144
     *
145
     * @param  \phpbu\App\Configuration $configuration
146
     * @throws \phpbu\App\Exception
147
     */
148
    public function setLoggers(Configuration $configuration)
149
    {
150
        /** @var \DOMElement $logNode */
151 13
        foreach ($this->xpath->query('logging/log') as $logNode) {
152
            $type = $logNode->getAttribute('type');
153
            if (!$type) {
154 13
                throw new Exception('invalid logger configuration: attribute type missing');
155 11
            }
156 11
            $options = $this->getOptions($logNode);
157 1
            if (isset($options['target'])) {
158
                $options['target'] = $this->toAbsolutePath($options['target']);
159 10
            }
160 10
            // search for target attribute to convert to option
161 1
            $target = $logNode->getAttribute('target');
162 1
            if (!empty($target)) {
163
                $options['target'] = $this->toAbsolutePath($target);
164 10
            }
165 10
            $configuration->addLogger(new Configuration\Logger($type, $options));
166 9
        }
167 9
    }
168 10
169 12
    /**
170 12
     * Set the backup configurations.
171
     *
172
     * @param  \phpbu\App\Configuration $configuration
173
     * @throws \phpbu\App\Exception
174
     */
175
    public function setBackups(Configuration $configuration)
176
    {
177
        foreach ($this->xpath->query('backups/backup') as $backupNode) {
178 12
            $configuration->addBackup($this->getBackupConfig($backupNode));
179
        }
180 12
    }
181 12
182 6
    /**
183 6
     * 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 View Code Duplication
    private function getBackupConfig(DOMElement $backupNode)
190
    {
191
        $stopOnFailure = Str::toBoolean($backupNode->getAttribute('stopOnFailure'), false);
192 12
        $backupName    = $backupNode->getAttribute('name');
193
        $backup        = new Configuration\Backup($backupName, $stopOnFailure);
194 12
195 12
        $backup->setSource($this->getSource($backupNode));
196 12
        $backup->setTarget($this->getTarget($backupNode));
197
198 12
        $this->setChecks($backup, $backupNode);
199 10
        $this->setCrypt($backup, $backupNode);
200
        $this->setSyncs($backup, $backupNode);
201 9
        $this->setCleanup($backup, $backupNode);
202 9
203 8
        return $backup;
204 7
    }
205
206 6
    /**
207
     * Get source configuration.
208
     *
209
     * @param  \DOMElement $node
210
     * @return \phpbu\App\Configuration\Backup\Source
211
     * @throws \phpbu\App\Exception
212
     */
213
    protected function getSource(DOMElement $node)
214
    {
215
        $sources = $node->getElementsByTagName('source');
216 12
        if ($sources->length !== 1) {
217
            throw new Exception('backup requires exactly one source config');
218 12
        }
219 12
        /** @var DOMElement $sourceNode */
220 1
        $sourceNode = $sources->item(0);
221
        $type       = $sourceNode->getAttribute('type');
222
        if (!$type) {
223 11
            throw new Exception('source requires type attribute');
224 11
        }
225 11
226 1
        return new Configuration\Backup\Source($type, $this->getOptions($sourceNode));
227
    }
228
229 10
    /**
230
     * Get Target configuration.
231
     *
232
     * @param  \DOMElement $node
233
     * @return \phpbu\App\Configuration\Backup\Target
234
     * @throws \phpbu\App\Exception
235
     */
236
    protected function getTarget(DOMElement $node)
237
    {
238
        $targets = $node->getElementsByTagName('target');
239 10
        if ($targets->length !== 1) {
240
            throw new Exception('backup requires exactly one target config');
241 10
        }
242 10
        /** @var DOMElement $targetNode */
243 1
        $targetNode = $targets->item(0);
244
        $compress   = $targetNode->getAttribute('compress');
245
        $filename   = $targetNode->getAttribute('filename');
246 9
        $dirname    = $targetNode->getAttribute('dirname');
247 9
248 9
        if ($dirname) {
249 9
            $dirname = $this->toAbsolutePath($dirname);
250
        }
251 9
252 9
        return new Configuration\Backup\Target($dirname, $filename, $compress);
253 9
    }
254
255 9
    /**
256
     * Set backup checks.
257
     *
258
     * @param \phpbu\App\Configuration\Backup $backup
259
     * @param \DOMElement                     $node
260
     */
261
    protected function setChecks(Configuration\Backup $backup, DOMElement $node)
262
    {
263
        /** @var DOMElement $checkNode */
264 9
        foreach ($node->getElementsByTagName('check') as $checkNode) {
265
            $type  = $checkNode->getAttribute('type');
266
            $value = $checkNode->getAttribute('value');
267 9
            // skip invalid sanity checks
268 9
            if (!$type || !$value) {
269 9
                continue;
270
            }
271 9
            $backup->addCheck(new Configuration\Backup\Check($type, $value));
272 1
        }
273
    }
274 8
275 9
    /**
276 9
     * Set the crypt configuration.
277
     *
278
     * @param  \phpbu\App\Configuration\Backup $backup
279
     * @param  \DOMElement                     $node
280
     * @throws \phpbu\App\Exception
281
     */
282 View Code Duplication
    protected function setCrypt(Configuration\Backup $backup, DOMElement $node)
283
    {
284
        /** @var \DOMNodeList $cryptNodes */
285 9
        $cryptNodes = $node->getElementsByTagName('crypt');
286
        if ($cryptNodes->length > 0) {
287
            /** @var \DOMElement $cryptNode */
288 9
            $cryptNode = $cryptNodes->item(0);
289 9
            $type = $cryptNode->getAttribute('type');
290
            if (!$type) {
291 7
                throw new Exception('invalid crypt configuration: attribute type missing');
292 7
            }
293 7
            $skip    = Str::toBoolean($cryptNode->getAttribute('skipOnFailure'), true);
294 1
            $options = $this->getOptions($cryptNode);
295
            $backup->setCrypt(new Configuration\Backup\Crypt($type, $skip, $options));
296 6
        }
297 6
    }
298 6
299 6
    /**
300 8
     * Set backup sync configurations.
301
     *
302
     * @param  \phpbu\App\Configuration\Backup $backup
303
     * @param  \DOMElement                     $node
304
     * @throws \phpbu\App\Exception
305
     */
306
    protected function setSyncs(Configuration\Backup $backup, DOMElement $node)
307
    {
308
        /** @var DOMElement $syncNode */
309 8
        foreach ($node->getElementsByTagName('sync') as $syncNode) {
310
            $type = $syncNode->getAttribute('type');
311
            if (!$type) {
312 8
                throw new Exception('invalid sync configuration: attribute type missing');
313 8
            }
314 8
            $skip    = Str::toBoolean($syncNode->getAttribute('skipOnFailure'), true);
315 1
            $options = $this->getOptions($syncNode);
316
            $backup->addSync(new Configuration\Backup\Sync($type, $skip, $options));
317 7
        }
318 7
    }
319 7
320 7
    /**
321 7
     * Set the cleanup configuration.
322
     *
323
     * @param  \phpbu\App\Configuration\Backup $backup
324
     * @param  \DOMElement                     $node
325
     * @throws \phpbu\App\Exception
326
     */
327 View Code Duplication
    protected function setCleanup(Configuration\Backup $backup, DOMElement $node)
328
    {
329
        /** @var \DOMNodeList $cleanupNodes */
330 7
        $cleanupNodes = $node->getElementsByTagName('cleanup');
331
        if ($cleanupNodes->length > 0) {
332
            /** @var \DOMElement $cleanupNode */
333 7
            $cleanupNode = $cleanupNodes->item(0);
334 7
            $type        = $cleanupNode->getAttribute('type');
335
            if (!$type) {
336 7
                throw new Exception('invalid cleanup configuration: attribute type missing');
337 7
            }
338 7
            $skip    = Str::toBoolean($cleanupNode->getAttribute('skipOnFailure'), true);
339 1
            $options = $this->getOptions($cleanupNode);
340
            $backup->setCleanup(new Configuration\Backup\Cleanup($type, $skip, $options));
341 6
        }
342 6
    }
343 6
344 6
    /**
345 6
     * Extracts all option tags.
346
     *
347
     * @param  DOMElement $node
348
     * @return array
349
     */
350
    protected function getOptions(DOMElement $node)
351
    {
352
        $options = [];
353 11
        /** @var \DOMElement $optionNode */
354
        foreach ($node->getElementsByTagName('option') as $optionNode) {
355 11
            $name           = $optionNode->getAttribute('name');
356
            $value          = $this->getOptionValue($optionNode->getAttribute('value'));
357 11
            $options[$name] = $value;
358 11
        }
359 11
        return $options;
360 11
    }
361 11
362 11
    /**
363
     * Load the XML-File.
364
     *
365
     * @param  string $filename
366
     * @throws \phpbu\App\Exception
367
     * @return \DOMDocument
368
     */
369
    private function loadXmlFile($filename)
370
    {
371
        $contents  = $this->loadFile($filename);
372 15
        $document  = new \DOMDocument;
373
        $message   = '';
374 15
        $internal  = libxml_use_internal_errors(true);
375 14
        $reporting = error_reporting(0);
376 14
377 14
        $document->documentURI = $filename;
378 14
        $loaded                = $document->loadXML($contents);
379
380 14
        foreach (libxml_get_errors() as $error) {
381 14
            $message .= "\n" . $error->message;
382
        }
383 14
384 1
        libxml_use_internal_errors($internal);
385 14
        error_reporting($reporting);
386
387 14
        if ($loaded === false || $message !== '') {
388 14
            throw new Exception(
389
                sprintf(
390 14
                    'Error loading file "%s".%s',
391 1
                    $filename,
392 1
                    $message != '' ? "\n" . $message : ''
393 1
                )
394 1
            );
395 1
        }
396 1
        return $document;
397 1
    }
398
}
399