Passed
Push — master ( b92dc0...8014e2 )
by Francesc
01:42
created

OrderXmlTables::checkStructure()   C

Complexity

Conditions 8
Paths 14

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 18
nc 14
nop 2
dl 0
loc 24
rs 5.7377
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of FSConsoleTools
4
 * Copyright (C) 2018 Francesc Pineda Segarra <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace FacturaScriptsUtils\Console\Command;
21
22
use DOMDocument;
23
use FacturaScriptsUtils\Console\ConsoleAbstract;
24
use SimpleXMLElement;
25
26
/**
27
 * Class OrderXmlTable
28
 *
29
 * @author Francesc Pineda Segarra <[email protected]>
30
 */
31
class OrderXmlTables extends ConsoleAbstract
32
{
33
    /**
34
     * Constant values for return, to easy know how execution ends.
35
     */
36
    const RETURN_SUCCESS = 0;
37
    const RETURN_SRC_FOLDER_NOT_SET = 1;
38
    const RETURN_DST_FOLDER_NOT_SET = 2;
39
    const RETURN_TAG_NAME_NOT_SET = 3;
40
    const RETURN_CANT_CREATE_FOLDER = 4;
41
    const RETURN_FAIL_SAVING_FILE = 5;
42
    const RETURN_NO_FILES = 6;
43
    const RETURN_SRC_FOLDER_NOT_EXISTS = 7;
44
    const RETURN_INCORRECT_FILE = 8;
45
46
    /**
47
     * Folder source path.
48
     *
49
     * @var string
50
     */
51
    private $folderSrcPath;
52
53
    /**
54
     * Folder destiny path.
55
     *
56
     * @var string
57
     */
58
    private $folderDstPath;
59
60
    /**
61
     * Tagname used for order.
62
     *
63
     * @var string
64
     */
65
    private $tagName;
66
67
    /**
68
     * Set default source folder.
69
     *
70
     * @param string $folderSrcPath
71
     *
72
     * @return $this
73
     */
74
    public function setFolderSrcPath(string $folderSrcPath): self
75
    {
76
        $this->folderSrcPath = $folderSrcPath;
77
        return $this;
78
    }
79
80
    /**
81
     * Set default destiny folder.
82
     *
83
     * @param string $folderDstPath
84
     *
85
     * @return $this
86
     */
87
    public function setFolderDstPath(string $folderDstPath): self
88
    {
89
        $this->folderDstPath = $folderDstPath;
90
        return $this;
91
    }
92
93
    /**
94
     * Set tag name.
95
     *
96
     * @param string $tagName
97
     *
98
     * @return $this
99
     */
100
    public function setTagName(string $tagName): self
101
    {
102
        $this->tagName = $tagName;
103
        return $this;
104
    }
105
106
    /**
107
     * Run the OrderXmlTable script.
108
     *
109
     * @param array $params
110
     *
111
     * @return int
112
     * @throws \Exception
113
     */
114
    public function run(...$params): int
115
    {
116
        $status = $this->checkOptions($params);
117
        if ($status < 0) {
118
            return $status;
119
        }
120
121
        $this->setFolderSrcPath($params[0] ?? \FS_FOLDER . 'Core/Table/');
122
        $this->setFolderDstPath($params[1] ?? \FS_FOLDER . 'Core/Table/');
123
        $this->setTagName($params[2] ?? 'name');
124
125
        echo 'Options setted:' . \PHP_EOL;
126
        echo '   Source path: ' . $this->folderSrcPath . \PHP_EOL;
127
        echo '   Destiny path: ' . $this->folderDstPath . \PHP_EOL;
128
        echo '   Tag name: ' . $this->tagName . \PHP_EOL;
129
        if (!$this->areYouSure()) {
130
            echo '   Options [SRC] [DST] [TAG]' . \PHP_EOL;
131
            return self::RETURN_SUCCESS;
132
        }
133
134
        $status = $this->check();
135
        if ($status > 0) {
136
            return $status;
137
        }
138
139
        $files = array_diff(scandir($this->folderSrcPath, SCANDIR_SORT_ASCENDING), ['.', '..']);
140
141
        if (\count($files) === 0) {
142
            echo 'ERROR: No files on folder' . \PHP_EOL;
143
            return self::RETURN_NO_FILES;
144
        }
145
146
        return $this->orderXml($files);
147
    }
148
149
    /**
150
     * Return description about this class.
151
     *
152
     * @return string
153
     */
154
    public function getDescription(): string
155
    {
156
        return 'Order XML content files by tag name.';
157
    }
158
159
    /**
160
     * Print help information to the user.
161
     */
162
    public function showHelp()
163
    {
164
        $array = \explode('\\', __CLASS__);
165
        $class = array_pop($array);
166
        echo 'Use as: php vendor/bin/console ' . $class . ' [OPTIONS]' . \PHP_EOL;
167
        echo 'Available options:' . \PHP_EOL;
168
        echo '   -h, --help        Show this help.' . \PHP_EOL;
169
        echo \PHP_EOL;
170
        echo '   OPTION1           Source path' . \PHP_EOL;
171
        echo '   OPTION2           Destiny path' . \PHP_EOL;
172
        echo '   OPTION3           Tag name' . \PHP_EOL;
173
        echo \PHP_EOL;
174
    }
175
176
    /**
177
     * Returns an associative array of available methods for the user.
178
     * Add more options if you want to add support for custom methods.
179
     *      [
180
     *          '-h'        => 'showHelp',
181
     *          '--help'    => 'showHelp',
182
     *      ]
183
     *
184
     * @return array
185
     */
186
    public function getUserMethods(): array
187
    {
188
        return [
189
            '-h' => 'showHelp',
190
            '--help' => 'showHelp'
191
        ];
192
    }
193
194
    /**
195
     * Order Xml files
196
     *
197
     * @param array $files
198
     *
199
     * @return int
200
     * @throws \Exception
201
     */
202
    private function orderXml(array $files): int
203
    {
204
        try {
205
            foreach ($files as $fileName) {
206
                $xml = simplexml_load_string(file_get_contents($this->folderSrcPath . $fileName));
207
                // Get all children of table into an array
208
                $table = (array) $xml->children();
209
                $this->checkStructure($fileName, $table);
210
                $dom = new DOMDocument();
211
                $dom->loadXML($this->generateXML($fileName, $table));
212
                $filePath = $this->folderDstPath . '/' . $fileName;
213
                if ($dom->save($filePath) === false) {
214
                    echo "ERROR: Can't save file " . $filePath . \PHP_EOL;
215
                    return self::RETURN_FAIL_SAVING_FILE;
216
                }
217
            }
218
        } catch (\Exception $e) {
219
            echo $e->getMessage(), \PHP_EOL;
220
            return self::RETURN_INCORRECT_FILE;
221
        }
222
        echo 'Finished! Look at "' . $this->folderDstPath . '"' . \PHP_EOL;
223
        return self::RETURN_SUCCESS;
224
    }
225
226
    /**
227
     * Check if basic structure is correct
228
     *
229
     * @param string $fileName
230
     * @param array  $table
231
     *
232
     * @throws \Exception
233
     */
234
    private function checkStructure(string $fileName, array $table)
235
    {
236
        if (!isset($table['column'])) {
237
            throw new \RuntimeException(
238
                'File is incorrect ' . $this->folderSrcPath . $fileName . \PHP_EOL
239
                . 'File does not contain any column.' . \PHP_EOL
240
                . 'Execution stopped'
241
            );
242
        }
243
        foreach ($table as $key => $row) {
244
            $item = isset($key) ? 'column' : null;
245
            $item = $item ?? (isset($key) ? 'constraint' : null);
246
            if ($item === null) {
247
                throw new \RuntimeException(
248
                    'File is incorrect ' . $this->folderSrcPath . $fileName . \PHP_EOL
249
                    . 'Unexpected data ' . print_r($key, true) . ' => ' . print_r($row, true) . '.'
250
                    . 'Execution stopped'
251
                );
252
            }
253
            if (isset($row[$item]) && !isset($row[$item]['name'], $row[$item]['type'])) {
254
                throw new \RuntimeException(
255
                    'File is incorrect ' . $this->folderSrcPath . $fileName . \PHP_EOL
256
                    . \ucfirst($item) . ' ' . print_r($row[$item], true) .  ' does not contain name or type.'
257
                    . 'Execution stopped'
258
                );
259
            }
260
        }
261
    }
262
263
    /**
264
     * Return the final content of the XML file.
265
     *
266
     * @param string $fileName
267
     * @param array  $table
268
     *
269
     * @return string
270
     */
271
    private function generateXML(string $fileName, array $table): string
272
    {
273
        $columns = $table['column'];
274
        // Call usort on the array
275
        if (!\is_object($columns)) {
276
            usort($columns, [$this, 'sortName']);
277
        }
278
        // Generate string XML result
279
        $strXML = $this->generateXMLHeader($fileName);
280
        $strXML .= '<table>' . PHP_EOL;
281
        $strXML .= $this->generateXMLContent($columns);
282
        if (isset($table['constraint'])) {
283
            $constraints = $table['constraint'];
284
            if (!\is_object($constraints)) {
285
                usort($constraints, [$this, 'sortName']);
286
            }
287
            $strXML .= $this->generateXMLContent($constraints, 'constraint');
288
        }
289
        $strXML .= '</table>';
290
        return $strXML;
291
    }
292
293
    /**
294
     * Check if options are looking for help.
295
     *
296
     * @param array $params
297
     *
298
     * @return int
299
     */
300
    private function checkOptions(array $params = []): int
301
    {
302
        if (isset($params[0])) {
303
            switch ($params[0]) {
304
                case '-h':
305
                case '--help':
306
                    $this->showHelp();
307
                    return -1;
308
            }
309
        }
310
        return 0;
311
    }
312
313
    /**
314
     * Launch basic checks.
315
     *
316
     * @return int
317
     */
318
    private function check(): int
319
    {
320
        if ($this->folderSrcPath === null) {
321
            echo 'ERROR: Source folder not setted.' . \PHP_EOL;
322
            return self::RETURN_SRC_FOLDER_NOT_SET;
323
        }
324
        if ($this->folderDstPath === null) {
325
            echo 'ERROR: Destiny folder not setted.' . \PHP_EOL;
326
            return self::RETURN_DST_FOLDER_NOT_SET;
327
        }
328
        if ($this->tagName === null) {
329
            echo 'ERROR: Tag name not setted.' . \PHP_EOL;
330
            return self::RETURN_TAG_NAME_NOT_SET;
331
        }
332
        if (!is_dir($this->folderSrcPath)) {
333
            echo 'ERROR: Source folder ' . $this->folderSrcPath . ' not exists.' . \PHP_EOL;
334
            return self::RETURN_SRC_FOLDER_NOT_EXISTS;
335
        }
336
        if (!is_file($this->folderDstPath) && !@mkdir($this->folderDstPath) && !is_dir($this->folderDstPath)) {
337
            echo "ERROR: Can't create folder " . $this->folderDstPath;
338
            return self::RETURN_CANT_CREATE_FOLDER;
339
        }
340
        return self::RETURN_SUCCESS;
341
    }
342
343
    /**
344
     * Custom function to re-order with usort.
345
     *
346
     * @param SimpleXMLElement $xmlA
347
     * @param SimpleXMLElement $xmlB
348
     *
349
     * @return int
350
     */
351
    private function sortName(SimpleXMLElement $xmlA, SimpleXMLElement $xmlB): int
352
    {
353
        return strcasecmp($xmlA->{$this->tagName}, $xmlB->{$this->tagName});
354
    }
355
356
    /**
357
     * Generate the content of the XML.
358
     *
359
     * @param array|object $data
360
     * @param string       $tagName
361
     *
362
     * @return string
363
     */
364
    private function generateXMLContent($data, $tagName = 'column'): string
365
    {
366
        $str = '';
367
        if (\is_array($data)) {
368
            foreach ($data as $field) {
369
                $str .= '    <' . $tagName . '>' . PHP_EOL;
370
                foreach ((array) $field as $key => $value) {
371
                    $str .= '        <' . $key . '>' . $value . '</' . $key . '>' . PHP_EOL;
372
                }
373
                $str .= '    </' . $tagName . '>' . PHP_EOL;
374
            }
375
        }
376
        if (\is_object($data)) {
377
            $str .= '    <' . $tagName . '>' . PHP_EOL;
378
            foreach ((array) $data as $key => $value) {
379
                $str .= '        <' . $key . '>' . $value . '</' . $key . '>' . PHP_EOL;
380
            }
381
            $str .= '    </' . $tagName . '>' . PHP_EOL;
382
        }
383
384
        return $str;
385
    }
386
387
    /**
388
     * Generate the header of the XML.
389
     *
390
     * @param string $fileName
391
     *
392
     * @return string
393
     */
394
    private function generateXMLHeader(string $fileName): string
395
    {
396
        $str = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
397
        $str .= '<!--' . PHP_EOL;
398
        $str .= '    Document   : ' . $fileName . PHP_EOL;
399
        $str .= '    Author     : Ordered with OrderXmlTables from FSConsoleTools' . PHP_EOL;
400
        $str .= '    Description: Structure for the ' . str_replace('.xml', '', $fileName) . ' table.' . PHP_EOL;
401
        $str .= '-->' . PHP_EOL;
402
        return $str;
403
    }
404
}
405