AnyDataset   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 315
Duplicated Lines 0 %

Test Coverage

Coverage 87.27%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 43
eloc 102
dl 0
loc 315
ccs 96
cts 110
cp 0.8727
rs 8.96
c 1
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A getIterator() 0 7 2
A getFilename() 0 3 1
A createFrom() 0 22 6
A appendRow() 0 17 4
A xml() 0 3 1
A insertRowBefore() 0 18 3
A import() 0 4 2
A save() 0 8 2
A removeRow() 0 21 6
A sort() 0 7 2
A quickSortExec() 0 22 4
A addField() 0 6 2
A getArray() 0 8 2
A defineSavePath() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like AnyDataset 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 AnyDataset, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ByJG\AnyDataset\Core;
4
5
use ByJG\AnyDataset\Core\Exception\DatabaseException;
6
use ByJG\AnyDataset\Core\Formatter\XmlFormatter;
7
use ByJG\Util\XmlUtil;
8
use InvalidArgumentException;
9
10
/**
11
 * AnyDataset is a simple way to store data using only XML file.
12
 * Your structure is hierarquical and each "row" contains "fields" but these structure can vary for each row.
13
 * Anydataset files have extension ".anydata.xml" and have many classes to put and get data into anydataset xml file.
14
 * Anydataset class just read and write files. To search elements you need use AnyIterator
15
 * and IteratorFilter. Each row have a class Row.
16
17
 * XML Structure
18
 * <code>
19
 * <anydataset>
20
 *    <row>
21
 *        <field name="fieldname1">value of fieldname 1</field>
22
 *        <field name="fieldname2">value of fieldname 2</field>
23
 *        <field name="fieldname3">value of fieldname 3</field>
24
 *    </row>
25
 *    <row>
26
 *        <field name="fieldname1">value of fieldname 1</field>
27
 *        <field name="fieldname4">value of fieldname 4</field>
28
 *    </row>
29
 * </anydataset>
30
 * </code>
31
32
 * How to use:
33
 * <code>
34
 * $any = new AnyDataset();
35
 * </code>
36
37
 *
38
*@see Row
39
 * @see AnyIterator
40
 * @see IteratorFilter
41
42
 */
43
class AnyDataset
44
{
45
46
    /**
47
     * Internal structure represent the current Row
48
     *
49
     * @var Row[]
50
     */
51
    private $collection;
52
53
    /**
54
     * Current node anydataset works
55
     * @var int
56
     */
57
    private $currentRow;
58
59
    /**
60
     * Path to anydataset file
61
     * @var string|null
62
     */
63
    private $filename;
64
65
    /**
66
     * @param null|string $filename
67
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
68
     * @throws \ByJG\Util\Exception\XmlUtilException
69
     */
70 18
    public function __construct($filename = null)
71
    {
72 18
        $this->collection = array();
73 18
        $this->currentRow = -1;
74
75 18
        $this->filename = null;
76 18
        $this->defineSavePath($filename, function () {
77 18
            if (!is_null($this->filename)) {
78 7
                $this->createFrom($this->filename);
79
            }
80 18
        });
81
    }
82
83
    /**
84
     * @return string|null
85
     */
86
    public function getFilename()
87
    {
88
        return $this->filename;
89
    }
90
91
    /**
92
     *
93
     * @param string|null $file
94
     * @param mixed $closure
95
     * @return void
96
     */
97 18
    private function defineSavePath($file, $closure)
98
    {
99 18
        if (!is_null($file)) {
100 7
            $ext = pathinfo($file, PATHINFO_EXTENSION);
101 7
            if (empty($ext) && substr($file, 0, 6) !== "php://") {
102 7
                $file .= '.anydata.xml';
103
            }
104 7
            $this->filename = $file;
105
        }
106
107 18
        $closure();
108
    }
109
110
    /**
111
     * Private method used to read and populate anydataset class from specified file
112
     *
113
     * @param string $filepath Path and Filename to be read
114
     * @return void
115
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
116
     * @throws \ByJG\Util\Exception\XmlUtilException
117
     */
118 7
    private function createFrom($filepath)
119
    {
120 7
        if (file_exists($filepath)) {
121 7
            $anyDataSet = XmlUtil::createXmlDocumentFromFile($filepath);
122 7
            $this->collection = array();
123
124 7
            $rows = $anyDataSet->getElementsByTagName("row");
125 7
            foreach ($rows as $row) {
126 7
                $sr = new Row();
127 7
                $fields = $row->getElementsByTagName("field");
128 7
                foreach ($fields as $field) {
129 7
                    $attr = $field->attributes->getNamedItem("name");
130 7
                    if (is_null($attr) || is_null($attr->nodeValue)) {
131
                        throw new InvalidArgumentException('Malformed anydataset file ' . basename($filepath));
132
                    }
133
134 7
                    $sr->addField($attr->nodeValue, $field->nodeValue);
135
                }
136 7
                $sr->acceptChanges();
137 7
                $this->collection[] = $sr;
138
            }
139 7
            $this->currentRow = count($this->collection) - 1;
140
        }
141
    }
142
143
    /**
144
     * Returns the AnyDataset XML representative structure.
145
     *
146
     * @return string XML String
147
     * @throws \ByJG\Util\Exception\XmlUtilException
148
     */
149 1
    public function xml()
150
    {
151 1
        return (new XmlFormatter($this->getIterator()))->toText();
152
    }
153
154
    /**
155
     * @param string|null $filename
156
     * @return void
157
     * @throws DatabaseException
158
     * @throws \ByJG\Util\Exception\XmlUtilException
159
     */
160 2
    public function save($filename = null)
161
    {
162 2
        $this->defineSavePath($filename, function () {
163 2
            if (is_null($this->filename)) {
164
                throw new DatabaseException("No such file path to save anydataset");
165
            }
166
167 2
            (new XmlFormatter($this->getIterator()))->saveToFile($this->filename);
168 2
        });
169
    }
170
171
    /**
172
     * Append one row to AnyDataset.
173
     *
174
     * @param Row|array|\stdClass|object|null $singleRow
175
     * @return void
176
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
177
     */
178 14
    public function appendRow($singleRow = [])
179
    {
180 14
        if (!empty($singleRow)) {
181 6
            if ($singleRow instanceof Row) {
182 3
                $this->collection[] = $singleRow;
183 3
                $singleRow->acceptChanges();
184 4
            } elseif (is_array($singleRow)) {
185 4
                $this->collection[] = new Row($singleRow);
186
            } else {
187 6
                throw new InvalidArgumentException("You must pass an array or a Row object");
188
            }
189
        } else {
190 8
            $singleRow = new Row();
191 8
            $this->collection[] = $singleRow;
192 8
            $singleRow->acceptChanges();
193
        }
194 14
        $this->currentRow = count($this->collection) - 1;
195
    }
196
197
    /**
198
     * Enter description here...
199
     *
200
     * @param GenericIterator $iterator
201
     * @return void
202
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
203
     */
204 2
    public function import($iterator)
205
    {
206 2
        foreach ($iterator as $singleRow) {
207 2
            $this->appendRow($singleRow);
208
        }
209
    }
210
211
    /**
212
     * Insert one row before specified position.
213
     *
214
     * @param int $rowNumber
215
     * @param Row|array|\stdClass|object $row
216
     * @return void
217
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
218
     */
219 1
    public function insertRowBefore($rowNumber, $row)
220
    {
221 1
        if ($rowNumber > count($this->collection)) {
222
            $this->appendRow($row);
223
        } else {
224 1
            $singleRow = $row;
225 1
            if (!($row instanceof Row)) {
226 1
                $singleRow = new Row($row);
227
            }
228
229
            /**
230
             * @psalm-suppress InvalidPropertyAssignmentValue
231
             */
232 1
            array_splice($this->collection, $rowNumber, 0, '');
233
            /**
234
             * @psalm-suppress InvalidPropertyAssignmentValue
235
             */
236 1
            $this->collection[$rowNumber] = $singleRow;
237
        }
238
    }
239
240
    /**
241
     *
242
     * @param mixed $row
243
     * @return void
244
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
245
     */
246 2
    public function removeRow($row = null)
247
    {
248 2
        if (is_null($row)) {
249
            $row = $this->currentRow;
250
        }
251 2
        if ($row instanceof Row) {
252
            $iPos = 0;
253
            foreach ($this->collection as $sr) {
254
                if ($sr->toArray() == $row->toArray()) {
255
                    $this->removeRow($iPos);
256
                    break;
257
                }
258
                $iPos++;
259
            }
260
            return;
261
        }
262
263 2
        if ($row == 0) {
264 1
            $this->collection = array_slice($this->collection, 1);
265
        } else {
266 1
            $this->collection = array_slice($this->collection, 0, $row) + array_slice($this->collection, $row);
267
        }
268
    }
269
270
    /**
271
     * Add a single string field to an existing row
272
     *
273
     * @param string $name - Field name
274
     * @param string $value - Field value
275
     * @return void
276
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
277
     */
278 7
    public function addField($name, $value)
279
    {
280 7
        if ($this->currentRow < 0) {
281 2
            $this->appendRow();
282
        }
283 7
        $this->collection[$this->currentRow]->addField($name, $value);
284
    }
285
286
    /**
287
     * Get an Iterator filtered by an IteratorFilter
288
     * @param IteratorFilter $itf
289
     * @return GenericIterator
290
     */
291 18
    public function getIterator(IteratorFilter $itf = null)
292
    {
293 18
        if (is_null($itf)) {
294 18
            return new AnyIterator($this->collection);
295
        }
296
297 1
        return new AnyIterator($itf->match($this->collection));
298
    }
299
300
    /**
301
     * Undocumented function
302
     *
303
     * @param string $fieldName
304
     * @param IteratorFilter $itf
305
     * @return array
306
     */
307 1
    public function getArray($fieldName, $itf = null)
308
    {
309 1
        $iterator = $this->getIterator($itf);
310 1
        $result = array();
311 1
        foreach ($iterator as $singleRow) {
312 1
            $result[] = $singleRow->get($fieldName);
313
        }
314 1
        return $result;
315
    }
316
317
    /**
318
     *
319
     * @param string $field
320
     * @return void
321
     */
322 1
    public function sort($field)
323
    {
324 1
        if (count($this->collection) == 0) {
325
            return;
326
        }
327
328 1
        $this->collection = $this->quickSortExec($this->collection, $field);
329
    }
330
331
    /**
332
     * @param Row[] $seq
333
     * @param string $field
334
     * @return array
335
     */
336 1
    protected function quickSortExec($seq, $field)
337
    {
338 1
        if (!count($seq)) {
339 1
            return $seq;
340
        }
341
342 1
        $key = $seq[0];
343 1
        $left = $right = array();
344
345 1
        $cntSeq = count($seq);
346 1
        for ($i = 1; $i < $cntSeq; $i ++) {
347 1
            if ($seq[$i]->get($field) <= $key->get($field)) {
348 1
                $left[] = $seq[$i];
349
            } else {
350 1
                $right[] = $seq[$i];
351
            }
352
        }
353
354 1
        return array_merge(
355 1
            $this->quickSortExec($left, $field),
356 1
            [ $key ],
357 1
            $this->quickSortExec($right, $field)
358 1
        );
359
    }
360
}
361