Passed
Push — master ( 5379a4...a2888f )
by Joao
04:49
created

src/Repository/AnyDataset.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace ByJG\AnyDataset\Repository;
4
5
use ByJG\AnyDataset\Exception\DatabaseException;
6
use ByJG\Util\XmlUtil;
7
use ForceUTF8\Encoding;
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 and IteratorFilter. Each row have a class SingleRow.
15
 *
16
 * XML Structure
17
 * <code>
18
 * <anydataset>
19
 * 		<row>
20
 * 			<field name="fieldname1">value of fieldname 1</field>
21
 * 			<field name="fieldname2">value of fieldname 2</field>
22
 * 			<field name="fieldname3">value of fieldname 3</field>
23
 * 		</row>
24
 * 		<row>
25
 * 			<field name="fieldname1">value of fieldname 1</field>
26
 * 			<field name="fieldname4">value of fieldname 4</field>
27
 * 		</row>
28
 * </anydataset>
29
 * </code>
30
 *
31
 * How to use:
32
 * <code>
33
 * $any = new AnyDataset();
34
 * </code>
35
 *
36
 * @see SingleRow
37
 * @see AnyIterator
38
 * @see IteratorFilter
39
 *
40
 */
41
class AnyDataset
42
{
43
44
    /**
45
     * Internal structure represent the current SingleRow
46
     * @var SingleRow[]
47
     */
48
    private $_collection;
49
50
    /**
51
     * Current node anydataset works
52
     * @var int
53
     */
54
    private $_currentRow;
55
56
    /**
57
     * Path to anydataset file
58
     * @var string
59
     */
60
    private $_path;
61
62
    /**
63
     *
64
     * @param string $file
65
     * @throws InvalidArgumentException
66
     */
67 8
    public function __construct($file = null)
68
    {
69 8
        $this->_collection = array();
70 8
        $this->_currentRow = -1;
71
72 8
        $this->_path = null;
73 8
        if (!is_null($file)) {
74 1
            if (is_string($file)) {
75 1
                $ext = pathinfo($file, PATHINFO_EXTENSION);
76 1
                if (empty($ext)) {
77 1
                    $file .= '.anydata.xml';
78 1
                }
79 1
                $this->_path = $file;
80 1
            } else {
81
                throw new \InvalidArgumentException('I expected a string as a file name');
82
            }
83 1
            $this->createFrom($this->_path);
84 1
        }
85 8
    }
86
87
    /**
88
     * Private method used to read and populate anydataset class from specified file
89
     * @param string $filepath Path and Filename to be read
90
     * @return null
91
     */
92
    private function createFrom($filepath)
93
    {
94
        if (file_exists($filepath)) {
95
            $anyDataSet = XmlUtil::createXmlDocumentFromFile($filepath);
96
            $this->_collection = array();
97
98
            $rows = $anyDataSet->getElementsByTagName("row");
99
            foreach ($rows as $row) {
100
                $sr = new SingleRow();
101
                $fields = $row->getElementsByTagName("field");
102
                foreach ($fields as $field) {
103
                    $attr = $field->attributes->getNamedItem("name");
104
                    if (!is_null($attr)) {
105
                        $sr->addField($attr->nodeValue, $field->nodeValue);
106
                    } else {
107
                        throw new \InvalidArgumentException('Malformed anydataset file ' . basename($filepath));
108
                    }
109
                }
110
                $sr->acceptChanges();
111
                $this->_collection[] = $sr;
112
            }
113
            $this->_currentRow = sizeof($this->_collection) - 1;
0 ignored issues
show
Documentation Bug introduced by
It seems like sizeof($this->_collection) - 1 can also be of type double. However, the property $_currentRow is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
114
        }
115
    }
116
117
    /**
118
     * Returns the AnyDataset XML representative structure.
119
     * @return string XML String
120
     */
121 1
    public function xml()
122
    {
123 1
        return $this->getDomObject()->saveXML();
124
    }
125
126
    /**
127
     * Returns the AnyDataset XmlDocument representive object
128
     * @return \DOMDocument XmlDocument object
129
     */
130 1
    public function getDomObject()
131
    {
132 1
        $anyDataSet = XmlUtil::createXmlDocumentFromStr("<anydataset/>");
133 1
        $nodeRoot = $anyDataSet->getElementsByTagName("anydataset")->item(0);
134 1
        foreach ($this->_collection as $sr) {
135 1
            $row = $sr->getDomObject();
136 1
            $nodeRow = $row->getElementsByTagName("row")->item(0);
137 1
            $newRow = XmlUtil::createChild($nodeRoot, "row");
138 1
            XmlUtil::addNodeFromNode($newRow, $nodeRow);
139 1
        }
140
141 1
        return $anyDataSet;
142
    }
143
144
    /**
145
     *
146
     * @param string $file
147
     * @throws DatabaseException
148
     */
149 1
    public function save($file = null)
150
    {
151 1
        if (!is_null($file)) {
152 1
            if (is_string($file)) {
153 1
                $this->_path = $file;
154 1
            } else {
155
                throw new InvalidArgumentException('Invalid file name');
156
            }
157 1
        }
158
159 1
        if (is_null($this->_path)) {
160
            throw new DatabaseException("No such file path to save anydataset");
161
        }
162
163 1
        XmlUtil::saveXmlDocument($this->getDomObject(), $this->_path);
164 1
    }
165
166
    /**
167
     * Append one row to AnyDataset.
168
     * @param SingleRow $sr
169
     * @return void
170
     */
171 8
    public function appendRow($sr = null)
172
    {
173 8
        if (!is_null($sr)) {
174 1
            if ($sr instanceof SingleRow) {
175 1
                $this->_collection[] = $sr;
176 1
                $sr->acceptChanges();
177 1
            } elseif (is_array($sr)) {
178
                $this->_collection[] = new SingleRow($sr);
179
            } else {
180
                throw new InvalidArgumentException("You must pass an array or a SingleRow object");
181
            }
182 1
        } else {
183 7
            $sr = new SingleRow();
184 7
            $this->_collection[] = $sr;
185 7
            $sr->acceptChanges();
186
        }
187 8
        $this->_currentRow = count($this->_collection) - 1;
188 8
    }
189
190
    /**
191
     * Enter description here...
192
     *
193
     * @param IteratorInterface $it
194
     */
195 1
    public function import(IteratorInterface $it)
196
    {
197 1
        foreach ($it as $singleRow) {
0 ignored issues
show
The expression $it of type object<ByJG\AnyDataset\R...tory\IteratorInterface> is not traversable.
Loading history...
198 1
            $this->appendRow($singleRow);
199 1
        }
200 1
    }
201
202
    /**
203
     * Insert one row before specified position.
204
     * @param int $rowNumber
205
     * @param mixed $row
206
     */
207 1
    public function insertRowBefore($rowNumber, $row = null)
208
    {
209 1
        if ($rowNumber > count($this->_collection)) {
210
            $this->appendRow($row);
211
        } else {
212 1
            $singleRow = $row;
213 1
            if (!($row instanceof SingleRow)) {
214 1
                $singleRow = new SingleRow($row);
215 1
            }
216 1
            array_splice($this->_collection, $rowNumber, 0, '');
217 1
            $this->_collection[$rowNumber] = $singleRow;
218
        }
219 1
    }
220
221
    /**
222
     *
223
     * @param mixed $row
224
     * @return null
225
     */
226 2
    public function removeRow($row = null)
227
    {
228 2
        if (is_null($row)) {
229
            $row = $this->_currentRow;
230
        }
231 2
        if ($row instanceof SingleRow) {
232
            $i = 0;
233
            foreach ($this->_collection as $sr) {
234
                if ($sr->toArray() == $row->toArray()) {
235
                    $this->removeRow($i);
236
                    break;
237
                }
238
                $i++;
239
            }
240
            return;
241
        }
242
243 2
        if ($row == 0) {
244 1
            $this->_collection = array_slice($this->_collection, 1);
245 1
        } else {
246 1
            $this->_collection = array_slice($this->_collection, 0, $row) + array_slice($this->_collection, $row);
247
        }
248 2
    }
249
250
    /**
251
     * Add a single string field to an existing row
252
     * @param string $name - Field name
253
     * @param string $value - Field value
254
     * @return void
255
     */
256 7
    public function addField($name, $value)
257
    {
258 7
        if ($this->_currentRow < 0) {
259 1
            $this->appendRow();
260 1
        }
261 7
        $this->_collection[$this->_currentRow]->addField($name, $value);
262 7
    }
263
264
    /**
265
     * Get an Iterator filtered by an IteratorFilter
266
     * @param IteratorFilter $itf
267
     * @return IteratorInterface
268
     */
269 7
    public function getIterator(IteratorFilter $itf = null)
270
    {
271 7
        if (is_null($itf)) {
272 7
            return new AnyIterator($this->_collection);
273
        } else {
274
            return new AnyIterator($itf->match($this->_collection));
275
        }
276
    }
277
278
    /**
279
     * @desc
280
     * @param IteratorFilter $itf
281
     * @param string $fieldName
282
     * @return array
283
     */
284 1 View Code Duplication
    public function getArray($itf, $fieldName)
285
    {
286 1
        $it = $this->getIterator($itf);
287 1
        $result = array();
288 1
        while ($it->hasNext()) {
289 1
            $sr = $it->moveNext();
290 1
            $result [] = $sr->getField($fieldName);
291 1
        }
292 1
        return $result;
293
    }
294
295
    /**
296
     *
297
     * @param string $field
298
     * @return void
299
     */
300 1
    public function sort($field)
301
    {
302 1
        if (count($this->_collection) == 0) {
303
            return;
304
        }
305
306 1
        $this->_collection = $this->quickSortExec($this->_collection, $field);
307
308 1
        return;
309
    }
310
311
    protected function quickSortExec($seq, $field)
312
    {
313
        if (!count($seq)) return $seq;
314
315
        $k = $seq [0];
316
        $x = $y = array();
317
318
        $cntSeq = count($seq);
319
        for ($i = 1; $i < $cntSeq; $i ++) {
320
            if ($seq[$i]->getField($field) <= $k->getField($field)) {
321
                $x [] = $seq [$i];
322
            } else {
323
                $y [] = $seq [$i];
324
            }
325
        }
326
327
        return array_merge($this->quickSortExec($x, $field), array($k), $this->quickSortExec($y, $field));
328
    }
329
330
    /**
331
     * @param $document
332
     * @return array|string
333
     */
334
    public static function fixUTF8($document)
335
    {
336
        return Encoding::fixUTF8(Encoding::removeBOM($document), Encoding::ICONV_TRANSLIT);
337
    }
338
}
339