Cleans up package
[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 (!count($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 * Returns the objects of the list.
205 *
206 * @return array<wcf\data\DatabaseObject>
207 */
208 public function getObjects() {
209 return $this->objects;
210 }
211
212 /**
213 * Returns the condition builder object.
214 *
215 * @return wcf\system\database\util\PreparedStatementConditionBuilder
216 */
217 public function getConditionBuilder() {
218 return $this->conditionBuilder;
219 }
220
221 /**
222 * Returns the name of the database table.
223 *
224 * @return string
225 */
226 public function getDatabaseTableName() {
227 return call_user_func(array($this->className, 'getDatabaseTableName'));
228 }
229
230 /**
231 * Returns the name of the database table.
232 *
233 * @return string
234 */
235 public function getDatabaseTableIndexName() {
236 return call_user_func(array($this->className, 'getDatabaseTableIndexName'));
237 }
238
239 /**
240 * Returns the name of the database table alias.
241 *
242 * @return string
243 */
244 public function getDatabaseTableAlias() {
245 return call_user_func(array($this->className, 'getDatabaseTableAlias'));
246 }
247
248 /**
249 * @see \Countable::count()
250 */
251 public function count() {
252 return count($this->objects);
253 }
254
255 /**
256 * @see \Iterator::current()
257 */
258 public function current() {
259 $objectID = $this->indexToObject[$this->index];
260 return $this->objects[$objectID];
261 }
262
263 /**
264 * CAUTION: This methods does not return the current iterator index,
265 * rather than the object key which maps to that index.
266 *
267 * @see \Iterator::key()
268 */
269 public function key() {
270 return $this->indexToObject[$this->index];
271 }
272
273 /**
274 * @see \Iterator::next()
275 */
276 public function next() {
277 ++$this->index;
278 }
279
280 /**
281 * @see \Iterator::rewind()
282 */
283 public function rewind() {
284 $this->index = 0;
285 }
286
287 /**
288 * @see \Iterator::valid()
289 */
290 public function valid() {
291 return isset($this->indexToObject[$this->index]);
292 }
293
294 /**
295 * @see \SeekableIterator::seek()
296 */
297 public function seek($index) {
298 $this->index = $index;
299
300 if (!$this->valid()) {
301 throw new \OutOfBoundsException();
302 }
303 }
304
305 /**
306 * @see wcf\data\ITraversableObject::seekTo()
307 */
308 public function seekTo($objectID) {
309 $this->index = array_search($objectID, $this->indexToObject);
310
311 if ($this->index === false) {
312 throw new SystemException("object id '".$objectID."' is invalid");
313 }
314 }
315
316 /**
317 * @see wcf\data\ITraversableObject::search()
318 */
319 public function search($objectID) {
320 try {
321 $this->seekTo($objectID);
322 return $this->current();
323 }
324 catch (SystemException $e) {
325 return null;
326 }
327 }
328 }