-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
354 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
<?php | ||
/** | ||
* @link http://www.yiiframework.com/ | ||
* @copyright Copyright (c) 2008 Yii Software LLC | ||
* @license http://www.yiiframework.com/license/ | ||
*/ | ||
|
||
namespace sammaye\pq; | ||
|
||
use Yii; | ||
use yii\base\Object; | ||
|
||
/** | ||
* BatchQueryResult represents a batch query from which you can retrieve data in batches. | ||
* | ||
* You usually do not instantiate BatchQueryResult directly. Instead, you obtain it by | ||
* calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the [[\Iterator]] interface, | ||
* you can iterate it to obtain a batch of data in each iteration. For example, | ||
* | ||
* ```php | ||
* $query = (new Query)->from('user'); | ||
* foreach ($query->batch() as $i => $users) { | ||
* // $users represents the rows in the $i-th batch | ||
* } | ||
* foreach ($query->each() as $user) { | ||
* } | ||
* ``` | ||
* | ||
* @author Qiang Xue <[email protected]> | ||
* @since 2.0 | ||
*/ | ||
class PagedQueryResult extends Object implements \Iterator | ||
{ | ||
/** | ||
* @var Connection the DB connection to be used when performing batch query. | ||
* If null, the "db" application component will be used. | ||
*/ | ||
public $db; | ||
/** | ||
* @var Query the query object associated with this batch query. | ||
* Do not modify this property directly unless after [[reset()]] is called explicitly. | ||
*/ | ||
public $query; | ||
/** | ||
* @var integer the number of rows to be returned in each batch. | ||
*/ | ||
public $batchSize = 100; | ||
/** | ||
* @var boolean whether to return a single row during each iteration. | ||
* If false, a whole batch of rows will be returned in each iteration. | ||
*/ | ||
public $each = false; | ||
/** | ||
* @var boolean whether or not to paginate the query via offset and limit | ||
* This is helpful for PHP7 where resuls sets actually add to a PHP's process | ||
* memory usage by default | ||
*/ | ||
public $page = false; | ||
|
||
/** | ||
* @var DataReader the data reader associated with this batch query. | ||
*/ | ||
private $_dataReader; | ||
/** | ||
* @var array the data retrieved in the current batch | ||
*/ | ||
private $_batch; | ||
/** | ||
* @var mixed the value for the current iteration | ||
*/ | ||
private $_value; | ||
/** | ||
* @var string|integer the key for the current iteration | ||
*/ | ||
private $_key; | ||
/** | ||
* @var integer holds the value of the offset for pagination | ||
*/ | ||
private $_offset; | ||
/** | ||
* @var integer holds the limit of the query, if one is provided | ||
*/ | ||
private $_limit; | ||
|
||
|
||
/** | ||
* Destructor. | ||
*/ | ||
public function __destruct() | ||
{ | ||
// make sure cursor is closed | ||
$this->reset(); | ||
} | ||
|
||
/** | ||
* Resets the batch query. | ||
* This method will clean up the existing batch query so that a new batch query can be performed. | ||
*/ | ||
public function reset() | ||
{ | ||
if ($this->_dataReader !== null) { | ||
$this->_dataReader->close(); | ||
} | ||
$this->_dataReader = null; | ||
$this->_batch = null; | ||
$this->_value = null; | ||
$this->_key = null; | ||
$this->_offset = null; | ||
$this->_limit = null; | ||
} | ||
|
||
/** | ||
* Resets the iterator to the initial state. | ||
* This method is required by the interface [[\Iterator]]. | ||
*/ | ||
public function rewind() | ||
{ | ||
$this->reset(); | ||
$this->next(); | ||
} | ||
|
||
/** | ||
* Moves the internal pointer to the next dataset. | ||
* This method is required by the interface [[\Iterator]]. | ||
*/ | ||
public function next() | ||
{ | ||
if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) { | ||
$this->_batch = $this->fetchData(); | ||
reset($this->_batch); | ||
} | ||
|
||
if ($this->each) { | ||
$this->_value = current($this->_batch); | ||
if ($this->query->indexBy !== null) { | ||
$this->_key = key($this->_batch); | ||
} elseif (key($this->_batch) !== null) { | ||
$this->_key++; | ||
} else { | ||
$this->_key = null; | ||
} | ||
} else { | ||
$this->_value = $this->_batch; | ||
$this->_key = $this->_key === null ? 0 : $this->_key + 1; | ||
} | ||
} | ||
|
||
/** | ||
* Fetches the next batch of data. | ||
* @return array the data fetched | ||
*/ | ||
protected function fetchData() | ||
{ | ||
if($this->_batch === null && $this->query->limit !== null){ | ||
// If it hasn't been fetched lets record the limit | ||
$this->_limit = $this->query->limit; | ||
} | ||
|
||
if($this->page){ | ||
|
||
// If we are reaching near of the end of the predefined limit then | ||
// let's sort that out | ||
$batchSize = $this->batchSize; | ||
if($this->_limit !== null){ | ||
if($this->_offset > $this->_limit){ | ||
$batchSize = 0; | ||
}elseif($this->_offset === $this->_limit){ | ||
// Normally DB techs are exclusive in OFFSET | ||
$batchSize = 1; | ||
}elseif(($this->batchSize + $this->_offset) >= $this->_limit){ | ||
$batchSize = $this->_limit - $this->_offset; | ||
} | ||
} | ||
|
||
$this->_dataReader = $this->query | ||
->limit($batchSize) | ||
->offset($this->_offset) | ||
->createCommand($this->db) | ||
->query(); | ||
|
||
}elseif($this->_dataReader === null){ | ||
$this->_dataReader = $this->query->createCommand($this->db)->query(); | ||
} | ||
|
||
$rows = []; | ||
$count = 0; | ||
while ($count++ < $this->batchSize && ($row = $this->_dataReader->read())) { | ||
$rows[] = $row; | ||
} | ||
|
||
if($this->page){ | ||
// If the result has been pulled without error then increment the offset | ||
$this->_offset += $batchSize; | ||
} | ||
|
||
return $this->query->populate($rows); | ||
} | ||
|
||
/** | ||
* Returns the index of the current dataset. | ||
* This method is required by the interface [[\Iterator]]. | ||
* @return integer the index of the current row. | ||
*/ | ||
public function key() | ||
{ | ||
return $this->_key; | ||
} | ||
|
||
/** | ||
* Returns the current dataset. | ||
* This method is required by the interface [[\Iterator]]. | ||
* @return mixed the current dataset. | ||
*/ | ||
public function current() | ||
{ | ||
return $this->_value; | ||
} | ||
|
||
/** | ||
* Returns whether there is a valid dataset at the current position. | ||
* This method is required by the interface [[\Iterator]]. | ||
* @return boolean whether there is a valid dataset at the current position. | ||
*/ | ||
public function valid() | ||
{ | ||
return !empty($this->_batch); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<?php | ||
|
||
namespace sammaye\pq; | ||
|
||
use Yii; | ||
use yii\db\Query as BaseQuery; | ||
use PagedQueryResult; | ||
|
||
class Query extends BaseQuery | ||
{ | ||
/** | ||
* Starts a batch query. | ||
* | ||
* A batch query supports fetching data in batches, which can keep the memory usage under a limit. | ||
* This method will return a [[BatchQueryResult]] object which implements the [[\Iterator]] interface | ||
* and can be traversed to retrieve the data in batches. | ||
* | ||
* For example, | ||
* | ||
* ```php | ||
* $query = (new Query)->from('user'); | ||
* foreach ($query->batch() as $rows) { | ||
* // $rows is an array of 10 or fewer rows from user table | ||
* } | ||
* ``` | ||
* | ||
* @param integer $batchSize the number of records to be fetched in each batch. | ||
* @param Connection $db the database connection. If not set, the "db" application component will be used. | ||
* @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface | ||
* and can be traversed to retrieve the data in batches. | ||
*/ | ||
public function batch($batchSize = 100, $page = true, $db = null) | ||
{ | ||
return Yii::createObject([ | ||
'class' => PagedQueryResult::className(), | ||
'query' => $this, | ||
'batchSize' => $batchSize, | ||
'db' => $db, | ||
'each' => false, | ||
'page' => $page | ||
]); | ||
} | ||
|
||
/** | ||
* Starts a batch query and retrieves data row by row. | ||
* This method is similar to [[batch()]] except that in each iteration of the result, | ||
* only one row of data is returned. For example, | ||
* | ||
* ```php | ||
* $query = (new Query)->from('user'); | ||
* foreach ($query->each() as $row) { | ||
* } | ||
* ``` | ||
* | ||
* @param integer $batchSize the number of records to be fetched in each batch. | ||
* @param Connection $db the database connection. If not set, the "db" application component will be used. | ||
* @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface | ||
* and can be traversed to retrieve the data in batches. | ||
*/ | ||
public function each($batchSize = 100, $page = true, $db = null) | ||
{ | ||
return Yii::createObject([ | ||
'class' => PagedQueryResult::className(), | ||
'query' => $this, | ||
'batchSize' => $batchSize, | ||
'db' => $db, | ||
'each' => true, | ||
'page' => $page | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,38 @@ | ||
# yii2-pagedquery | ||
# yii2-pq | ||
A paged query class for Yii2 to deal with PHP7 changed in mysqlnd | ||
|
||
It will load your result set the same way as pagination works using `OFFSET` and `LIMIT`. | ||
|
||
This extension is 99% for MySQL users. | ||
|
||
## Installation | ||
|
||
Just include it in your composer: | ||
|
||
php composer require "sammaye/yii2-pq":"~1.0.0" | ||
|
||
## Usage | ||
|
||
$query = (new \sammaye\pq\Query) | ||
->from(Title::tableName()) | ||
->where('live=1') | ||
->limit(300) | ||
->orderBy(['id' => SORT_DESC]); | ||
foreach($query->each() as $k => $v){ | ||
|
||
And it will return in batches of 100 up to 300. | ||
|
||
As you can see there is not much to learn about this extension except how to include it. | ||
|
||
**Note:** There is no active record part to this query currently due to the | ||
nature of PHP class inheritance and inclusion which means I would have to copy the `ActiveQuery` entirely. | ||
|
||
## Why? | ||
|
||
I noticed that many of my cronjobs failed after an upgrade to PHP7. It was not | ||
long before I realised that there was two changes since PHP5.4: | ||
|
||
- The default MySQL driver used by PHP7 has changed to mysqlnd | ||
- And, mysqlnd now adds your [result sets to it's own memory](http://php.net/manual/en/mysqlinfo.concepts.buffering.php), buffering queries | ||
|
||
Added to that, my own observations that unbuffered queries suck meant that I created this. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "sammaye/yii2-pq", | ||
"description": "A paged query class for Yii2 to deal with PHP7 changed in mysqlnd", | ||
"keywords": ["yii2", "pagination", "query", "db"], | ||
"type": "yii2-extension", | ||
"authors": [ | ||
{ | ||
"name": "Sammaye", | ||
"homepage": "https://www.sammaye.com" | ||
} | ||
], | ||
"require": { | ||
"php": ">=5.4.0" | ||
}, | ||
"autoload": { | ||
"psr-4": {"sammaye\\pq\\": ""} | ||
} | ||
} |