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