1<?php
2/*
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 *
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
18 */
19
20namespace Doctrine\Common\Collections;
21
22use ArrayIterator;
23use Closure;
24use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
25
26/**
27 * An ArrayCollection is a Collection implementation that wraps a regular PHP array.
28 *
29 * Warning: Using (un-)serialize() on a collection is not a supported use-case
30 * and may break when we change the internals in the future. If you need to
31 * serialize a collection use {@link toArray()} and reconstruct the collection
32 * manually.
33 *
34 * @since  2.0
35 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
36 * @author Jonathan Wage <jonwage@gmail.com>
37 * @author Roman Borschel <roman@code-factory.org>
38 */
39class ArrayCollection implements Collection, Selectable
40{
41    /**
42     * An array containing the entries of this collection.
43     *
44     * @var array
45     */
46    private $elements;
47
48    /**
49     * Initializes a new ArrayCollection.
50     *
51     * @param array $elements
52     */
53    public function __construct(array $elements = array())
54    {
55        $this->elements = $elements;
56    }
57
58    /**
59     * Creates a new instance from the specified elements.
60     *
61     * This method is provided for derived classes to specify how a new
62     * instance should be created when constructor semantics have changed.
63     *
64     * @param array $elements Elements.
65     *
66     * @return static
67     */
68    protected function createFrom(array $elements)
69    {
70        return new static($elements);
71    }
72
73    /**
74     * {@inheritDoc}
75     */
76    public function toArray()
77    {
78        return $this->elements;
79    }
80
81    /**
82     * {@inheritDoc}
83     */
84    public function first()
85    {
86        return reset($this->elements);
87    }
88
89    /**
90     * {@inheritDoc}
91     */
92    public function last()
93    {
94        return end($this->elements);
95    }
96
97    /**
98     * {@inheritDoc}
99     */
100    public function key()
101    {
102        return key($this->elements);
103    }
104
105    /**
106     * {@inheritDoc}
107     */
108    public function next()
109    {
110        return next($this->elements);
111    }
112
113    /**
114     * {@inheritDoc}
115     */
116    public function current()
117    {
118        return current($this->elements);
119    }
120
121    /**
122     * {@inheritDoc}
123     */
124    public function remove($key)
125    {
126        if ( ! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) {
127            return null;
128        }
129
130        $removed = $this->elements[$key];
131        unset($this->elements[$key]);
132
133        return $removed;
134    }
135
136    /**
137     * {@inheritDoc}
138     */
139    public function removeElement($element)
140    {
141        $key = array_search($element, $this->elements, true);
142
143        if ($key === false) {
144            return false;
145        }
146
147        unset($this->elements[$key]);
148
149        return true;
150    }
151
152    /**
153     * Required by interface ArrayAccess.
154     *
155     * {@inheritDoc}
156     */
157    public function offsetExists($offset)
158    {
159        return $this->containsKey($offset);
160    }
161
162    /**
163     * Required by interface ArrayAccess.
164     *
165     * {@inheritDoc}
166     */
167    public function offsetGet($offset)
168    {
169        return $this->get($offset);
170    }
171
172    /**
173     * Required by interface ArrayAccess.
174     *
175     * {@inheritDoc}
176     */
177    public function offsetSet($offset, $value)
178    {
179        if ( ! isset($offset)) {
180            return $this->add($value);
181        }
182
183        $this->set($offset, $value);
184    }
185
186    /**
187     * Required by interface ArrayAccess.
188     *
189     * {@inheritDoc}
190     */
191    public function offsetUnset($offset)
192    {
193        return $this->remove($offset);
194    }
195
196    /**
197     * {@inheritDoc}
198     */
199    public function containsKey($key)
200    {
201        return isset($this->elements[$key]) || array_key_exists($key, $this->elements);
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    public function contains($element)
208    {
209        return in_array($element, $this->elements, true);
210    }
211
212    /**
213     * {@inheritDoc}
214     */
215    public function exists(Closure $p)
216    {
217        foreach ($this->elements as $key => $element) {
218            if ($p($key, $element)) {
219                return true;
220            }
221        }
222
223        return false;
224    }
225
226    /**
227     * {@inheritDoc}
228     */
229    public function indexOf($element)
230    {
231        return array_search($element, $this->elements, true);
232    }
233
234    /**
235     * {@inheritDoc}
236     */
237    public function get($key)
238    {
239        return isset($this->elements[$key]) ? $this->elements[$key] : null;
240    }
241
242    /**
243     * {@inheritDoc}
244     */
245    public function getKeys()
246    {
247        return array_keys($this->elements);
248    }
249
250    /**
251     * {@inheritDoc}
252     */
253    public function getValues()
254    {
255        return array_values($this->elements);
256    }
257
258    /**
259     * {@inheritDoc}
260     */
261    public function count()
262    {
263        return count($this->elements);
264    }
265
266    /**
267     * {@inheritDoc}
268     */
269    public function set($key, $value)
270    {
271        $this->elements[$key] = $value;
272    }
273
274    /**
275     * {@inheritDoc}
276     */
277    public function add($element)
278    {
279        $this->elements[] = $element;
280
281        return true;
282    }
283
284    /**
285     * {@inheritDoc}
286     */
287    public function isEmpty()
288    {
289        return empty($this->elements);
290    }
291
292    /**
293     * Required by interface IteratorAggregate.
294     *
295     * {@inheritDoc}
296     */
297    public function getIterator()
298    {
299        return new ArrayIterator($this->elements);
300    }
301
302    /**
303     * {@inheritDoc}
304     */
305    public function map(Closure $func)
306    {
307        return $this->createFrom(array_map($func, $this->elements));
308    }
309
310    /**
311     * {@inheritDoc}
312     */
313    public function filter(Closure $p)
314    {
315        return $this->createFrom(array_filter($this->elements, $p));
316    }
317
318    /**
319     * {@inheritDoc}
320     */
321    public function forAll(Closure $p)
322    {
323        foreach ($this->elements as $key => $element) {
324            if ( ! $p($key, $element)) {
325                return false;
326            }
327        }
328
329        return true;
330    }
331
332    /**
333     * {@inheritDoc}
334     */
335    public function partition(Closure $p)
336    {
337        $matches = $noMatches = array();
338
339        foreach ($this->elements as $key => $element) {
340            if ($p($key, $element)) {
341                $matches[$key] = $element;
342            } else {
343                $noMatches[$key] = $element;
344            }
345        }
346
347        return array($this->createFrom($matches), $this->createFrom($noMatches));
348    }
349
350    /**
351     * Returns a string representation of this object.
352     *
353     * @return string
354     */
355    public function __toString()
356    {
357        return __CLASS__ . '@' . spl_object_hash($this);
358    }
359
360    /**
361     * {@inheritDoc}
362     */
363    public function clear()
364    {
365        $this->elements = array();
366    }
367
368    /**
369     * {@inheritDoc}
370     */
371    public function slice($offset, $length = null)
372    {
373        return array_slice($this->elements, $offset, $length, true);
374    }
375
376    /**
377     * {@inheritDoc}
378     */
379    public function matching(Criteria $criteria)
380    {
381        $expr     = $criteria->getWhereExpression();
382        $filtered = $this->elements;
383
384        if ($expr) {
385            $visitor  = new ClosureExpressionVisitor();
386            $filter   = $visitor->dispatch($expr);
387            $filtered = array_filter($filtered, $filter);
388        }
389
390        if ($orderings = $criteria->getOrderings()) {
391            $next = null;
392            foreach (array_reverse($orderings) as $field => $ordering) {
393                $next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
394            }
395
396            uasort($filtered, $next);
397        }
398
399        $offset = $criteria->getFirstResult();
400        $length = $criteria->getMaxResults();
401
402        if ($offset || $length) {
403            $filtered = array_slice($filtered, (int)$offset, $length);
404        }
405
406        return $this->createFrom($filtered);
407    }
408}
409