Passed
Pull Request — master (#10)
by Joao
01:36
created

AnyDataset::save()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 8
ccs 4
cts 5
cp 0.8
rs 10
c 1
b 0
f 0
cc 2
nc 1
nop 1
crap 2.032
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
62
     */
63
    private $filename;
64
65
    /**
66
     * @param string $filename
67
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
68
     * @throws \ByJG\Util\Exception\XmlUtilException
69
     */
70 17
    public function __construct($filename = null)
71
    {
72 17
        $this->collection = array();
73 17
        $this->currentRow = -1;
74
75 17
        $this->filename = null;
76 17
        $this->defineSavePath($filename, function () {
77 17
            if (!is_null($this->filename)) {
0 ignored issues
show
introduced by
The condition is_null($this->filename) is always false.
Loading history...
78 7
                $this->createFrom($this->filename);
79
            }
80
        });
81
    }
82
83
    public function getFilename()
84
    {
85
        return $this->filename;
86
    }
87
88 17
    private function defineSavePath($file, $closure)
89
    {
90 17
        if (!is_null($file)) {
91 7
            if (!is_string($file)) {
92
                throw new InvalidArgumentException('I expected a string as a file name');
93
            }
94
95 7
            $ext = pathinfo($file, PATHINFO_EXTENSION);
96 7
            if (empty($ext) && substr($file, 0, 6) !== "php://") {
97 7
                $file .= '.anydata.xml';
98
            }
99 7
            $this->filename = $file;
100
        }
101
102 17
        $closure();
103
    }
104
105
    /**
106
     * Private method used to read and populate anydataset class from specified file
107
     *
108
     * @param string $filepath Path and Filename to be read
109
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
110
     * @throws \ByJG\Util\Exception\XmlUtilException
111
     */
112 7
    private function createFrom($filepath)
113
    {
114 7
        if (file_exists($filepath)) {
115 7
            $anyDataSet = XmlUtil::createXmlDocumentFromFile($filepath);
116 7
            $this->collection = array();
117
118 7
            $rows = $anyDataSet->getElementsByTagName("row");
119 7
            foreach ($rows as $row) {
120 7
                $sr = new Row();
121 7
                $fields = $row->getElementsByTagName("field");
122 7
                foreach ($fields as $field) {
123 7
                    $attr = $field->attributes->getNamedItem("name");
124 7
                    if (is_null($attr)) {
125
                        throw new InvalidArgumentException('Malformed anydataset file ' . basename($filepath));
126
                    }
127
128 7
                    $sr->addField($attr->nodeValue, $field->nodeValue);
129
                }
130 7
                $sr->acceptChanges();
131 7
                $this->collection[] = $sr;
132
            }
133 7
            $this->currentRow = count($this->collection) - 1;
134
        }
135
    }
136
137
    /**
138
     * Returns the AnyDataset XML representative structure.
139
     *
140
     * @return string XML String
141
     * @throws \ByJG\Util\Exception\XmlUtilException
142
     */
143 1
    public function xml()
144
    {
145 1
        return (new XmlFormatter($this->getIterator()))->toText();
146
    }
147
148
    /**
149
     * @param string $filename
150
     * @throws DatabaseException
151
     * @throws \ByJG\Util\Exception\XmlUtilException
152
     */
153 2
    public function save($filename = null)
154
    {
155 2
        $this->defineSavePath($filename, function () {
156 2
            if (is_null($this->filename)) {
0 ignored issues
show
introduced by
The condition is_null($this->filename) is always false.
Loading history...
157
                throw new DatabaseException("No such file path to save anydataset");
158
            }
159
160 2
            (new XmlFormatter($this->getIterator()))->saveToFile($this->filename);
161
        });
162
    }
163
164
    /**
165
     * Append one row to AnyDataset.
166
     *
167
     * @param Row|array|\stdClass|object $singleRow
168
     * @return void
169
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
170
     */
171 13
    public function appendRow($singleRow = [])
172
    {
173 13
        if (!empty($singleRow)) {
174 5
            if ($singleRow instanceof Row) {
175 3
                $this->collection[] = $singleRow;
176 3
                $singleRow->acceptChanges();
177 3
            } elseif (is_array($singleRow)) {
178 3
                $this->collection[] = new Row($singleRow);
179
            } else {
180 5
                throw new InvalidArgumentException("You must pass an array or a Row object");
181
            }
182
        } else {
183 8
            $singleRow = new Row();
184 8
            $this->collection[] = $singleRow;
185 8
            $singleRow->acceptChanges();
186
        }
187 13
        $this->currentRow = count($this->collection) - 1;
188
    }
189
190
    /**
191
     * Enter description here...
192
     *
193
     * @param GenericIterator $iterator
194
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
195
     */
196 2
    public function import($iterator)
197
    {
198 2
        foreach ($iterator as $singleRow) {
199 2
            $this->appendRow($singleRow);
200
        }
201
    }
202
203
    /**
204
     * Insert one row before specified position.
205
     *
206
     * @param int $rowNumber
207
     * @param mixed $row
208
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
209
     */
210 1
    public function insertRowBefore($rowNumber, $row = null)
211
    {
212 1
        if ($rowNumber > count($this->collection)) {
213
            $this->appendRow($row);
214
        } else {
215 1
            $singleRow = $row;
216 1
            if (!($row instanceof Row)) {
217 1
                $singleRow = new Row($row);
218
            }
219 1
            array_splice($this->collection, $rowNumber, 0, '');
220 1
            $this->collection[$rowNumber] = $singleRow;
221
        }
222
    }
223
224
    /**
225
     *
226
     * @param mixed $row
227
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
228
     */
229 2
    public function removeRow($row = null)
230
    {
231 2
        if (is_null($row)) {
232
            $row = $this->currentRow;
233
        }
234 2
        if ($row instanceof Row) {
235
            $iPos = 0;
236
            foreach ($this->collection as $sr) {
237
                if ($sr->toArray() == $row->toArray()) {
238
                    $this->removeRow($iPos);
239
                    break;
240
                }
241
                $iPos++;
242
            }
243
            return;
244
        }
245
246 2
        if ($row == 0) {
247 1
            $this->collection = array_slice($this->collection, 1);
248
        } else {
249 1
            $this->collection = array_slice($this->collection, 0, $row) + array_slice($this->collection, $row);
250
        }
251
    }
252
253
    /**
254
     * Add a single string field to an existing row
255
     *
256
     * @param string $name - Field name
257
     * @param string $value - Field value
258
     * @return void
259
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
260
     */
261 7
    public function addField($name, $value)
262
    {
263 7
        if ($this->currentRow < 0) {
264 2
            $this->appendRow();
265
        }
266 7
        $this->collection[$this->currentRow]->addField($name, $value);
267
    }
268
269
    /**
270
     * Get an Iterator filtered by an IteratorFilter
271
     * @param IteratorFilter $itf
272
     * @return GenericIterator
273
     */
274 17
    public function getIterator(IteratorFilter $itf = null)
275
    {
276 17
        if (is_null($itf)) {
277 17
            return new AnyIterator($this->collection);
278
        }
279
280 1
        return new AnyIterator($itf->match($this->collection));
281
    }
282
283
    /**
284
     * Undocumented function
285
     *
286
     * @param string $fieldName
287
     * @param IteratorFilter $itf
288
     * @return array
289
     */
290 1
    public function getArray($fieldName, $itf = null)
291
    {
292 1
        $iterator = $this->getIterator($itf);
293 1
        $result = array();
294 1
        while ($iterator->hasNext()) {
295 1
            $singleRow = $iterator->moveNext();
296 1
            $result[] = $singleRow->get($fieldName);
297
        }
298 1
        return $result;
299
    }
300
301
    /**
302
     *
303
     * @param string $field
304
     * @return void
305
     */
306 1
    public function sort($field)
307
    {
308 1
        if (count($this->collection) == 0) {
309
            return;
310
        }
311
312 1
        $this->collection = $this->quickSortExec($this->collection, $field);
313
314 1
        return;
315
    }
316
317
    /**
318
     * @param Row[] $seq
319
     * @param $field
320
     * @return array
321
     */
322 1
    protected function quickSortExec($seq, $field)
323
    {
324 1
        if (!count($seq)) {
325 1
            return $seq;
326
        }
327
328 1
        $key = $seq[0];
329 1
        $left = $right = array();
330
331 1
        $cntSeq = count($seq);
332 1
        for ($i = 1; $i < $cntSeq; $i ++) {
333 1
            if ($seq[$i]->get($field) <= $key->get($field)) {
334 1
                $left[] = $seq[$i];
335
            } else {
336 1
                $right[] = $seq[$i];
337
            }
338
        }
339
340 1
        return array_merge(
341 1
            $this->quickSortExec($left, $field),
342 1
            [ $key ],
343 1
            $this->quickSortExec($right, $field)
344
        );
345
    }
346
}
347