mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW: ORM’ Query is a generator-based IteratorAggregate
API: Query no longer has iterator methods current(), first(), rewind(), next() Using generators reduces the amount of boilerplate needed for this code. Turning it into an IteratorAggregate means that the iterator can be re-created for each subsequent foreach call. This means that the rewind() and seek() functionality can be discarded.
This commit is contained in:
parent
d8735633a7
commit
77c7552c3f
@ -378,7 +378,7 @@ abstract class DBSchemaManager
|
|||||||
if ($dbID && isset($options[$dbID])) {
|
if ($dbID && isset($options[$dbID])) {
|
||||||
if (preg_match('/ENGINE=([^\s]*)/', $options[$dbID] ?? '', $alteredEngineMatches)) {
|
if (preg_match('/ENGINE=([^\s]*)/', $options[$dbID] ?? '', $alteredEngineMatches)) {
|
||||||
$alteredEngine = $alteredEngineMatches[1];
|
$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);
|
$tableOptionsChanged = ($tableStatus['Engine'] != $alteredEngine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,16 +45,24 @@ class MySQLQuery extends Query
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function seek($row)
|
public function getIterator()
|
||||||
{
|
{
|
||||||
|
$floatTypes = [MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL];
|
||||||
if (is_object($this->handle)) {
|
if (is_object($this->handle)) {
|
||||||
// Fix for https://github.com/silverstripe/silverstripe-framework/issues/9097 without breaking the seek() API
|
while ($row = $this->handle->fetch_array(MYSQLI_NUM)) {
|
||||||
$this->handle->data_seek($row);
|
$data = [];
|
||||||
$result = $this->nextRecord();
|
foreach ($row as $i => $value) {
|
||||||
$this->handle->data_seek($row);
|
if (!isset($this->columns[$i])) {
|
||||||
return $result;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function numRecords()
|
public function numRecords()
|
||||||
@ -62,27 +70,7 @@ class MySQLQuery extends Query
|
|||||||
if (is_object($this->handle)) {
|
if (is_object($this->handle)) {
|
||||||
return $this->handle->num_rows;
|
return $this->handle->num_rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,26 @@ class MySQLStatement extends Query
|
|||||||
*/
|
*/
|
||||||
protected $boundValues = [];
|
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
|
* Binds this statement to the variables
|
||||||
*/
|
*/
|
||||||
@ -82,58 +102,24 @@ class MySQLStatement extends Query
|
|||||||
call_user_func_array([$this->statement, 'bind_result'], $variables ?? []);
|
call_user_func_array([$this->statement, 'bind_result'], $variables ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getIterator()
|
||||||
* 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;
|
while($this->statement->fetch()) {
|
||||||
$this->metadata = $metadata;
|
// Dereferenced row
|
||||||
|
$row = [];
|
||||||
// Immediately bind and buffer
|
foreach ($this->boundValues as $key => $value) {
|
||||||
$this->bind();
|
$floatTypes = [MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE, MYSQLI_TYPE_DECIMAL, MYSQLI_TYPE_NEWDECIMAL];
|
||||||
}
|
if (in_array($this->types[$key], $floatTypes ?? [])) {
|
||||||
|
$value = (float)$value;
|
||||||
public function __destruct()
|
}
|
||||||
{
|
$row[$key] = $value;
|
||||||
$this->statement->close();
|
}
|
||||||
$this->currentRecord = false;
|
yield $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function numRecords()
|
public function numRecords()
|
||||||
{
|
{
|
||||||
return $this->statement->num_rows();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,30 +27,9 @@ use Iterator;
|
|||||||
* on providing the specific data-access methods that are required: {@link nextRecord()}, {@link numRecords()}
|
* on providing the specific data-access methods that are required: {@link nextRecord()}, {@link numRecords()}
|
||||||
* and {@link seek()}
|
* 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
|
* Return an array containing all the values from a specific column. If no column is set, then the first will be
|
||||||
* returned
|
* returned
|
||||||
@ -62,7 +41,7 @@ abstract class Query implements Iterator
|
|||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
while ($record = $this->next()) {
|
foreach ($this as $record) {
|
||||||
if ($column) {
|
if ($column) {
|
||||||
$result[] = $record[$column];
|
$result[] = $record[$column];
|
||||||
} else {
|
} else {
|
||||||
@ -82,6 +61,7 @@ abstract class Query implements Iterator
|
|||||||
public function keyedColumn()
|
public function keyedColumn()
|
||||||
{
|
{
|
||||||
$column = [];
|
$column = [];
|
||||||
|
|
||||||
foreach ($this as $record) {
|
foreach ($this as $record) {
|
||||||
$val = $record[key($record)];
|
$val = $record[key($record)];
|
||||||
$column[$val] = $val;
|
$column[$val] = $val;
|
||||||
@ -106,13 +86,13 @@ abstract class Query implements Iterator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next record in the iterator.
|
* Returns the first record in the result
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function record()
|
public function record()
|
||||||
{
|
{
|
||||||
return $this->next();
|
return $this->getIterator()->current();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,7 +102,7 @@ abstract class Query implements Iterator
|
|||||||
*/
|
*/
|
||||||
public function value()
|
public function value()
|
||||||
{
|
{
|
||||||
$record = $this->next();
|
$record = $this->record();
|
||||||
if ($record) {
|
if ($record) {
|
||||||
return $record[key($record)];
|
return $record[key($record)];
|
||||||
}
|
}
|
||||||
@ -164,94 +144,12 @@ abstract class Query implements Iterator
|
|||||||
return $result;
|
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 the next record in the query result.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
abstract public function nextRecord();
|
abstract public function getIterator();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the total number of items in the query result.
|
* Return the total number of items in the query result.
|
||||||
@ -259,12 +157,4 @@ abstract class Query implements Iterator
|
|||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
abstract public function numRecords();
|
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);
|
|
||||||
}
|
}
|
||||||
|
@ -521,7 +521,7 @@ class ManyManyList extends RelationList
|
|||||||
$query->addWhere([
|
$query->addWhere([
|
||||||
"\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID
|
"\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID
|
||||||
]);
|
]);
|
||||||
$queryResult = $query->execute()->current();
|
$queryResult = $query->execute()->record();
|
||||||
if ($queryResult) {
|
if ($queryResult) {
|
||||||
foreach ($queryResult as $fieldName => $value) {
|
foreach ($queryResult as $fieldName => $value) {
|
||||||
$result[$fieldName] = $value;
|
$result[$fieldName] = $value;
|
||||||
|
@ -74,7 +74,7 @@ class DatabaseTest extends SapphireTest
|
|||||||
'SHOW TABLE STATUS WHERE "Name" = \'%s\'',
|
'SHOW TABLE STATUS WHERE "Name" = \'%s\'',
|
||||||
'DatabaseTest_MyObject'
|
'DatabaseTest_MyObject'
|
||||||
)
|
)
|
||||||
)->first();
|
)->record();
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$ret['Engine'],
|
$ret['Engine'],
|
||||||
'InnoDB',
|
'InnoDB',
|
||||||
|
Loading…
Reference in New Issue
Block a user