OrderXmlTables   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 146
dl 0
loc 376
rs 8.64
c 0
b 0
f 0
wmc 47

15 Methods

Rating   Name   Duplication   Size   Complexity  
A setTagName() 0 4 1
A setSrcFolder() 0 4 1
A setDstFolder() 0 4 1
A generateXMLContent() 0 21 6
A checkOptions() 0 11 4
A getUserMethods() 0 3 1
B checkStructure() 0 24 8
B check() 0 23 8
A getHelpMsg() 0 12 1
A orderXml() 0 22 4
A getDescription() 0 3 1
A sortName() 0 3 1
A generateXMLHeader() 0 9 1
A run() 0 38 5
A generateXML() 0 20 4

How to fix   Complexity   

Complex Class

Complex classes like OrderXmlTables often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrderXmlTables, and based on these observations, apply Extract Interface, too.

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 FacturaScripts\Core\Base\FileManager;
0 ignored issues
show
Bug introduced by
The type FacturaScripts\Core\Base\FileManager was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
use FacturaScriptsUtils\Console\ConsoleAbstract;
25
use SimpleXMLElement;
26
27
/**
28
 * Class OrderXmlTable
29
 *
30
 * @author Francesc Pineda Segarra <[email protected]>
31
 */
32
class OrderXmlTables extends ConsoleAbstract
33
{
34
    /**
35
     * Constant values for return, to easy know how execution ends.
36
     */
37
    const RETURN_SUCCESS = 0;
38
    const RETURN_SRC_FOLDER_NOT_SET = 1;
39
    const RETURN_DST_FOLDER_NOT_SET = 2;
40
    const RETURN_TAG_NAME_NOT_SET = 3;
41
    const RETURN_CANT_CREATE_FOLDER = 4;
42
    const RETURN_FAIL_SAVING_FILE = 5;
43
    const RETURN_NO_FILES = 6;
44
    const RETURN_SRC_FOLDER_NOT_EXISTS = 7;
45
    const RETURN_INCORRECT_FILE = 8;
46
47
    /**
48
     * Folder source path.
49
     *
50
     * @var string
51
     */
52
    private $srcFolder;
53
54
    /**
55
     * Folder destiny path.
56
     *
57
     * @var string
58
     */
59
    private $dstFolder;
60
61
    /**
62
     * Tagname used for order.
63
     *
64
     * @var string
65
     */
66
    private $tagName;
67
68
    /**
69
     * Set default source folder.
70
     *
71
     * @param string $srcFolder
72
     *
73
     * @return $this
74
     */
75
    public function setSrcFolder(string $srcFolder): self
76
    {
77
        $this->srcFolder = $srcFolder;
78
        return $this;
79
    }
80
81
    /**
82
     * Set default destiny folder.
83
     *
84
     * @param string $dstFolder
85
     *
86
     * @return $this
87
     */
88
    public function setDstFolder(string $dstFolder): self
89
    {
90
        $this->dstFolder = $dstFolder;
91
        return $this;
92
    }
93
94
    /**
95
     * Set tag name.
96
     *
97
     * @param string $tagName
98
     *
99
     * @return $this
100
     */
101
    public function setTagName(string $tagName): self
102
    {
103
        $this->tagName = $tagName;
104
        return $this;
105
    }
106
107
    /**
108
     * Run the OrderXmlTable script.
109
     *
110
     * @param array $params
111
     *
112
     * @return int
113
     * @throws \Exception
114
     */
115
    public function run(...$params): int
116
    {
117
        $this->autoReply = (bool) $params[2] ?? false;
118
        $this->autoHide = (bool) $params[3] ?? false;
119
120
        $status = $this->checkOptions($params);
121
        if ($status !== 0) {
122
            return $status;
123
        }
124
125
        $this->setSrcFolder(\FS_FOLDER . ($params[0] ?? 'Core/Table/'));
126
        $this->setDstFolder(\FS_FOLDER . ($params[1] ?? 'Core/Table/'));
127
        $this->setTagName($params[2] ?? 'name');
128
129
        $this->showMessage('Ordering XML table content' . \PHP_EOL . \PHP_EOL);
130
        $this->showMessage('   Options setted:' . \PHP_EOL);
131
        $this->showMessage('      Source path: ' . $this->srcFolder . \PHP_EOL);
132
        $this->showMessage('      Destiny path: ' . $this->dstFolder . \PHP_EOL);
133
        $this->showMessage('      Tag name: ' . $this->tagName . \PHP_EOL);
134
135
        if (!$this->areYouSure($this->autoReply)) {
136
            $this->showMessage('   Options [SRC] [DST] [TAG]' . \PHP_EOL);
137
            return self::RETURN_SUCCESS;
138
        }
139
140
        $status = $this->check();
141
        if ($status !== 0) {
142
            return $status;
143
        }
144
145
        $files = FileManager::scanFolder($this->srcFolder);
146
147
        if (\count($files) === 0) {
148
            trigger_error('ERROR: No files on folder' . \PHP_EOL . \PHP_EOL);
149
            return self::RETURN_NO_FILES;
150
        }
151
152
        return $this->orderXml($files);
153
    }
154
155
    /**
156
     * Return description about this class.
157
     *
158
     * @return string
159
     */
160
    public function getDescription(): string
161
    {
162
        return 'Order XML content files by tag name.';
163
    }
164
165
    /**
166
     * Print help information to the user.
167
     *
168
     * @return string
169
     */
170
    public function getHelpMsg(): string
171
    {
172
        $array = \explode('\\', __CLASS__);
173
        $class = array_pop($array);
174
        return 'Use as: php vendor/bin/console ' . $class . ' [OPTIONS]' . \PHP_EOL
175
            . 'Available options:' . \PHP_EOL
176
            . '   -h, --help        Show this help.' . \PHP_EOL
177
            . \PHP_EOL
178
            . '   OPTION1           Source path' . \PHP_EOL
179
            . '   OPTION2           Destiny path' . \PHP_EOL
180
            . '   OPTION3           Tag name' . \PHP_EOL
181
            . \PHP_EOL;
182
    }
183
184
    /**
185
     * Returns an associative array of available methods for the user.
186
     * Add more options if you want to add support for custom methods.
187
     *      [
188
     *          '-h'        => 'getHelpMsg',
189
     *          '--help'    => 'getHelpMsg',
190
     *      ]
191
     *
192
     * @return array
193
     */
194
    public function getUserMethods(): array
195
    {
196
        return parent::getUserMethods();
197
    }
198
199
    /**
200
     * Order Xml files
201
     *
202
     * @param array $files
203
     *
204
     * @return int
205
     * @throws \Exception
206
     */
207
    private function orderXml(array $files): int
208
    {
209
        try {
210
            foreach ($files as $fileName) {
211
                $xml = simplexml_load_string(file_get_contents($this->srcFolder . $fileName));
212
                // Get all children of table into an array
213
                $table = (array) $xml->children();
214
                $this->checkStructure($fileName, $table);
215
                $dom = new DOMDocument();
216
                $dom->loadXML($this->generateXML($fileName, $table));
217
                $filePath = $this->dstFolder . '/' . $fileName;
218
                if ($dom->save($filePath) === false) {
219
                    trigger_error("ERROR: Can't save file " . $filePath . \PHP_EOL);
220
                    return self::RETURN_FAIL_SAVING_FILE;
221
                }
222
            }
223
        } catch (\Exception $e) {
224
            trigger_error($e->getMessage(), \PHP_EOL);
0 ignored issues
show
Bug introduced by
PHP_EOL of type string is incompatible with the type integer expected by parameter $error_type of trigger_error(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

224
            trigger_error($e->getMessage(), /** @scrutinizer ignore-type */ \PHP_EOL);
Loading history...
225
            return self::RETURN_INCORRECT_FILE;
226
        }
227
        $this->showMessage('Finished! Look at "' . $this->dstFolder . '"' . \PHP_EOL);
228
        return self::RETURN_SUCCESS;
229
    }
230
231
    /**
232
     * Check if basic structure is correct
233
     *
234
     * @param string $fileName
235
     * @param array  $table
236
     *
237
     * @throws \Exception
238
     */
239
    private function checkStructure(string $fileName, array $table)
240
    {
241
        if (!isset($table['column'])) {
242
            throw new \RuntimeException(
243
                'File is incorrect ' . $this->srcFolder . $fileName . \PHP_EOL
244
                . 'File does not contain any column.' . \PHP_EOL
245
                . 'Execution stopped'
246
            );
247
        }
248
        foreach ($table as $key => $row) {
249
            $item = isset($key) ? 'column' : null;
250
            $item = $item ?? (isset($key) ? 'constraint' : null);
251
            if ($item === null) {
252
                throw new \RuntimeException(
253
                    'File is incorrect ' . $this->srcFolder . $fileName . \PHP_EOL
254
                    . 'Unexpected data ' . print_r($key, true) . ' => ' . print_r($row, true) . '.'
255
                    . 'Execution stopped'
256
                );
257
            }
258
            if (isset($row[$item]) && !isset($row[$item]['name'], $row[$item]['type'])) {
259
                throw new \RuntimeException(
260
                    'File is incorrect ' . $this->srcFolder . $fileName . \PHP_EOL
261
                    . \ucfirst($item) . ' ' . print_r($row[$item], true) . ' does not contain name or type.'
262
                    . 'Execution stopped'
263
                );
264
            }
265
        }
266
    }
267
268
    /**
269
     * Return the final content of the XML file.
270
     *
271
     * @param string $fileName
272
     * @param array  $table
273
     *
274
     * @return string
275
     */
276
    private function generateXML(string $fileName, array $table): string
277
    {
278
        $columns = $table['column'];
279
        // Call usort on the array
280
        if (!\is_object($columns)) {
281
            usort($columns, [$this, 'sortName']);
282
        }
283
        // Generate string XML result
284
        $strXML = $this->generateXMLHeader($fileName);
285
        $strXML .= '<table>' . PHP_EOL;
286
        $strXML .= $this->generateXMLContent($columns);
287
        if (isset($table['constraint'])) {
288
            $constraints = $table['constraint'];
289
            if (!\is_object($constraints)) {
290
                usort($constraints, [$this, 'sortName']);
291
            }
292
            $strXML .= $this->generateXMLContent($constraints, 'constraint');
293
        }
294
        $strXML .= '</table>';
295
        return $strXML;
296
    }
297
298
    /**
299
     * Check if options are looking for help.
300
     *
301
     * @param array $params
302
     *
303
     * @return int
304
     */
305
    private function checkOptions(array $params = []): int
306
    {
307
        if (isset($params[0])) {
308
            switch ($params[0]) {
309
                case '-h':
310
                case '--help':
311
                    $this->showMessage($this->getHelpMsg());
312
                    return -1;
313
            }
314
        }
315
        return 0;
316
    }
317
318
    /**
319
     * Launch basic checks.
320
     *
321
     * @return int
322
     */
323
    private function check(): int
324
    {
325
        if ($this->srcFolder === null) {
326
            trigger_error('ERROR: Source folder not setted.' . \PHP_EOL . \PHP_EOL);
327
            return self::RETURN_SRC_FOLDER_NOT_SET;
328
        }
329
        if ($this->dstFolder === null) {
330
            trigger_error('ERROR: Destiny folder not setted.' . \PHP_EOL . \PHP_EOL);
331
            return self::RETURN_DST_FOLDER_NOT_SET;
332
        }
333
        if ($this->tagName === null) {
334
            trigger_error('ERROR: Tag name not setted.' . \PHP_EOL . \PHP_EOL);
335
            return self::RETURN_TAG_NAME_NOT_SET;
336
        }
337
        if (!is_dir($this->srcFolder)) {
338
            trigger_error('ERROR: Source folder ' . $this->srcFolder . ' not exists.' . \PHP_EOL . \PHP_EOL);
339
            return self::RETURN_SRC_FOLDER_NOT_EXISTS;
340
        }
341
        if (!is_file($this->dstFolder) && !@mkdir($this->dstFolder) && !is_dir($this->dstFolder)) {
342
            trigger_error("ERROR: Can't create folder " . $this->dstFolder . '.' . \PHP_EOL . \PHP_EOL);
343
            return self::RETURN_CANT_CREATE_FOLDER;
344
        }
345
        return self::RETURN_SUCCESS;
346
    }
347
348
    /**
349
     * Custom function to re-order with usort.
350
     *
351
     * @param SimpleXMLElement $xmlA
352
     * @param SimpleXMLElement $xmlB
353
     *
354
     * @return int
355
     */
356
    private function sortName(SimpleXMLElement $xmlA, SimpleXMLElement $xmlB): int
357
    {
358
        return strcasecmp($xmlA->{$this->tagName}, $xmlB->{$this->tagName});
359
    }
360
361
    /**
362
     * Generate the content of the XML.
363
     *
364
     * @param array|object $data
365
     * @param string       $tagName
366
     *
367
     * @return string
368
     */
369
    private function generateXMLContent($data, $tagName = 'column'): string
370
    {
371
        $str = '';
372
        if (\is_array($data)) {
373
            foreach ($data as $field) {
374
                $str .= '    <' . $tagName . '>' . PHP_EOL;
375
                foreach ((array) $field as $key => $value) {
376
                    $str .= '        <' . $key . '>' . $value . '</' . $key . '>' . PHP_EOL;
377
                }
378
                $str .= '    </' . $tagName . '>' . PHP_EOL;
379
            }
380
        }
381
        if (\is_object($data)) {
382
            $str .= '    <' . $tagName . '>' . PHP_EOL;
383
            foreach ((array) $data as $key => $value) {
384
                $str .= '        <' . $key . '>' . $value . '</' . $key . '>' . PHP_EOL;
385
            }
386
            $str .= '    </' . $tagName . '>' . PHP_EOL;
387
        }
388
389
        return $str;
390
    }
391
392
    /**
393
     * Generate the header of the XML.
394
     *
395
     * @param string $fileName
396
     *
397
     * @return string
398
     */
399
    private function generateXMLHeader(string $fileName): string
400
    {
401
        $str = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
402
        $str .= '<!--' . PHP_EOL;
403
        $str .= '    Document   : ' . $fileName . PHP_EOL;
404
        $str .= '    Author     : Ordered with OrderXmlTables from FSConsoleTools' . PHP_EOL;
405
        $str .= '    Description: Structure for the ' . str_replace('.xml', '', $fileName) . ' table.' . PHP_EOL;
406
        $str .= '-->' . PHP_EOL;
407
        return $str;
408
    }
409
}
410