1<?php
2/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
3
4namespace Icinga\Data\DataArray;
5
6use ArrayIterator;
7use Icinga\Data\Selectable;
8use Icinga\Data\SimpleQuery;
9
10class ArrayDatasource implements Selectable
11{
12    /**
13     * The array being used as data source
14     *
15     * @var array
16     */
17    protected $data;
18
19    /**
20     * The current result
21     *
22     * @var array
23     */
24    protected $result;
25
26    /**
27     * The result of a counted query
28     *
29     * @var int
30     */
31    protected $count;
32
33    /**
34     * The name of the column to map array keys on
35     *
36     * In case the array being used as data source provides keys of type string,this name
37     * will be used to set such as column on each row, if the column is not set already.
38     *
39     * @var string
40     */
41    protected $keyColumn;
42
43    /**
44     * Create a new data source for the given array
45     *
46     * @param   array   $data   The array you're going to use as a data source
47     */
48    public function __construct(array $data)
49    {
50        $this->data = $data;
51    }
52
53    /**
54     * Set the name of the column to map array keys on
55     *
56     * @param   string  $name
57     *
58     * @return  $this
59     */
60    public function setKeyColumn($name)
61    {
62        $this->keyColumn = $name;
63        return $this;
64    }
65
66    /**
67     * Return the name of the column to map array keys on
68     *
69     * @return  string
70     */
71    public function getKeyColumn()
72    {
73        return $this->keyColumn;
74    }
75
76    /**
77     * Provide a query for this data source
78     *
79     * @return  SimpleQuery
80     */
81    public function select()
82    {
83        return new SimpleQuery(clone $this);
84    }
85
86    /**
87     * Fetch and return all rows of the given query's result set using an iterator
88     *
89     * @param   SimpleQuery     $query
90     *
91     * @return  ArrayIterator
92     */
93    public function query(SimpleQuery $query)
94    {
95        return new ArrayIterator($this->fetchAll($query));
96    }
97
98    /**
99     * Fetch and return a column of all rows of the result set as an array
100     *
101     * @param   SimpleQuery     $query
102     *
103     * @return  array
104     */
105    public function fetchColumn(SimpleQuery $query)
106    {
107        $result = array();
108        foreach ($this->getResult($query) as $row) {
109            $arr = (array) $row;
110            $result[] = array_shift($arr);
111        }
112
113        return $result;
114    }
115
116    /**
117     * Fetch and return all rows of the given query's result as a flattened key/value based array
118     *
119     * @param   SimpleQuery     $query
120     *
121     * @return  array
122     */
123    public function fetchPairs(SimpleQuery $query)
124    {
125        $result = array();
126        $keys = null;
127        foreach ($this->getResult($query) as $row) {
128            if ($keys === null) {
129                $keys = array_keys((array) $row);
130                if (count($keys) < 2) {
131                    $keys[1] = $keys[0];
132                }
133            }
134
135            $result[$row->{$keys[0]}] = $row->{$keys[1]};
136        }
137
138        return $result;
139    }
140
141    /**
142     * Fetch and return the first row of the given query's result
143     *
144     * @param   SimpleQuery     $query
145     *
146     * @return  object|false    The row or false in case the result is empty
147     */
148    public function fetchRow(SimpleQuery $query)
149    {
150        $result = $this->getResult($query);
151        if (empty($result)) {
152            return false;
153        }
154
155        return array_shift($result);
156    }
157
158    /**
159     * Fetch and return all rows of the given query's result as an array
160     *
161     * @param   SimpleQuery     $query
162     *
163     * @return  array
164     */
165    public function fetchAll(SimpleQuery $query)
166    {
167        return $this->getResult($query);
168    }
169
170    /**
171     * Count all rows of the given query's result
172     *
173     * @param   SimpleQuery     $query
174     *
175     * @return  int
176     */
177    public function count(SimpleQuery $query)
178    {
179        if ($this->count === null) {
180            $this->count = count($this->createResult($query));
181        }
182
183        return $this->count;
184    }
185
186    /**
187     * Create and return the result for the given query
188     *
189     * @param   SimpleQuery     $query
190     *
191     * @return  array
192     */
193    protected function createResult(SimpleQuery $query)
194    {
195        $columns = $query->getColumns();
196        $filter = $query->getFilter();
197        $offset = $query->hasOffset() ? $query->getOffset() : 0;
198        $limit = $query->hasLimit() ? $query->getLimit() : 0;
199
200        $foundStringKey = false;
201        $result = array();
202        $skipped = 0;
203        foreach ($this->data as $key => $row) {
204            if ($this->keyColumn !== null && !isset($row->{$this->keyColumn})) {
205                $row = clone $row; // Make sure that this won't affect the actual data
206                $row->{$this->keyColumn} = $key;
207            }
208
209            if (! $filter->matches($row)) {
210                continue;
211            } elseif ($skipped < $offset) {
212                $skipped++;
213                continue;
214            }
215
216            // Get only desired columns if asked so
217            if (! empty($columns)) {
218                $filteredRow = (object) array();
219                foreach ($columns as $alias => $name) {
220                    if (! is_string($alias)) {
221                        $alias = $name;
222                    }
223
224                    if (isset($row->$name)) {
225                        $filteredRow->$alias = $row->$name;
226                    } else {
227                        $filteredRow->$alias = null;
228                    }
229                }
230            } else {
231                $filteredRow = $row;
232            }
233
234            $foundStringKey |= is_string($key);
235            $result[$key] = $filteredRow;
236
237            if (count($result) === $limit) {
238                break;
239            }
240        }
241
242        // Sort the result
243        if ($query->hasOrder()) {
244            if ($foundStringKey) {
245                uasort($result, array($query, 'compare'));
246            } else {
247                usort($result, array($query, 'compare'));
248            }
249        } elseif (! $foundStringKey) {
250            $result = array_values($result);
251        }
252
253        return $result;
254    }
255
256    /**
257     * Return whether a query result exists
258     *
259     * @return  bool
260     */
261    protected function hasResult()
262    {
263        return $this->result !== null;
264    }
265
266    /**
267     * Set the current result
268     *
269     * @param   array   $result
270     *
271     * @return  $this
272     */
273    protected function setResult(array $result)
274    {
275        $this->result = $result;
276        return $this;
277    }
278
279    /**
280     * Return the result for the given query
281     *
282     * @param   SimpleQuery     $query
283     *
284     * @return  array
285     */
286    protected function getResult(SimpleQuery $query)
287    {
288        if (! $this->hasResult()) {
289            $this->setResult($this->createResult($query));
290        }
291
292        return $this->result;
293    }
294}
295