3 use wcf\system\database\util\PreparedStatementConditionBuilder
;
4 use wcf\system\exception\SystemException
;
6 use wcf\util\ClassUtil
;
9 * Abstract class for a list of database objects.
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
16 * @category Community Framework
18 abstract class DatabaseObjectList
implements \Countable
, ITraversableObject
{
23 public $className = '';
26 * class name of the object decorator; if left empty, no decorator is used
29 public $decoratorClassName = '';
35 public $objectClassName = '';
39 * @var array<wcf\data\DatabaseObject>
41 public $objects = array();
44 * ids of result objects
47 public $objectIDs = null;
53 public $sqlOffset = 0;
62 * sql order by statement
65 public $sqlOrderBy = '';
68 * sql select parameters
71 public $sqlSelects = '';
74 * sql select joins which are necessary for where statements
77 public $sqlConditionJoins = '';
83 public $sqlJoins = '';
86 * enables the automatic usage of the qualified shorthand
89 public $useQualifiedShorthand = true;
93 * @var wcf\system\database\util\PreparedStatementConditionBuilder
95 protected $conditionBuilder = null;
98 * current iterator index
101 protected $index = 0;
104 * list of index to object relation
105 * @var array<integer>
107 protected $indexToObject = null;
110 * Creates a new DatabaseObjectList object.
112 public function __construct() {
114 if (empty($this->className
)) {
115 $className = get_called_class();
117 if (mb_substr($className, -4) == 'List') {
118 $this->className
= mb_substr($className, 0, -4);
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'");
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."'");
135 $this->conditionBuilder
= new PreparedStatementConditionBuilder();
139 * Counts the number of objects.
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'];
155 * Reads the object ids from database.
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'];
172 * Reads the objects from database.
174 public function readObjects() {
175 if ($this->objectIDs
!== null) {
176 if (empty($this->objectIDs
)) {
179 $sql = "SELECT ".(!empty($this->sqlSelects
) ?
$this->sqlSelects
.($this->useQualifiedShorthand ?
',' : '') : '')."
180 ".($this->useQualifiedShorthand ?
$this->getDatabaseTableAlias().'.*' : '')."
181 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
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
));
190 $sql = "SELECT ".(!empty($this->sqlSelects
) ?
$this->sqlSelects
.($this->useQualifiedShorthand ?
',' : '') : '')."
191 ".($this->useQualifiedShorthand ?
$this->getDatabaseTableAlias().'.*' : '')."
192 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
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
));
202 if (!empty($this->decoratorClassName
)) {
203 foreach ($this->objects
as &$object) {
204 $object = new $this->decoratorClassName($object);
209 // use table index as array index
211 foreach ($this->objects
as $object) {
212 $objectID = $object->getObjectID();
213 $objects[$objectID] = $object;
215 $this->indexToObject
[] = $objectID;
217 $this->objectIDs
= $this->indexToObject
;
218 $this->objects
= $objects;
222 * Returns the object ids of the list.
224 * @return array<integer>
226 public function getObjectIDs() {
227 return $this->objectIDs
;
231 * Sets the object ids.
233 * @param array<integer> $objectIDs
235 public function setObjectIDs(array $objectIDs) {
236 $this->objectIDs
= $objectIDs;
240 * Returns the objects of the list.
242 * @return array<wcf\data\DatabaseObject>
244 public function getObjects() {
245 return $this->objects
;
249 * Returns the condition builder object.
251 * @return wcf\system\database\util\PreparedStatementConditionBuilder
253 public function getConditionBuilder() {
254 return $this->conditionBuilder
;
258 * Returns the name of the database table.
262 public function getDatabaseTableName() {
263 return call_user_func(array($this->className
, 'getDatabaseTableName'));
267 * Returns the name of the database table.
271 public function getDatabaseTableIndexName() {
272 return call_user_func(array($this->className
, 'getDatabaseTableIndexName'));
276 * Returns the name of the database table alias.
280 public function getDatabaseTableAlias() {
281 return call_user_func(array($this->className
, 'getDatabaseTableAlias'));
285 * @see \Countable::count()
287 public function count() {
288 return count($this->objects
);
292 * @see \Iterator::current()
294 public function current() {
295 $objectID = $this->indexToObject
[$this->index
];
296 return $this->objects
[$objectID];
300 * CAUTION: This methods does not return the current iterator index,
301 * rather than the object key which maps to that index.
303 * @see \Iterator::key()
305 public function key() {
306 return $this->indexToObject
[$this->index
];
310 * @see \Iterator::next()
312 public function next() {
317 * @see \Iterator::rewind()
319 public function rewind() {
324 * @see \Iterator::valid()
326 public function valid() {
327 return isset($this->indexToObject
[$this->index
]);
331 * @see \SeekableIterator::seek()
333 public function seek($index) {
334 $this->index
= $index;
336 if (!$this->valid()) {
337 throw new \
OutOfBoundsException();
342 * @see wcf\data\ITraversableObject::seekTo()
344 public function seekTo($objectID) {
345 $this->index
= array_search($objectID, $this->indexToObject
);
347 if ($this->index
=== false) {
348 throw new SystemException("object id '".$objectID."' is invalid");
353 * @see wcf\data\ITraversableObject::search()
355 public function search($objectID) {
357 $this->seekTo($objectID);
358 return $this->current();
360 catch (SystemException
$e) {