Permalink
Browse files

Merge pull request #546 branch 'phpnode-add-cdataprovideriterator'

* phpnode-add-cdataprovideriterator:
  Refactoring of CDataProviderIterator
  #545 more tiny code style changes
  #545 reformat code according to Yii style
  #545 dont use getters or setters if we can avoid it
  #545 dont use getters or setters if we can avoid it
  #545 make protected members private
  #545 update changelog
  #545 fix whitespace
  Update changelog for #545
  Add CDataProviderIterator to allow iterating over large data sets
  • Loading branch information...
2 parents 5722729 + 7150d84 commit 1cdaa63218fa420c5ccd99038fd45b9ff3e62891 @cebe cebe committed Nov 14, 2012
Showing with 215 additions and 0 deletions.
  1. +1 −0 CHANGELOG
  2. +155 −0 framework/web/CDataProviderIterator.php
  3. +59 −0 tests/framework/web/CDataProviderIteratorTest.php
View
@@ -45,6 +45,7 @@ Version 1.1.13 work in progress
- Enh #259: CHttpRequest::getRestParams is now public (samdark)
- Enh #291: CFormatter::formatDate and formatDateTime now also accept strings in strtotime() format (francis_tm, cebe)
- Enh #486: CHttpSession::$gCProbability and CDbHttpSession::$gCProbability are floats now. Minimal possible $gCProbability value has been changed to the ≈0.00000005% (1/2147483647), was integer 1% before, default value left unchanged (1%) (resurtm)
+- Enh #545: Add CDataProviderIterator to allow iteration over large data sets (phpnode)
- Enh #556: CDbColumnSchema::$comment property has been added. It stores comment for the table column, comment retrieving is working for MySQL, PgSQL and Oracle (resurtm)
- Enh #724: Third argument of CHtml::listData() now receives anonymous function as calculator of the text field value, PHP 5.3+ only (resurtm)
- Enh #846: Added addPrimaryKey() / dropPrimaryKey() commands to CDbMigration (ridget)
@@ -0,0 +1,155 @@
+<?php
+/**
+ * CDataProviderIterator class file.
+ *
+ * @author Charles Pick <charles.pick@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2012 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CDataProviderIterator allows iteration over large data sets without holding the entire set in memory.
+ *
+ * CDataProviderIterator iterates over the results of a data provider, starting at the first page
+ * of results and ending at the last page. It is usually only suited for use with {@link CActiveDataProvider}.
+ *
+ * For example, the following code will iterate over all registered users (active record class User) without
+ * running out of memory, even if there are millions of users in the database.
+ * <pre>
+ * $dataProvider = new CActiveDataProvider("User");
+ * $iterator = new CDataProviderIterator($dataProvider);
+ * foreach($iterator as $user) {
+ * echo $user->name."\n";
+ * }
+ * </pre>
+ *
+ * @property CDataProvider $dataProvider the data provider to iterate over
+ * @property integer $totalItemCount the total number of items in the iterator
+ *
+ * @author Charles Pick <charles.pick@gmail.com>
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @package system.web
+ * @since 1.1.13
+ */
+class CDataProviderIterator extends CComponent implements Iterator, Countable
+{
+ private $_dataProvider;
+ private $_currentIndex=-1;
+ private $_currentPage=0;
+ private $_totalItemCount=-1;
+ private $_items;
+
+ /**
+ * Constructor.
+ * @param CDataProvider $dataProvider the data provider to iterate over
+ * @param integer $pageSize pageSize to use for iteration. This is the number of objects loaded into memory at the same time.
+ */
+ public function __construct(CDataProvider $dataProvider, $pageSize=null)
+ {
+ $this->_dataProvider=$dataProvider;
+ $this->_totalItemCount=$dataProvider->getTotalItemCount();
+
+ if(($pagination=$this->_dataProvider->getPagination())===false)
+ $this->_dataProvider->setPagination(new CPagination());
+
+ if($pageSize!==null)
+ $pagination->setPageSize($pageSize);
+ }
+
+ /**
+ * Returns the data provider to iterate over
+ * @return CDataProvider the data provider to iterate over
+ */
+ public function getDataProvider()
+ {
+ return $this->_dataProvider;
+ }
+
+ /**
+ * Gets the total number of items to iterate over
+ * @return integer the total number of items to iterate over
+ */
+ public function getTotalItemCount()
+ {
+ return $this->_totalItemCount;
+ }
+
+ /**
+ * Loads a page of items
+ * @return array the items from the next page of results
+ */
+ protected function loadPage()
+ {
+ $this->_dataProvider->getPagination()->setCurrentPage($this->_currentPage);
+ return $this->_items=$this->dataProvider->getData(true);
+ }
+
+ /**
+ * Gets the current item in the list.
+ * This method is required by the Iterator interface.
+ * @return mixed the current item in the list
+ */
+ public function current()
+ {
+ return $this->_items[$this->_currentIndex];
+ }
+
+ /**
+ * Gets the key of the current item.
+ * This method is required by the Iterator interface.
+ * @return integer the key of the current item
+ */
+ public function key()
+ {
+ $pageSize=$this->_dataProvider->getPagination()->getPageSize();
+ return $this->_currentPage*$pageSize+$this->_currentIndex;
+ }
+
+ /**
+ * Moves the pointer to the next item in the list.
+ * This method is required by the Iterator interface.
+ */
+ public function next()
+ {
+ $pageSize=$this->_dataProvider->getPagination()->getPageSize();
+ $this->_currentIndex++;
+ if($this->_currentIndex >= $pageSize)
+ {
+ $this->_currentPage++;
+ $this->_currentIndex=0;
+ $this->loadPage();
+ }
+ }
+
+ /**
+ * Rewinds the iterator to the start of the list.
+ * This method is required by the Iterator interface.
+ */
+ public function rewind()
+ {
+ $this->_currentIndex=0;
+ $this->_currentPage=0;
+ $this->loadPage();
+ }
+
+ /**
+ * Checks if the current position is valid or not.
+ * This method is required by the Iterator interface.
+ * @return boolean true if this index is valid
+ */
+ public function valid()
+ {
+ return $this->key() < $this->_totalItemCount;
+ }
+
+ /**
+ * Gets the total number of items in the dataProvider.
+ * This method is required by the Countable interface.
+ * @return integer the total number of items
+ */
+ public function count()
+ {
+ return $this->_totalItemCount;
+ }
+}
@@ -0,0 +1,59 @@
+<?php
+Yii::import("system.web.*");
+/**
+ * Tests for the {@link CDataProviderIterator} class
+ * @author Charles Pick <charles.pick@gmail.com>
+ */
+class CDataProviderIteratorTest extends CTestCase
+{
+ public function pageSizes()
+ {
+ return array(
+ array(null),
+ array(1),
+ array(10),
+ array(110),
+ );
+ }
+
+ /**
+ * Tests the iterator
+ *
+ * @dataProvider pageSizes
+ */
+ public function testIterator($pageSize)
+ {
+ $dataProvider = new CArrayDataProvider($this->generateData(100));
+ $iterator = new CDataProviderIterator($dataProvider, $pageSize);
+
+ $this->assertTrue($iterator->getDataProvider()===$dataProvider);
+
+ $this->assertEquals(100, $iterator->getTotalItemCount());
+ $this->assertEquals(100, count($iterator));
+
+ $n = 0;
+ foreach($iterator as $item) {
+ $this->assertEquals("Item ".$n,$item['name']);
+ $n++;
+ }
+
+ $this->assertEquals(100, $n);
+ }
+
+ /**
+ * Generates some data to fill a dataProvider
+ * @param integer $totalItems the total number of items to generate
+ * @return array the data
+ */
+ protected function generateData($totalItems)
+ {
+ $data = array();
+ for($i = 0; $i < $totalItems; $i++) {
+ $data[] = array(
+ "id" => $i,
+ "name" => "Item ".$i,
+ );
+ }
+ return $data;
+ }
+}

0 comments on commit 1cdaa63

Please sign in to comment.