1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Extbase\Persistence\Generic;
17
18use TYPO3\CMS\Core\Utility\GeneralUtility;
19use TYPO3\CMS\Extbase\Object\ObjectManager;
20use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
21use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
22use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
23use TYPO3\CMS\Extbase\Persistence\QueryInterface;
24use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
25
26/**
27 * A lazy result list that is returned by Query::execute()
28 */
29class QueryResult implements QueryResultInterface
30{
31    /**
32     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
33     */
34    protected $dataMapper;
35
36    /**
37     * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
38     */
39    protected $persistenceManager;
40
41    /**
42     * @var int|null
43     */
44    protected $numberOfResults;
45
46    /**
47     * @var \TYPO3\CMS\Extbase\Persistence\QueryInterface
48     */
49    protected $query;
50
51    /**
52     * @var array
53     */
54    protected $queryResult;
55
56    /**
57     * @var ObjectManagerInterface
58     */
59    protected $objectManager;
60
61    /**
62     * @param ObjectManagerInterface $objectManager
63     */
64    public function injectObjectManager(ObjectManagerInterface $objectManager)
65    {
66        $this->objectManager = $objectManager;
67    }
68
69    /**
70     * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
71     */
72    public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager)
73    {
74        $this->persistenceManager = $persistenceManager;
75    }
76
77    /**
78     * Constructor
79     *
80     * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
81     */
82    public function __construct(QueryInterface $query)
83    {
84        $this->query = $query;
85    }
86
87    /**
88     * Object initialization called when object is created with ObjectManager, after constructor
89     */
90    public function initializeObject()
91    {
92        $this->dataMapper = $this->objectManager->get(DataMapper::class);
93        $this->dataMapper->setQuery($this->query);
94    }
95
96    /**
97     * Loads the objects this QueryResult is supposed to hold
98     */
99    protected function initialize()
100    {
101        if (!is_array($this->queryResult)) {
102            $this->queryResult = $this->dataMapper->map($this->query->getType(), $this->persistenceManager->getObjectDataByQuery($this->query));
103        }
104    }
105
106    /**
107     * Returns a clone of the query object
108     *
109     * @return \TYPO3\CMS\Extbase\Persistence\QueryInterface
110     */
111    public function getQuery()
112    {
113        return clone $this->query;
114    }
115
116    /**
117     * Returns the first object in the result set
118     *
119     * @return object
120     */
121    public function getFirst()
122    {
123        if (is_array($this->queryResult)) {
124            $queryResult = $this->queryResult;
125            reset($queryResult);
126        } else {
127            $query = $this->getQuery();
128            $query->setLimit(1);
129            $queryResult = $this->dataMapper->map($query->getType(), $this->persistenceManager->getObjectDataByQuery($query));
130        }
131        $firstResult = current($queryResult);
132        if ($firstResult === false) {
133            $firstResult = null;
134        }
135        return $firstResult;
136    }
137
138    /**
139     * Returns the number of objects in the result
140     *
141     * @return int The number of matching objects
142     */
143    public function count()
144    {
145        if ($this->numberOfResults === null) {
146            if (is_array($this->queryResult)) {
147                $this->numberOfResults = count($this->queryResult);
148            } else {
149                $this->numberOfResults = $this->persistenceManager->getObjectCountByQuery($this->query);
150            }
151        }
152        return $this->numberOfResults;
153    }
154
155    /**
156     * Returns an array with the objects in the result set
157     *
158     * @return array
159     */
160    public function toArray()
161    {
162        $this->initialize();
163        return iterator_to_array($this);
164    }
165
166    /**
167     * This method is needed to implement the ArrayAccess interface,
168     * but it isn't very useful as the offset has to be an integer
169     *
170     * @param mixed $offset
171     * @return bool
172     * @see ArrayAccess::offsetExists()
173     */
174    public function offsetExists($offset)
175    {
176        $this->initialize();
177        return isset($this->queryResult[$offset]);
178    }
179
180    /**
181     * @param mixed $offset
182     * @return mixed
183     * @see ArrayAccess::offsetGet()
184     */
185    public function offsetGet($offset)
186    {
187        $this->initialize();
188        return $this->queryResult[$offset] ?? null;
189    }
190
191    /**
192     * This method has no effect on the persisted objects but only on the result set
193     *
194     * @param mixed $offset
195     * @param mixed $value
196     * @see ArrayAccess::offsetSet()
197     */
198    public function offsetSet($offset, $value)
199    {
200        $this->initialize();
201        $this->numberOfResults = null;
202        $this->queryResult[$offset] = $value;
203    }
204
205    /**
206     * This method has no effect on the persisted objects but only on the result set
207     *
208     * @param mixed $offset
209     * @see ArrayAccess::offsetUnset()
210     */
211    public function offsetUnset($offset)
212    {
213        $this->initialize();
214        $this->numberOfResults = null;
215        unset($this->queryResult[$offset]);
216    }
217
218    /**
219     * @return mixed
220     * @see Iterator::current()
221     */
222    public function current()
223    {
224        $this->initialize();
225        return current($this->queryResult);
226    }
227
228    /**
229     * @return mixed
230     * @see Iterator::key()
231     */
232    public function key()
233    {
234        $this->initialize();
235        return key($this->queryResult);
236    }
237
238    /**
239     * @see Iterator::next()
240     */
241    public function next()
242    {
243        $this->initialize();
244        next($this->queryResult);
245    }
246
247    /**
248     * @see Iterator::rewind()
249     */
250    public function rewind()
251    {
252        $this->initialize();
253        reset($this->queryResult);
254    }
255
256    /**
257     * @return bool
258     * @see Iterator::valid()
259     */
260    public function valid()
261    {
262        $this->initialize();
263        return current($this->queryResult) !== false;
264    }
265
266    /**
267     * Ensures that the objectManager, persistenceManager and dataMapper are back when loading the QueryResult
268     * from the cache
269     * @internal only to be used within Extbase, not part of TYPO3 Core API.
270     */
271    public function __wakeup()
272    {
273        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
274        $this->persistenceManager = $objectManager->get(PersistenceManagerInterface::class);
275        $this->dataMapper = $objectManager->get(DataMapper::class);
276    }
277
278    /**
279     * @return array
280     * @internal only to be used within Extbase, not part of TYPO3 Core API.
281     */
282    public function __sleep()
283    {
284        return ['query'];
285    }
286}
287