3 use wcf\system\database\util\PreparedStatementConditionBuilder
;
4 use wcf\system\exception\SystemException
;
6 use wcf\util\ClassUtil
;
7 use wcf\util\StringUtil
;
10 * Abstract class for a list of database objects.
13 * @copyright 2001-2013 WoltLab GmbH
14 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15 * @package com.woltlab.wcf
17 * @category Community Framework
19 abstract class DatabaseObjectList
implements \Countable
, ITraversableObject
{
24 public $className = '';
27 * class name of the object decorator; if left empty, no decorator is used
30 public $decoratorClassName = '';
36 public $objectClassName = '';
40 * @var array<wcf\data\DatabaseObject>
42 public $objects = array();
45 * ids of result objects
48 public $objectIDs = null;
54 public $sqlOffset = 0;
63 * sql order by statement
66 public $sqlOrderBy = '';
69 * sql select parameters
72 public $sqlSelects = '';
75 * sql select joins which are necessary for where statements
78 public $sqlConditionJoins = '';
84 public $sqlJoins = '';
87 * enables the automatic usage of the qualified shorthand
90 public $useQualifiedShorthand = true;
94 * @var wcf\system\database\util\PreparedStatementConditionBuilder
96 protected $conditionBuilder = null;
99 * current iterator index
102 protected $index = 0;
105 * list of index to object relation
106 * @var array<integer>
108 protected $indexToObject = null;
111 * Creates a new DatabaseObjectList object.
113 public function __construct() {
115 if (empty($this->className
)) {
116 $className = get_called_class();
118 if (StringUtil
::substring($className, -4) == 'List') {
119 $this->className
= StringUtil
::substring($className, 0, -4);
123 if (!empty($this->decoratorClassName
)) {
124 // validate decorator class name
125 if (!ClassUtil
::isInstanceOf($this->decoratorClassName
, 'wcf\data\DatabaseObjectDecorator')) {
126 throw new SystemException("'".$this->decoratorClassName
."' should extend 'wcf\data\DatabaseObjectDecorator'");
129 $objectClassName = $this->objectClassName ?
: $this->className
;
130 $baseClassName = call_user_func(array($this->decoratorClassName
, 'getBaseClass'));
131 if ($objectClassName != $baseClassName && !ClassUtil
::isInstanceOf($baseClassName, $objectClassName)) {
132 throw new SystemException("'".$this->decoratorClassName
."' can't decorate objects of class '".$objectClassName."'");
136 $this->conditionBuilder
= new PreparedStatementConditionBuilder();
140 * Counts the number of objects.
144 public function countObjects() {
145 $sql = "SELECT COUNT(*) AS count
146 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
147 ".$this->sqlConditionJoins
."
148 ".$this->getConditionBuilder();
149 $statement = WCF
::getDB()->prepareStatement($sql);
150 $statement->execute($this->getConditionBuilder()->getParameters());
151 $row = $statement->fetchArray();
152 return $row['count'];
156 * Reads the object ids from database.
158 public function readObjectIDs() {
159 $this->objectIDs
= array();
160 $sql = "SELECT ".$this->getDatabaseTableAlias().".".$this->getDatabaseTableIndexName()." AS objectID
161 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
162 ".$this->sqlConditionJoins
."
163 ".$this->getConditionBuilder()."
164 ".(!empty($this->sqlOrderBy
) ?
"ORDER BY ".$this->sqlOrderBy
: '');
165 $statement = WCF
::getDB()->prepareStatement($sql, $this->sqlLimit
, $this->sqlOffset
);
166 $statement->execute($this->getConditionBuilder()->getParameters());
167 while ($row = $statement->fetchArray()) {
168 $this->objectIDs
[] = $row['objectID'];
173 * Reads the objects from database.
175 public function readObjects() {
176 if ($this->objectIDs
!== null) {
177 if (empty($this->objectIDs
)) {
180 $sql = "SELECT ".(!empty($this->sqlSelects
) ?
$this->sqlSelects
.($this->useQualifiedShorthand ?
',' : '') : '')."
181 ".($this->useQualifiedShorthand ?
$this->getDatabaseTableAlias().'.*' : '')."
182 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
184 WHERE ".$this->getDatabaseTableAlias().".".$this->getDatabaseTableIndexName()." IN (?".str_repeat(',?', count($this->objectIDs
) - 1).")
185 ".(!empty($this->sqlOrderBy
) ?
"ORDER BY ".$this->sqlOrderBy
: '');
186 $statement = WCF
::getDB()->prepareStatement($sql);
187 $statement->execute($this->objectIDs
);
188 $this->objects
= $statement->fetchObjects(($this->objectClassName ?
: $this->className
));
191 $sql = "SELECT ".(!empty($this->sqlSelects
) ?
$this->sqlSelects
.($this->useQualifiedShorthand ?
',' : '') : '')."
192 ".($this->useQualifiedShorthand ?
$this->getDatabaseTableAlias().'.*' : '')."
193 FROM ".$this->getDatabaseTableName()." ".$this->getDatabaseTableAlias()."
195 ".$this->getConditionBuilder()."
196 ".(!empty($this->sqlOrderBy
) ?
"ORDER BY ".$this->sqlOrderBy
: '');
197 $statement = WCF
::getDB()->prepareStatement($sql, $this->sqlLimit
, $this->sqlOffset
);
198 $statement->execute($this->getConditionBuilder()->getParameters());
199 $this->objects
= $statement->fetchObjects(($this->objectClassName ?
: $this->className
));
203 if (!empty($this->decoratorClassName
)) {
204 foreach ($this->objects
as &$object) {
205 $object = new $this->decoratorClassName($object);
210 // use table index as array index
212 foreach ($this->objects
as $object) {
213 $objectID = $object->getObjectID();
214 $objects[$objectID] = $object;
216 $this->indexToObject
[] = $objectID;
218 $this->objectIDs
= $this->indexToObject
;
219 $this->objects
= $objects;
223 * Returns the object ids of the list.
225 * @return array<integer>
227 public function getObjectIDs() {
228 return $this->objectIDs
;
232 * Sets the object ids.
234 * @param array<integer> $objectIDs
236 public function setObjectIDs(array $objectIDs) {
237 $this->objectIDs
= $objectIDs;
241 * Returns the objects of the list.
243 * @return array<wcf\data\DatabaseObject>
245 public function getObjects() {
246 return $this->objects
;
250 * Returns the condition builder object.
252 * @return wcf\system\database\util\PreparedStatementConditionBuilder
254 public function getConditionBuilder() {
255 return $this->conditionBuilder
;
259 * Returns the name of the database table.
263 public function getDatabaseTableName() {
264 return call_user_func(array($this->className
, 'getDatabaseTableName'));
268 * Returns the name of the database table.
272 public function getDatabaseTableIndexName() {
273 return call_user_func(array($this->className
, 'getDatabaseTableIndexName'));
277 * Returns the name of the database table alias.
281 public function getDatabaseTableAlias() {
282 return call_user_func(array($this->className
, 'getDatabaseTableAlias'));
286 * @see \Countable::count()
288 public function count() {
289 return count($this->objects
);
293 * @see \Iterator::current()
295 public function current() {
296 $objectID = $this->indexToObject
[$this->index
];
297 return $this->objects
[$objectID];
301 * CAUTION: This methods does not return the current iterator index,
302 * rather than the object key which maps to that index.
304 * @see \Iterator::key()
306 public function key() {
307 return $this->indexToObject
[$this->index
];
311 * @see \Iterator::next()
313 public function next() {
318 * @see \Iterator::rewind()
320 public function rewind() {
325 * @see \Iterator::valid()
327 public function valid() {
328 return isset($this->indexToObject
[$this->index
]);
332 * @see \SeekableIterator::seek()
334 public function seek($index) {
335 $this->index
= $index;
337 if (!$this->valid()) {
338 throw new \
OutOfBoundsException();
343 * @see wcf\data\ITraversableObject::seekTo()
345 public function seekTo($objectID) {
346 $this->index
= array_search($objectID, $this->indexToObject
);
348 if ($this->index
=== false) {
349 throw new SystemException("object id '".$objectID."' is invalid");
354 * @see wcf\data\ITraversableObject::search()
356 public function search($objectID) {
358 $this->seekTo($objectID);
359 return $this->current();
361 catch (SystemException
$e) {