Merge pull request #680 from Gravatronics/enhancement/formDataLangVar
[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-2011 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 //if (!empty($this->sqlSelects)) die("x".$this->sqlSelects);
172 $sql = "SELECT ".(!empty($this->sqlSelects) ? $this->sqlSelects.($this->useQualifiedShorthand ? ',' : '') : '')."
173 ".($this->useQualifiedShorthand ? $this->getDatabaseTableAlias().'.*' : '')."
174 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
175 ".$this->sqlJoins."
176 ".$this->getConditionBuilder()->__toString()."
177 ".(!empty($this->sqlOrderBy) ? "ORDER BY ".$this->sqlOrderBy : '');
178 $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
179 $statement->execute($this->getConditionBuilder()->getParameters());
180 $this->objects = $statement->fetchObjects(($this->objectClassName ?: $this->className));
181 }
182
183 // use table index as array index
184 $objects = array();
185 foreach($this->objects as $object) {
186 $objectID = $object->{$this->getDatabaseTableIndexName()};
187 $objects[$objectID] = $object;
188
189 $this->indexToObject[] = $objectID;
190 }
191 $this->objectIDs = $this->indexToObject;
192 $this->objects = $objects;
193 }
194
195 /**
196 * Returns the object ids of the list.
197 *
198 * @return array<integer>
199 */
200 public function getObjectIDs() {
201 return $this->objectIDs;
202 }
203
204 /**
205 * Returns the objects of the list.
206 *
207 * @return array<wcf\data\DatabaseObject>
208 */
209 public function getObjects() {
210 return $this->objects;
211 }
212
213 /**
214 * Returns the condition builder object.
215 *
216 * @return wcf\system\database\util\PreparedStatementConditionBuilder
217 */
218 public function getConditionBuilder() {
219 return $this->conditionBuilder;
220 }
221
222 /**
223 * Returns the name of the database table.
224 *
225 * @return string
226 */
227 public function getDatabaseTableName() {
228 return call_user_func(array($this->className, 'getDatabaseTableName'));
229 }
230
231 /**
232 * Returns the name of the database table.
233 *
234 * @return string
235 */
236 public function getDatabaseTableIndexName() {
237 return call_user_func(array($this->className, 'getDatabaseTableIndexName'));
238 }
239
240 /**
241 * Returns the name of the database table alias.
242 *
243 * @return string
244 */
245 public function getDatabaseTableAlias() {
246 return call_user_func(array($this->className, 'getDatabaseTableAlias'));
247 }
248
249 /**
250 * @see \Countable::count()
251 */
252 public function count() {
253 return count($this->objects);
254 }
255
256 /**
257 * @see \Iterator::current()
258 */
259 public function current() {
260 $objectID = $this->indexToObject[$this->index];
261 return $this->objects[$objectID];
262 }
263
264 /**
265 * CAUTION: This methods does not return the current iterator index,
266 * rather than the object key which maps to that index.
267 *
268 * @see \Iterator::key()
269 */
270 public function key() {
271 return $this->indexToObject[$this->index];
272 }
273
274 /**
275 * @see \Iterator::next()
276 */
277 public function next() {
278 ++$this->index;
279 }
280
281 /**
282 * @see \Iterator::rewind()
283 */
284 public function rewind() {
285 $this->index = 0;
286 }
287
288 /**
289 * @see \Iterator::valid()
290 */
291 public function valid() {
292 return isset($this->indexToObject[$this->index]);
293 }
294
295 /**
296 * @see \SeekableIterator::seek()
297 */
298 public function seek($index) {
299 $this->index = $index;
300
301 if (!$this->valid()) {
302 throw new \OutOfBoundsException();
303 }
304 }
305
306 /**
307 * @see wcf\data\ITraversableObject::seekTo()
308 */
309 public function seekTo($objectID) {
310 $this->index = array_search($objectID, $this->indexToObject);
311
312 if ($this->index === false) {
313 throw new SystemException("object id '".$objectID."' is invalid");
314 }
315 }
316
317 /**
318 * @see wcf\data\ITraversableObject::search()
319 */
320 public function search($objectID) {
321 try {
322 $this->seekTo($objectID);
323 return $this->current();
324 }
325 catch (SystemException $e) {
326 return null;
327 }
328 }
329 }