diff --git a/src/ORM/ArrayList.php b/src/ORM/ArrayList.php index 74a7cd600..d8dca3195 100644 --- a/src/ORM/ArrayList.php +++ b/src/ORM/ArrayList.php @@ -2,8 +2,8 @@ namespace SilverStripe\ORM; -use ArrayIterator; use InvalidArgumentException; +use Iterator; use LogicException; use SilverStripe\Dev\Debug; use SilverStripe\Dev\Deprecation; @@ -103,19 +103,16 @@ class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, L /** * Returns an Iterator for this ArrayList. * This function allows you to use ArrayList in foreach loops - * - * @return ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { - $items = array_map( - function ($item) { - return is_array($item) ? new ArrayData($item) : $item; - }, - $this->items ?? [] - ); - return new ArrayIterator($items); + foreach ($this->items as $i => $item) { + if (is_array($item)) { + yield new ArrayData($item); + } else { + yield $item; + } + } } /** diff --git a/src/ORM/Connect/DBSchemaManager.php b/src/ORM/Connect/DBSchemaManager.php index 585fd8187..7e3eb3e63 100644 --- a/src/ORM/Connect/DBSchemaManager.php +++ b/src/ORM/Connect/DBSchemaManager.php @@ -380,7 +380,7 @@ abstract class DBSchemaManager if ($dbID && isset($options[$dbID])) { if (preg_match('/ENGINE=([^\s]*)/', $options[$dbID] ?? '', $alteredEngineMatches)) { $alteredEngine = $alteredEngineMatches[1]; - $tableStatus = $this->query(sprintf('SHOW TABLE STATUS LIKE \'%s\'', $table))->first(); + $tableStatus = $this->query(sprintf('SHOW TABLE STATUS LIKE \'%s\'', $table))->record(); $tableOptionsChanged = ($tableStatus['Engine'] != $alteredEngine); } } diff --git a/src/ORM/Connect/MySQLQuery.php b/src/ORM/Connect/MySQLQuery.php index c81138643..d01916701 100644 --- a/src/ORM/Connect/MySQLQuery.php +++ b/src/ORM/Connect/MySQLQuery.php @@ -2,6 +2,8 @@ namespace SilverStripe\ORM\Connect; +use Iterator; + /** * A result-set from a MySQL database (using MySQLiConnector) * Note that this class is only used for the results of non-prepared statements @@ -45,16 +47,13 @@ class MySQLQuery extends Query } } - public function seek($row) + public function getIterator(): Iterator { if (is_object($this->handle)) { - // Fix for https://github.com/silverstripe/silverstripe-framework/issues/9097 without breaking the seek() API - $this->handle->data_seek($row); - $result = $this->nextRecord(); - $this->handle->data_seek($row); - return $result; + while ($data = $this->handle->fetch_assoc()) { + yield $data; + } } - return null; } public function numRecords() @@ -62,27 +61,7 @@ class MySQLQuery extends Query if (is_object($this->handle)) { return $this->handle->num_rows; } + return null; } - - public function nextRecord() - { - $floatTypes = [MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL]; - - if (is_object($this->handle) && ($row = $this->handle->fetch_array(MYSQLI_NUM))) { - $data = []; - foreach ($row as $i => $value) { - if (!isset($this->columns[$i])) { - throw new DatabaseException("Can't get metadata for column $i"); - } - if (in_array($this->columns[$i]->type, $floatTypes ?? [])) { - $value = (float)$value; - } - $data[$this->columns[$i]->name] = $value; - } - return $data; - } else { - return false; - } - } } diff --git a/src/ORM/Connect/MySQLStatement.php b/src/ORM/Connect/MySQLStatement.php index 7bc54765f..eada8ad40 100644 --- a/src/ORM/Connect/MySQLStatement.php +++ b/src/ORM/Connect/MySQLStatement.php @@ -2,6 +2,7 @@ namespace SilverStripe\ORM\Connect; +use Iterator; use mysqli_result; use mysqli_stmt; @@ -56,6 +57,26 @@ class MySQLStatement extends Query */ protected $boundValues = []; + /** + * Hook the result-set given into a Query class, suitable for use by SilverStripe. + * @param mysqli_stmt $statement The related statement, if present + * @param mysqli_result $metadata The metadata for this statement + */ + public function __construct($statement, $metadata) + { + $this->statement = $statement; + $this->metadata = $metadata; + + // Immediately bind and buffer + $this->bind(); + } + + public function __destruct() + { + $this->statement->close(); + $this->currentRecord = false; + } + /** * Binds this statement to the variables */ @@ -82,58 +103,20 @@ class MySQLStatement extends Query call_user_func_array([$this->statement, 'bind_result'], $variables ?? []); } - /** - * Hook the result-set given into a Query class, suitable for use by SilverStripe. - * @param mysqli_stmt $statement The related statement, if present - * @param mysqli_result $metadata The metadata for this statement - */ - public function __construct($statement, $metadata) + public function getIterator(): Iterator { - $this->statement = $statement; - $this->metadata = $metadata; - - // Immediately bind and buffer - $this->bind(); - } - - public function __destruct() - { - $this->statement->close(); - $this->currentRecord = false; - } - - public function seek($row) - { - $this->rowNum = $row - 1; - - // Fix for https://github.com/silverstripe/silverstripe-framework/issues/9097 without breaking the seek() API - $this->statement->data_seek($row); - $result = $this->next(); - $this->statement->data_seek($row); - return $result; + while ($this->statement->fetch()) { + // Dereferenced row + $row = []; + foreach ($this->boundValues as $key => $value) { + $row[$key] = $value; + } + yield $row; + } } public function numRecords() { return $this->statement->num_rows(); } - - public function nextRecord() - { - // Skip data if out of data - if (!$this->statement->fetch()) { - return false; - } - - // Dereferenced row - $row = []; - foreach ($this->boundValues as $key => $value) { - $floatTypes = [MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL]; - if (in_array($this->types[$key], $floatTypes ?? [])) { - $value = (float)$value; - } - $row[$key] = $value; - } - return $row; - } } diff --git a/src/ORM/Connect/PDOQuery.php b/src/ORM/Connect/PDOQuery.php index 7180de28d..24f9712eb 100644 --- a/src/ORM/Connect/PDOQuery.php +++ b/src/ORM/Connect/PDOQuery.php @@ -2,6 +2,9 @@ namespace SilverStripe\ORM\Connect; +use ArrayIterator; +use Iterator; + /** * A result-set from a PDO database. */ @@ -14,7 +17,7 @@ class PDOQuery extends Query /** * Hook the result-set given into a Query class, suitable for use by SilverStripe. - * @param PDOStatement $statement The internal PDOStatement containing the results + * @param PDOStatementHandle $statement The internal PDOStatement containing the results */ public function __construct(PDOStatementHandle $statement) { @@ -26,25 +29,13 @@ class PDOQuery extends Query $statement->closeCursor(); } - public function seek($row) + public function getIterator(): Iterator { - $this->rowNum = $row - 1; - return $this->nextRecord(); + return new ArrayIterator($this->results); } public function numRecords() { - return count($this->results ?? []); - } - - public function nextRecord() - { - $index = $this->rowNum + 1; - - if (isset($this->results[$index])) { - return $this->results[$index]; - } else { - return false; - } + return count($this->results); } } diff --git a/src/ORM/Connect/Query.php b/src/ORM/Connect/Query.php index af9e0c3a5..8aac231b2 100644 --- a/src/ORM/Connect/Query.php +++ b/src/ORM/Connect/Query.php @@ -27,30 +27,9 @@ use Iterator; * on providing the specific data-access methods that are required: {@link nextRecord()}, {@link numRecords()} * and {@link seek()} */ -abstract class Query implements Iterator +abstract class Query implements \IteratorAggregate { - /** - * The current record in the iterator. - * - * @var array - */ - protected $currentRecord = null; - - /** - * The number of the current row in the iterator. - * - * @var int - */ - protected $rowNum = -1; - - /** - * Flag to keep track of whether iteration has begun, to prevent unnecessary seeks - * - * @var bool - */ - protected $queryHasBegun = false; - /** * Return an array containing all the values from a specific column. If no column is set, then the first will be * returned @@ -62,7 +41,7 @@ abstract class Query implements Iterator { $result = []; - while ($record = $this->next()) { + foreach ($this as $record) { if ($column) { $result[] = $record[$column]; } else { @@ -82,6 +61,7 @@ abstract class Query implements Iterator public function keyedColumn() { $column = []; + foreach ($this as $record) { $val = $record[key($record)]; $column[$val] = $val; @@ -106,13 +86,22 @@ abstract class Query implements Iterator } /** - * Returns the next record in the iterator. + * Returns the first record in the result * * @return array */ public function record() { - return $this->next(); + return $this->getIterator()->current(); + } + + /** + * @deprecated Use record() instead + * @return array + */ + public function first() + { + return $this->record(); } /** @@ -122,7 +111,7 @@ abstract class Query implements Iterator */ public function value() { - $record = $this->next(); + $record = $this->record(); if ($record) { return $record[key($record)]; } @@ -164,94 +153,10 @@ abstract class Query implements Iterator return $result; } - /** - * Iterator function implementation. Rewind the iterator to the first item and return it. - * Makes use of {@link seek()} and {@link numRecords()}, takes care of the plumbing. - * - * @return void - */ - #[\ReturnTypeWillChange] - public function rewind() - { - if ($this->queryHasBegun && $this->numRecords() > 0) { - $this->queryHasBegun = false; - $this->currentRecord = null; - $this->seek(0); - } - } - - /** - * Iterator function implementation. Return the current item of the iterator. - * - * @return array - */ - #[\ReturnTypeWillChange] - public function current() - { - if (!$this->currentRecord) { - return $this->next(); - } else { - return $this->currentRecord; - } - } - - /** - * Iterator function implementation. Return the first item of this iterator. - * - * @return array - */ - public function first() - { - $this->rewind(); - return $this->current(); - } - - /** - * Iterator function implementation. Return the row number of the current item. - * - * @return int - */ - #[\ReturnTypeWillChange] - public function key() - { - return $this->rowNum; - } - - /** - * Iterator function implementation. Return the next record in the iterator. - * Makes use of {@link nextRecord()}, takes care of the plumbing. - * - * @return array - */ - #[\ReturnTypeWillChange] - public function next() - { - $this->queryHasBegun = true; - $this->currentRecord = $this->nextRecord(); - $this->rowNum++; - return $this->currentRecord; - } - - /** - * Iterator function implementation. Check if the iterator is pointing to a valid item. - * - * @return bool - */ - #[\ReturnTypeWillChange] - public function valid() - { - if (!$this->queryHasBegun) { - $this->next(); - } - return $this->currentRecord !== false; - } - /** * Return the next record in the query result. - * - * @return array */ - abstract public function nextRecord(); + abstract public function getIterator(): Iterator; /** * Return the total number of items in the query result. @@ -259,12 +164,4 @@ abstract class Query implements Iterator * @return int */ abstract public function numRecords(); - - /** - * Go to a specific row number in the query result and return the record. - * - * @param int $rowNum Row number to go to. - * @return array - */ - abstract public function seek($rowNum); } diff --git a/src/ORM/DataList.php b/src/ORM/DataList.php index 72dfaf02e..124ceefb0 100644 --- a/src/ORM/DataList.php +++ b/src/ORM/DataList.php @@ -6,10 +6,12 @@ use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\Debug; use SilverStripe\ORM\Filters\SearchFilter; use SilverStripe\ORM\Queries\SQLConditionGroup; +use SilverStripe\View\TemplateIterator; use SilverStripe\View\ViewableData; use ArrayIterator; use Exception; use InvalidArgumentException; +use Iterator; use LogicException; /** @@ -49,6 +51,13 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li */ protected $dataQuery; + /** + * A cached Query to save repeated database calls. {@see DataList::getTemplateIteratorCount()} + * + * @var SilverStripe\ORM\Connect\Query + */ + protected $finalisedQuery; + /** * Create a new DataList. * No querying is done on construction, but the initial query schema is set up. @@ -79,6 +88,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li public function __clone() { $this->dataQuery = clone $this->dataQuery; + $this->finalisedQuery = null; } /** @@ -863,13 +873,32 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li /** * Returns an Iterator for this DataList. * This function allows you to use DataLists in foreach loops - * - * @return ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { - return new ArrayIterator($this->toArray()); + foreach ($this->getFinalisedQuery() as $row) { + yield $this->createDataObject($row); + } + + // Re-set the finaliseQuery so that it can be re-executed + $this->finalisedQuery = null; + } + + /** + * Returns the Query result for this DataList. Repeated calls will return + * a cached result, unless the DataQuery underlying this list has been + * modified + * + * @return SilverStripe\ORM\Connect\Query + * @internal This API may change in minor releases + */ + protected function getFinalisedQuery() + { + if (!$this->finalisedQuery) { + $this->finalisedQuery = $this->dataQuery->query()->execute(); + } + + return $this->finalisedQuery; } /** @@ -880,6 +909,10 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li #[\ReturnTypeWillChange] public function count() { + if ($this->finalisedQuery) { + return $this->finalisedQuery->numRecords(); + } + return $this->dataQuery->count(); } @@ -1027,8 +1060,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li */ public function column($colName = "ID") { - $dataQuery = clone $this->dataQuery; - return $dataQuery->distinct(false)->column($colName); + return $this->dataQuery->distinct(false)->column($colName); } /** diff --git a/src/ORM/ListDecorator.php b/src/ORM/ListDecorator.php index d3f006e64..f34e5ea5a 100644 --- a/src/ORM/ListDecorator.php +++ b/src/ORM/ListDecorator.php @@ -2,6 +2,7 @@ namespace SilverStripe\ORM; +use Iterator; use SilverStripe\View\ViewableData; use LogicException; @@ -96,8 +97,7 @@ abstract class ListDecorator extends ViewableData implements SS_List, Sortable, $this->list->remove($itemObject); } - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { return $this->list->getIterator(); } diff --git a/src/ORM/ManyManyList.php b/src/ORM/ManyManyList.php index 5ad69cd8f..ff841a9dc 100644 --- a/src/ORM/ManyManyList.php +++ b/src/ORM/ManyManyList.php @@ -521,7 +521,7 @@ class ManyManyList extends RelationList $query->addWhere([ "\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID ]); - $queryResult = $query->execute()->current(); + $queryResult = $query->execute()->record(); if ($queryResult) { foreach ($queryResult as $fieldName => $value) { $result[$fieldName] = $value; diff --git a/src/ORM/Map.php b/src/ORM/Map.php index 6bcd68494..a29116c53 100644 --- a/src/ORM/Map.php +++ b/src/ORM/Map.php @@ -4,6 +4,7 @@ namespace SilverStripe\ORM; use ArrayAccess; use Countable; +use Iterator; use IteratorAggregate; /** @@ -250,23 +251,55 @@ class Map implements ArrayAccess, Countable, IteratorAggregate } /** - * Returns an Map_Iterator instance for iterating over the complete set + * Returns an Iterator for iterating over the complete set * of items in the map. * - * Satisfies the IteratorAggreagte interface. - * - * @return Map_Iterator + * Satisfies the IteratorAggregate interface. */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { - return new Map_Iterator( - $this->list->getIterator(), - $this->keyField, - $this->valueField, - $this->firstItems, - $this->lastItems - ); + $keyField = $this->keyField; + $valueField = $this->valueField; + + foreach ($this->firstItems as $k => $v) { + yield $k => $v; + } + + foreach ($this->list as $record) { + if (isset($this->firstItems[$record->$keyField])) { + continue; + } + if (isset($this->lastItems[$record->$keyField])) { + continue; + } + yield $this->extractValue($record, $this->keyField) => $this->extractValue($record, $this->valueField); + } + + foreach ($this->lastItems as $k => $v) { + yield $k => $v; + } + } + + /** + * Extracts a value from an item in the list, where the item is either an + * object or array. + * + * @param array|object $item + * @param string $key + * @return mixed + */ + protected function extractValue($item, $key) + { + if (is_object($item)) { + if (method_exists($item, 'hasMethod') && $item->hasMethod($key)) { + return $item->{$key}(); + } + return $item->{$key}; + } else { + if (array_key_exists($key, $item)) { + return $item[$key]; + } + } } /** diff --git a/src/ORM/Map_Iterator.php b/src/ORM/Map_Iterator.php deleted file mode 100644 index d645ca07d..000000000 --- a/src/ORM/Map_Iterator.php +++ /dev/null @@ -1,200 +0,0 @@ -items = $items; - $this->keyField = $keyField; - $this->titleField = $titleField; - $this->endItemIdx = null; - - if ($firstItems) { - foreach ($firstItems as $k => $v) { - $this->firstItems[] = [$k, $v]; - $this->excludedItems[] = $k; - } - } - - if ($lastItems) { - foreach ($lastItems as $k => $v) { - $this->lastItems[] = [$k, $v]; - $this->excludedItems[] = $k; - } - } - } - - /** - * Rewind the Iterator to the first element. - * - * @return mixed - */ - #[\ReturnTypeWillChange] - public function rewind() - { - $this->firstItemIdx = 0; - $this->endItemIdx = null; - - $rewoundItem = $this->items->rewind(); - - if (isset($this->firstItems[$this->firstItemIdx])) { - return $this->firstItems[$this->firstItemIdx][1]; - } else { - if ($rewoundItem) { - return $this->extractValue($rewoundItem, $this->titleField); - } else { - if (!$this->items->valid() && $this->lastItems) { - $this->endItemIdx = 0; - - return $this->lastItems[0][1]; - } - } - } - } - - /** - * Return the current element. - * - * @return mixed - */ - #[\ReturnTypeWillChange] - public function current() - { - if (($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) { - return $this->lastItems[$this->endItemIdx][1]; - } else { - if (isset($this->firstItems[$this->firstItemIdx])) { - return $this->firstItems[$this->firstItemIdx][1]; - } - } - return $this->extractValue($this->items->current(), $this->titleField); - } - - /** - * Extracts a value from an item in the list, where the item is either an - * object or array. - * - * @param array|object $item - * @param string $key - * @return mixed - */ - protected function extractValue($item, $key) - { - if (is_object($item)) { - if (method_exists($item, 'hasMethod') && $item->hasMethod($key)) { - return $item->{$key}(); - } - return $item->{$key}; - } else { - if (array_key_exists($key, $item ?? [])) { - return $item[$key]; - } - } - } - - /** - * Return the key of the current element. - * - * @return string - */ - #[\ReturnTypeWillChange] - public function key() - { - if (($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) { - return $this->lastItems[$this->endItemIdx][0]; - } else { - if (isset($this->firstItems[$this->firstItemIdx])) { - return $this->firstItems[$this->firstItemIdx][0]; - } else { - return $this->extractValue($this->items->current(), $this->keyField); - } - } - } - - /** - * Move forward to next element. - * - * @return mixed - */ - #[\ReturnTypeWillChange] - public function next() - { - $this->firstItemIdx++; - - if (isset($this->firstItems[$this->firstItemIdx])) { - return $this->firstItems[$this->firstItemIdx][1]; - } else { - if (!isset($this->firstItems[$this->firstItemIdx - 1])) { - $this->items->next(); - } - - if ($this->excludedItems) { - while (($c = $this->items->current()) && in_array($c->{$this->keyField}, $this->excludedItems ?? [], true)) { - $this->items->next(); - } - } - } - - if (!$this->items->valid()) { - // iterator has passed the preface items, off the end of the items - // list. Track through the end items to go through to the next - if ($this->endItemIdx === null) { - $this->endItemIdx = -1; - } - - $this->endItemIdx++; - - if (isset($this->lastItems[$this->endItemIdx])) { - return $this->lastItems[$this->endItemIdx]; - } - - return false; - } - } - - /** - * Checks if current position is valid. - * - * @return boolean - */ - #[\ReturnTypeWillChange] - public function valid() - { - return ( - (isset($this->firstItems[$this->firstItemIdx])) || - (($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) || - $this->items->valid() - ); - } -} diff --git a/src/ORM/PaginatedList.php b/src/ORM/PaginatedList.php index f7289e9a8..b962c0e54 100644 --- a/src/ORM/PaginatedList.php +++ b/src/ORM/PaginatedList.php @@ -8,6 +8,7 @@ use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\View\ArrayData; use ArrayAccess; use Exception; +use Iterator; use IteratorIterator; /** @@ -208,11 +209,7 @@ class PaginatedList extends ListDecorator return $this; } - /** - * @return IteratorIterator - */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { $pageLength = $this->getPageLength(); if ($this->limitItems && $pageLength) { @@ -225,6 +222,21 @@ class PaginatedList extends ListDecorator } } + /** + * @return array + */ + public function toArray() + { + $result = []; + + // Use getIterator() + foreach ($this as $record) { + $result[] = $record; + } + + return $result; + } + /** * Returns a set of links to all the pages in the list. This is useful for * basic pagination. @@ -344,8 +356,8 @@ class PaginatedList extends ListDecorator $num = $i + 1; $emptyRange = $num != 1 && $num != $total && ( - $num == $left - 1 || $num == $right + 1 - ); + $num == $left - 1 || $num == $right + 1 + ); if ($emptyRange) { $result->push(new ArrayData([ diff --git a/src/ORM/UnsavedRelationList.php b/src/ORM/UnsavedRelationList.php index 51cde920f..0206ef19e 100644 --- a/src/ORM/UnsavedRelationList.php +++ b/src/ORM/UnsavedRelationList.php @@ -4,6 +4,7 @@ namespace SilverStripe\ORM; use InvalidArgumentException; use ArrayIterator; +use Iterator; use SilverStripe\ORM\FieldType\DBField; /** @@ -120,11 +121,8 @@ class UnsavedRelationList extends ArrayList implements Relation /** * Returns an Iterator for this relation. - * - * @return ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { return new ArrayIterator($this->toArray()); } diff --git a/src/View/SSViewer_Scope.php b/src/View/SSViewer_Scope.php index 224fd0753..28d1b541f 100644 --- a/src/View/SSViewer_Scope.php +++ b/src/View/SSViewer_Scope.php @@ -3,6 +3,7 @@ namespace SilverStripe\View; use ArrayIterator; +use Countable; use Iterator; /** @@ -289,16 +290,31 @@ class SSViewer_Scope } if (!$this->itemIterator) { + // Note: it is important that getIterator() is called before count() as implemenations may rely on + // this to efficiency get both the number of records and an iterator (e.g. DataList does this) + + // Item may be an array or a regular IteratorAggregate if (is_array($this->item)) { $this->itemIterator = new ArrayIterator($this->item); } else { $this->itemIterator = $this->item->getIterator(); + + // This will execute code in a generator up to the first yield. For example, this ensures that + // DataList::getIterator() is called before Datalist::count() + $this->itemIterator->rewind(); + } + + // If the item implements Countable, use that to fetch the count, otherwise we have to inspect the + // iterator and then rewind it. + if ($this->item instanceof Countable) { + $this->itemIteratorTotal = count($this->item); + } else { + $this->itemIteratorTotal = iterator_count($this->itemIterator); + $this->itemIterator->rewind(); } $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator; - $this->itemIteratorTotal = iterator_count($this->itemIterator); // Count the total number of items $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal; - $this->itemIterator->rewind(); } else { $this->itemIterator->next(); } diff --git a/src/View/ViewableData.php b/src/View/ViewableData.php index 2755b1da4..4fd3051c2 100644 --- a/src/View/ViewableData.php +++ b/src/View/ViewableData.php @@ -5,6 +5,7 @@ namespace SilverStripe\View; use ArrayIterator; use Exception; use InvalidArgumentException; +use Iterator; use IteratorAggregate; use LogicException; use SilverStripe\Core\ClassInfo; @@ -573,11 +574,8 @@ class ViewableData implements IteratorAggregate * * This is useful so you can use a single record inside a <% control %> block in a template - and then use * to access individual fields on this object. - * - * @return ArrayIterator */ - #[\ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Iterator { return new ArrayIterator([$this]); } diff --git a/tests/php/ORM/DatabaseTest.php b/tests/php/ORM/DatabaseTest.php index 25fa3deed..33a45caaf 100644 --- a/tests/php/ORM/DatabaseTest.php +++ b/tests/php/ORM/DatabaseTest.php @@ -74,7 +74,7 @@ class DatabaseTest extends SapphireTest 'SHOW TABLE STATUS WHERE "Name" = \'%s\'', 'DatabaseTest_MyObject' ) - )->first(); + )->record(); $this->assertEquals( $ret['Engine'], 'InnoDB', diff --git a/tests/php/ORM/MySQLDatabaseTest.php b/tests/php/ORM/MySQLDatabaseTest.php index 7b084b045..e4ffdfd2f 100644 --- a/tests/php/ORM/MySQLDatabaseTest.php +++ b/tests/php/ORM/MySQLDatabaseTest.php @@ -45,58 +45,47 @@ class MySQLDatabaseTest extends SapphireTest $this->assertInstanceOf(MySQLQuery::class, $result3); // Iterating one level should not buffer, but return the right result + $result1Array = []; + foreach ($result1 as $record) { + $result1Array[] = $record; + } $this->assertEquals( [ - 'Sort' => 1, - 'Title' => 'First Item' + [ 'Sort' => 1, 'Title' => 'First Item' ], + [ 'Sort' => 2, 'Title' => 'Second Item' ], + [ 'Sort' => 3, 'Title' => 'Third Item' ], + [ 'Sort' => 4, 'Title' => 'Last Item' ], ], - $result1->next() - ); - $this->assertEquals( - [ - 'Sort' => 2, - 'Title' => 'Second Item' - ], - $result1->next() - ); - - // Test first - $this->assertEquals( - [ - 'Sort' => 1, - 'Title' => 'First Item' - ], - $result1->first() - ); - - // Test seek - $this->assertEquals( - [ - 'Sort' => 2, - 'Title' => 'Second Item' - ], - $result1->seek(1) + $result1Array ); // Test count $this->assertEquals(4, $result1->numRecords()); // Test second statement + $result2Array = []; + foreach ($result2 as $record) { + $result2Array[] = $record; + break; + } $this->assertEquals( [ - 'Sort' => 3, - 'Title' => 'Third Item' + [ 'Sort' => 3, 'Title' => 'Third Item' ], ], - $result2->next() + $result2Array ); // Test non-prepared query + $result3Array = []; + foreach ($result3 as $record) { + $result3Array[] = $record; + break; + } $this->assertEquals( [ - 'Sort' => 1, - 'Title' => 'First Item' + [ 'Sort' => 1, 'Title' => 'First Item' ], ], - $result3->next() + $result3Array ); } diff --git a/tests/php/ORM/PDODatabaseTest.php b/tests/php/ORM/PDODatabaseTest.php index fa91676c5..546205671 100644 --- a/tests/php/ORM/PDODatabaseTest.php +++ b/tests/php/ORM/PDODatabaseTest.php @@ -43,58 +43,47 @@ class PDODatabaseTest extends SapphireTest $this->assertInstanceOf(PDOQuery::class, $result3); // Iterating one level should not buffer, but return the right result + $result1Array = []; + foreach ($result1 as $record) { + $result1Array[] = $record; + } $this->assertEquals( [ - 'Sort' => 1, - 'Title' => 'First Item' + [ 'Sort' => 1, 'Title' => 'First Item' ], + [ 'Sort' => 2, 'Title' => 'Second Item' ], + [ 'Sort' => 3, 'Title' => 'Third Item' ], + [ 'Sort' => 4, 'Title' => 'Last Item' ], ], - $result1->next() - ); - $this->assertEquals( - [ - 'Sort' => 2, - 'Title' => 'Second Item' - ], - $result1->next() - ); - - // Test first - $this->assertEquals( - [ - 'Sort' => 1, - 'Title' => 'First Item' - ], - $result1->first() - ); - - // Test seek - $this->assertEquals( - [ - 'Sort' => 2, - 'Title' => 'Second Item' - ], - $result1->seek(1) + $result1Array ); // Test count $this->assertEquals(4, $result1->numRecords()); // Test second statement + $result2Array = []; + foreach ($result2 as $record) { + $result2Array[] = $record; + break; + } $this->assertEquals( [ - 'Sort' => 3, - 'Title' => 'Third Item' + [ 'Sort' => 3, 'Title' => 'Third Item' ], ], - $result2->next() + $result2Array ); // Test non-prepared query + $result3Array = []; + foreach ($result3 as $record) { + $result3Array[] = $record; + break; + } $this->assertEquals( [ - 'Sort' => 1, - 'Title' => 'First Item' + [ 'Sort' => 1, 'Title' => 'First Item' ], ], - $result3->next() + $result3Array ); } diff --git a/tests/php/ORM/PaginatedListTest.php b/tests/php/ORM/PaginatedListTest.php index fb63403d6..f8a1e2c97 100644 --- a/tests/php/ORM/PaginatedListTest.php +++ b/tests/php/ORM/PaginatedListTest.php @@ -90,7 +90,7 @@ class PaginatedListTest extends SapphireTest $this->assertEquals(1, $list->CurrentPage()); } - public function testGetIterator() + public function testIteration() { $list = new PaginatedList( new ArrayList([ @@ -105,26 +105,23 @@ class PaginatedListTest extends SapphireTest $this->assertListEquals( [['Num' => 1], ['Num' => 2]], - ArrayList::create($list->getIterator()->getInnerIterator()->getArrayCopy()) + $list ); $list->setCurrentPage(2); $this->assertListEquals( [['Num' => 3], ['Num' => 4]], - ArrayList::create($list->getIterator()->getInnerIterator()->getArrayCopy()) + $list ); $list->setCurrentPage(3); $this->assertListEquals( [['Num' => 5]], - ArrayList::create($list->getIterator()->getInnerIterator()->getArrayCopy()) + $list ); $list->setCurrentPage(999); - $this->assertListEquals( - [], - ArrayList::create($list->getIterator()->getInnerIterator()->getArrayCopy()) - ); + $this->assertListEquals([], $list); // Test disabled paging $list->setPageLength(0); @@ -137,14 +134,13 @@ class PaginatedListTest extends SapphireTest ['Num' => 4], ['Num' => 5], ], - ArrayList::create($list->getIterator()->getInnerIterator()->getArrayCopy()) + $list ); // Test with dataobjectset $players = Player::get(); $list = new PaginatedList($players); $list->setPageLength(1); - $list->getIterator(); $this->assertEquals( 4, $list->getTotalItems(), @@ -223,10 +219,10 @@ class PaginatedListTest extends SapphireTest $list = new PaginatedList($list); $list->setCurrentPage(3); - $this->assertCount(10, $list->getIterator()->getInnerIterator()); + $this->assertEquals(10, count($list->toArray())); $list->setLimitItems(false); - $this->assertCount(50, $list->getIterator()->getInnerIterator()); + $this->assertEquals(50, count($list->toArray())); } public function testCurrentPage()