Merge branch 'master' of github.com:WoltLab/WCF
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / DatabaseObjectList.class.php
1 <?php
2 namespace wcf\data;
3 use wcf\system\database\util\PreparedStatementConditionBuilder;
4 use wcf\system\exception\SystemException;
5 use wcf\system\WCF;
6 use wcf\util\StringUtil;
7
8 /**
9 * Abstract class for a list of database objects.
10 *
11 * @author Marcel Werk
12 * @copyright 2001-2012 WoltLab GmbH
13 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14 * @package com.woltlab.wcf
15 * @subpackage data
16 * @category Community Framework
17 */
18 abstract class DatabaseObjectList implements \Countable, ITraversableObject {
19 /**
20 * class name
21 * @var string
22 */
23 public $className = '';
24
25 /**
26 * object class name
27 * @var string
28 */
29 public $objectClassName = '';
30
31 /**
32 * result objects
33 * @var array<wcf\data\DatabaseObject>
34 */
35 public $objects = array();
36
37 /**
38 * ids of result objects
39 * @var array<integer>
40 */
41 public $objectIDs = null;
42
43 /**
44 * sql offset
45 * @var integer
46 */
47 public $sqlOffset = 0;
48
49 /**
50 * sql limit
51 * @var integer
52 */
53 public $sqlLimit = 20;
54
55 /**
56 * sql order by statement
57 * @var string
58 */
59 public $sqlOrderBy = '';
60
61 /**
62 * sql select parameters
63 * @var string
64 */
65 public $sqlSelects = '';
66
67 /**
68 * sql select joins which are necessary for where statements
69 * @var string
70 */
71 public $sqlConditionJoins = '';
72
73 /**
74 * sql select joins
75 * @var string
76 */
77 public $sqlJoins = '';
78
79 /**
80 * enables the automatic usage of the qualified shorthand
81 * @var boolean
82 */
83 public $useQualifiedShorthand = true;
84
85 /**
86 * sql conditions
87 * @var wcf\system\database\util\PreparedStatementConditionBuilder
88 */
89 protected $conditionBuilder = null;
90
91 /**
92 * current iterator index
93 * @var integer
94 */
95 protected $index = 0;
96
97 /**
98 * list of index to object relation
99 * @var array<integer>
100 */
101 protected $indexToObject = null;
102
103 /**
104 * Creates a new DatabaseObjectList object.
105 */
106 public function __construct() {
107 // set class name
108 if (empty($this->className)) {
109 $className = get_called_class();
110
111 if (StringUtil::substring($className, -4) == 'List') {
112 $this->className = StringUtil::substring($className, 0, -4);
113 }
114 }
115
116 $this->conditionBuilder = new PreparedStatementConditionBuilder();
117 }
118
119 /**
120 * Counts the number of objects.
121 *
122 * @return integer
123 */
124 public function countObjects() {
125 $sql = "SELECT COUNT(*) AS count
126 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
127 ".$this->sqlConditionJoins."
128 ".$this->getConditionBuilder()->__toString();
129 $statement = WCF::getDB()->prepareStatement($sql);
130 $statement->execute($this->getConditionBuilder()->getParameters());
131 $row = $statement->fetchArray();
132 return $row['count'];
133 }
134
135 /**
136 * Reads the object ids from database.
137 */
138 public function readObjectIDs() {
139 $this->objectIDs = array();
140 $sql = "SELECT ".$this->getDatabaseTableAlias().".".$this->getDatabaseTableIndexName()." AS objectID
141 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
142 ".$this->sqlConditionJoins."
143 ".$this->getConditionBuilder()->__toString()."
144 ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
145 $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
146 $statement->execute($this->getConditionBuilder()->getParameters());
147 while ($row = $statement->fetchArray()) {
148 $this->objectIDs[] = $row['objectID'];
149 }
150 }
151
152 /**
153 * Reads the objects from database.
154 */
155 public function readObjects() {
156 if ($this->objectIDs !== null) {
157 if (empty($this->objectIDs)) {
158 return;
159 }
160 $sql = "SELECT ".(!empty($this->sqlSelects) ? $this->sqlSelects.($this->useQualifiedShorthand ? ',' : '') : '')."
161 ".($this->useQualifiedShorthand ? $this->getDatabaseTableAlias().'.*' : '')."
162 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
163 ".$this->sqlJoins."
164 WHERE ".$this->getDatabaseTableAlias().".".$this->getDatabaseTableIndexName()." IN (?".str_repeat(',?', count($this->objectIDs) - 1).")
165 ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
166 $statement = WCF::getDB()->prepareStatement($sql);
167 $statement->execute($this->objectIDs);
168 $this->objects = $statement->fetchObjects(($this->objectClassName ?: $this->className));
169 }
170 else {
171 $sql = "SELECT ".(!empty($this->sqlSelects) ? $this->sqlSelects.($this->useQualifiedShorthand ? ',' : '') : '')."
172 ".($this->useQualifiedShorthand ? $this->getDatabaseTableAlias().'.*' : '')."
173 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
174 ".$this->sqlJoins."
175 ".$this->getConditionBuilder()->__toString()."
176 ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
177 $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
178 $statement->execute($this->getConditionBuilder()->getParameters());
179 $this->objects = $statement->fetchObjects(($this->objectClassName ?: $this->className));
180 }
181
182 // use table index as array index
183 $objects = array();
184 foreach($this->objects as $object) {
185 $objectID = $object->{$this->getDatabaseTableIndexName()};
186 $objects[$objectID] = $object;
187
188 $this->indexToObject[] = $objectID;
189 }
190 $this->objectIDs = $this->indexToObject;
191 $this->objects = $objects;
192 }
193
194 /**
195 * Returns the object ids of the list.
196 *
197 * @return array<integer>
198 */
199 public function getObjectIDs() {
200 return $this->objectIDs;
201 }
202
203 /**
204 * Sets the object ids.
205 *
206 * @param array<integer> $objectIDs
207 */
208 public function setObjectIDs(array $objectIDs) {
209 $this->objectIDs = $objectIDs;
210 }
211
212 /**
213 * Returns the objects of the list.
214 *
215 * @return array<wcf\data\DatabaseObject>
216 */
217 public function getObjects() {
218 return $this->objects;
219 }
220
221 /**
222 * Returns the condition builder object.
223 *
224 * @return wcf\system\database\util\PreparedStatementConditionBuilder
225 */
226 public function getConditionBuilder() {
227 return $this->conditionBuilder;
228 }
229
230 /**
231 * Returns the name of the database table.
232 *
233 * @return string
234 */
235 public function getDatabaseTableName() {
236 return call_user_func(array($this->className, 'getDatabaseTableName'));
237 }
238
239 /**
240 * Returns the name of the database table.
241 *
242 * @return string
243 */
244 public function getDatabaseTableIndexName() {
245 return call_user_func(array($this->className, 'getDatabaseTableIndexName'));
246 }
247
248 /**
249 * Returns the name of the database table alias.
250 *
251 * @return string
252 */
253 public function getDatabaseTableAlias() {
254 return call_user_func(array($this->className, 'getDatabaseTableAlias'));
255 }
256
257 /**
258 * @see \Countable::count()
259 */
260 public function count() {
261 return count($this->objects);
262 }
263
264 /**
265 * @see \Iterator::current()
266 */
267 public function current() {
268 $objectID = $this->indexToObject[$this->index];
269 return $this->objects[$objectID];
270 }
271
272 /**
273 * CAUTION: This methods does not return the current iterator index,
274 * rather than the object key which maps to that index.
275 *
276 * @see \Iterator::key()
277 */
278 public function key() {
279 return $this->indexToObject[$this->index];
280 }
281
282 /**
283 * @see \Iterator::next()
284 */
285 public function next() {
286 ++$this->index;
287 }
288
289 /**
290 * @see \Iterator::rewind()
291 */
292 public function rewind() {
293 $this->index = 0;
294 }
295
296 /**
297 * @see \Iterator::valid()
298 */
299 public function valid() {
300 return isset($this->indexToObject[$this->index]);
301 }
302
303 /**
304 * @see \SeekableIterator::seek()
305 */
306 public function seek($index) {
307 $this->index = $index;
308
309 if (!$this->valid()) {
310 throw new \OutOfBoundsException();
311 }
312 }
313
314 /**
315 * @see wcf\data\ITraversableObject::seekTo()
316 */
317 public function seekTo($objectID) {
318 $this->index = array_search($objectID, $this->indexToObject);
319
320 if ($this->index === false) {
321 throw new SystemException("object id '".$objectID."' is invalid");
322 }
323 }
324
325 /**
326 * @see wcf\data\ITraversableObject::search()
327 */
328 public function search($objectID) {
329 try {
330 $this->seekTo($objectID);
331 return $this->current();
332 }
333 catch (SystemException $e) {
334 return null;
335 }
336 }
337 }