5 use wcf\system\database\util\PreparedStatementConditionBuilder
;
6 use wcf\system\event\EventHandler
;
7 use wcf\system\exception\SystemException
;
11 * Abstract class for a list of database objects.
14 * @copyright 2001-2019 WoltLab GmbH
15 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
16 * @package WoltLabSuite\Core\Data
18 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 DatabaseObject[]
45 * ids of result objects
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 PreparedStatementConditionBuilder
96 protected $conditionBuilder;
99 * current iterator index
102 protected $index = 0;
105 * list of index to object relation
108 protected $indexToObject;
111 * Creates a new DatabaseObjectList object.
113 public function __construct()
116 if (empty($this->className
)) {
117 $className = \
get_called_class();
119 if (\
mb_substr($className, -4) == 'List') {
120 $this->className
= \
mb_substr($className, 0, -4);
124 if (!empty($this->decoratorClassName
)) {
125 // validate decorator class name
126 if (!\
is_subclass_of($this->decoratorClassName
, DatabaseObjectDecorator
::class)) {
127 throw new SystemException("'" . $this->decoratorClassName
. "' should extend '" . DatabaseObjectDecorator
::class . "'");
130 $objectClassName = $this->objectClassName ?
: $this->className
;
131 $baseClassName = \
call_user_func([$this->decoratorClassName
, 'getBaseClass']);
132 if ($objectClassName != $baseClassName && !\
is_subclass_of($baseClassName, $objectClassName)) {
133 throw new SystemException("'" . $this->decoratorClassName
. "' can't decorate objects of class '" . $objectClassName . "'");
137 $this->conditionBuilder
= new PreparedStatementConditionBuilder();
139 EventHandler
::getInstance()->fireAction($this, 'init');
143 * Counts the number of objects.
147 public function countObjects()
149 $sql = "SELECT COUNT(*)
150 FROM " . $this->getDatabaseTableName() . " " . $this->getDatabaseTableAlias() . "
151 " . $this->sqlConditionJoins
. "
152 " . $this->getConditionBuilder();
153 $statement = WCF
::getDB()->prepareStatement($sql);
154 $statement->execute($this->getConditionBuilder()->getParameters());
156 return $statement->fetchSingleColumn();
160 * Reads the object ids from database.
162 public function readObjectIDs()
164 $this->objectIDs
= [];
165 $sql = "SELECT " . $this->getDatabaseTableAlias() . "." . $this->getDatabaseTableIndexName() . " AS objectID
166 FROM " . $this->getDatabaseTableName() . " " . $this->getDatabaseTableAlias() . "
167 " . $this->sqlConditionJoins
. "
168 " . $this->getConditionBuilder() . "
169 " . (!empty($this->sqlOrderBy
) ?
"ORDER BY " . $this->sqlOrderBy
: '');
170 $statement = WCF
::getDB()->prepareStatement($sql, $this->sqlLimit
, $this->sqlOffset
);
171 $statement->execute($this->getConditionBuilder()->getParameters());
172 $this->objectIDs
= $statement->fetchAll(\PDO
::FETCH_COLUMN
);
176 * Reads the objects from database.
178 public function readObjects()
180 if ($this->objectIDs
!== null) {
181 if (empty($this->objectIDs
)) {
185 $objectIdPlaceholder = "?" . \
str_repeat(',?', \
count($this->objectIDs
) - 1);
187 $sql = "SELECT " . (!empty($this->sqlSelects
) ?
$this->sqlSelects
. ($this->useQualifiedShorthand ?
',' : '') : '') . "
188 " . ($this->useQualifiedShorthand ?
$this->getDatabaseTableAlias() . '.*' : '') . "
189 FROM " . $this->getDatabaseTableName() . " " . $this->getDatabaseTableAlias() . "
190 " . $this->sqlJoins
. "
191 WHERE " . $this->getDatabaseTableAlias() . "." . $this->getDatabaseTableIndexName() . " IN ({$objectIdPlaceholder})
192 " . (!empty($this->sqlOrderBy
) ?
"ORDER BY " . $this->sqlOrderBy
: '');
193 $statement = WCF
::getDB()->prepareStatement($sql);
194 $statement->execute($this->objectIDs
);
195 $this->objects
= $statement->fetchObjects(($this->objectClassName ?
: $this->className
));
197 $sql = "SELECT " . (!empty($this->sqlSelects
) ?
$this->sqlSelects
. ($this->useQualifiedShorthand ?
',' : '') : '') . "
198 " . ($this->useQualifiedShorthand ?
$this->getDatabaseTableAlias() . '.*' : '') . "
199 FROM " . $this->getDatabaseTableName() . " " . $this->getDatabaseTableAlias() . "
200 " . $this->sqlJoins
. "
201 " . $this->getConditionBuilder() . "
202 " . (!empty($this->sqlOrderBy
) ?
"ORDER BY " . $this->sqlOrderBy
: '');
203 $statement = WCF
::getDB()->prepareStatement($sql, $this->sqlLimit
, $this->sqlOffset
);
204 $statement->execute($this->getConditionBuilder()->getParameters());
205 $this->objects
= $statement->fetchObjects(($this->objectClassName ?
: $this->className
));
209 if (!empty($this->decoratorClassName
)) {
210 foreach ($this->objects
as &$object) {
211 $object = new $this->decoratorClassName($object);
216 // use table index as array index
217 $objects = $this->indexToObject
= [];
218 foreach ($this->objects
as $object) {
219 $objectID = $object->getObjectID();
220 $objects[$objectID] = $object;
222 $this->indexToObject
[] = $objectID;
224 $this->objectIDs
= $this->indexToObject
;
225 $this->objects
= $objects;
229 * Returns the object ids of the list.
233 public function getObjectIDs()
235 return $this->objectIDs
;
239 * Sets the object ids.
241 * @param int[] $objectIDs
243 public function setObjectIDs(array $objectIDs)
245 $this->objectIDs
= \array_merge
($objectIDs);
249 * Returns the objects of the list.
251 * @return DatabaseObject[]
253 public function getObjects()
255 return $this->objects
;
259 * Returns the condition builder object.
261 * @return PreparedStatementConditionBuilder
263 public function getConditionBuilder()
265 return $this->conditionBuilder
;
269 * Sets the condition builder dynamically.
271 * @param PreparedStatementConditionBuilder $conditionBuilder
274 public function setConditionBuilder(PreparedStatementConditionBuilder
$conditionBuilder)
276 $this->conditionBuilder
= $conditionBuilder;
280 * Returns the name of the database table.
284 public function getDatabaseTableName()
286 return \
call_user_func([$this->className
, 'getDatabaseTableName']);
290 * Returns the name of the database table.
294 public function getDatabaseTableIndexName()
296 return \
call_user_func([$this->className
, 'getDatabaseTableIndexName']);
300 * Returns the name of the database table alias.
304 public function getDatabaseTableAlias()
306 return \
call_user_func([$this->className
, 'getDatabaseTableAlias']);
312 public function count()
314 return \
count($this->objects
);
320 public function current()
322 $objectID = $this->indexToObject
[$this->index
];
324 return $this->objects
[$objectID];
328 * CAUTION: This methods does not return the current iterator index,
329 * but the object key which maps to that index.
331 * @see \Iterator::key()
333 public function key()
335 return $this->indexToObject
[$this->index
];
341 public function next()
349 public function rewind()
357 public function valid()
359 return isset($this->indexToObject
[$this->index
]);
365 public function seek($index)
367 $this->index
= $index;
369 if (!$this->valid()) {
370 throw new \
OutOfBoundsException();
377 public function seekTo($objectID)
379 $this->index
= \array_search
($objectID, $this->indexToObject
);
381 if ($this->index
=== false) {
382 throw new SystemException("object id '" . $objectID . "' is invalid");
389 public function search($objectID)
392 $this->seekTo($objectID);
394 return $this->current();
395 } catch (SystemException
$e) {
401 * Returns the only object in this list or `null` if the list is empty.
403 * @return DatabaseObject|null
404 * @throws \BadMethodCallException if list contains more than one object
406 public function getSingleObject()
408 if (\
count($this->objects
) > 1) {
409 throw new \
BadMethodCallException("Cannot get a single object when the list contains " . \
count($this->objects
) . " objects.");
412 if (empty($this->objects
)) {
416 return \reset
($this->objects
);