diff --git a/src/Forms/GridField/GridFieldExportButton.php b/src/Forms/GridField/GridFieldExportButton.php index 9e56b76aa..9fc3ad3f3 100644 --- a/src/Forms/GridField/GridFieldExportButton.php +++ b/src/Forms/GridField/GridFieldExportButton.php @@ -223,10 +223,6 @@ class GridFieldExportButton extends AbstractGridFieldComponent implements GridFi // Remove limit as the list may be paginated, we want the full list for the export $items = $items->limit(null); - // Use Generator in applicable cases to reduce memory consumption - $items = $items instanceof DataList - ? $items->getGenerator() - : $items; /** @var DataObject $item */ foreach ($items as $item) { diff --git a/src/ORM/ArrayList.php b/src/ORM/ArrayList.php index 74a7cd600..eca2da26c 100644 --- a/src/ORM/ArrayList.php +++ b/src/ORM/ArrayList.php @@ -109,13 +109,13 @@ class ArrayList extends ViewableData implements SS_List, Filterable, Sortable, L #[\ReturnTypeWillChange] public function getIterator() { - $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 7f93148cb..7a10cef8e 100644 --- a/src/ORM/Connect/DBSchemaManager.php +++ b/src/ORM/Connect/DBSchemaManager.php @@ -378,7 +378,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..4f8db6993 100644 --- a/src/ORM/Connect/MySQLQuery.php +++ b/src/ORM/Connect/MySQLQuery.php @@ -45,16 +45,30 @@ class MySQLQuery extends Query } } - public function seek($row) + #[\ReturnTypeWillChange] + public function getIterator() { + $floatTypes = [MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL]; 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 ($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; + } + yield $data; + } + // Check for the method first since $this->handle is a mixed type + if (method_exists($this->handle, 'data_seek')) { + // Reset so the query can be iterated over again + $this->handle->data_seek(0); + } } - return null; } public function numRecords() @@ -62,27 +76,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..da6878988 100644 --- a/src/ORM/Connect/MySQLStatement.php +++ b/src/ORM/Connect/MySQLStatement.php @@ -56,6 +56,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 +102,25 @@ 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) + #[\ReturnTypeWillChange] + public function getIterator() { - $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) { + $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; + } + 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/Query.php b/src/ORM/Connect/Query.php index af9e0c3a5..afcf6d4f8 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,11 @@ 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(); + #[\ReturnTypeWillChange] + abstract public function getIterator(); /** * Return the total number of items in the query result. @@ -259,12 +165,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..e9f65b04d 100644 --- a/src/ORM/DataList.php +++ b/src/ORM/DataList.php @@ -6,6 +6,7 @@ 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; @@ -49,6 +50,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 +87,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li public function __clone() { $this->dataQuery = clone $this->dataQuery; + $this->finalisedQuery = null; } /** @@ -781,20 +790,6 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li return $this; } - /** - * Returns a generator for this DataList - * - * @return \Generator&DataObject[] - */ - public function getGenerator() - { - $query = $this->dataQuery->query()->execute(); - - while ($row = $query->record()) { - yield $this->createDataObject($row); - } - } - public function debug() { $val = "

" . static::class . "