Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

activerecord as array #819

Closed
wants to merge 19 commits into from

5 participants

@cebe
Owner

implementation of asArray as discussed in #531.
Allows returning arrays as result of all find methods and relations with getRelated().

Implementation introduces a scope-like method named asArray which can be used as follows:

<?php
    $post=Post::model()->asArray()->findByPk(1);
    $posts=Post::model()->asArray()->findAll();
    // This also works for relations (asuming <code>author</code> and <code>categories</code> are relations):
    $author=$post->asArray()->author;
    $categories=$post->asArray()->categories;

It will work for all find Methods, so I touched CActiveRecord::query(), CActiveRecord::findBySql() and CActiveRecord::findAllBySql().
Also fixed scope resetting ($this->_c=null) in findBySql which did not work properly.

adjustments to getRelated() diff

getRelated() will return array result, when scope asArray is true by using asArray implementation of activeFinder.
The $this->_related cache is not touched in case of asArray.

adjustments to query() diff

query will use the result of $command->queryAll()/$command->queryRow() to provide asArray functionallity. It will consider $criteria->index just like populateRecords() does.
If criteria has a with option set, it will use CActiveFinders asArray implementation.

asArray in CActiveFinder diff

CActiveFinder will use a new class CActiveRecordArray (see code in diff) which provides the same interface as CActiveRecord does for adding related records, getting primary key and getting attributes, so the changes to code of CActiveFinder can be reduced to a minimum. This class is necessary because CJoinElement::populateRecord() works on the objects by reference to attach related records. This is not possible with arrays, so I had to take the overhead of this class into to make it possible without having to change the whole implementation. As shown in a comment below this is not as good as pure array but much better than using CActiveRecord itself.
All places that create records now have to check if asArray is true and create CActiveRecordArray instead of CActiveRecord and all places where result is returned will use CActiveRecordArray::toArray() to return array result.
So CActiveRecordArray is only used internally by CActiveFinder and never comes to the outside (I am checking this in all of the tests).

@cebe cebe issue #531 added asArray property to CActiveFinder
added asArray property to activeFinder to support returning query
results as arrays instead of records.
To fully support this, the API of CActiveRecord needs to be adjusted to
make use of this feature, also some places marked with @todo are not implemented yet.

This commit is ment to be a request for comments.
7f7f511
@cebe

Using the ArrayAccess of CModel here helps to keep code readable. Otherwise there needs to be an extra if statement.

@cebe

Using the ArrayAccess of CModel here helps to keep code readable. Otherwise there needs to be an extra if statement.

@cebe
Owner

except the @todo annotations this is all we need to do with ActiveFinder to enable array support.

cebe added some commits
@cebe cebe removed todo annotations
code already did what it should

related to #531
91d4f37
@cebe cebe added missing implementation of getPrimaryKey
needed a getPrimaryKey() for array of attributes.

related to #531
a6a424d
@cebe cebe documentation annotations
related to #531
569220c
@cebe cebe code style adjustments to CActiveFinder 6f64c17
@cebe cebe issue #531: implemented asArray for CActiveRecord
Allows returning arrays as result of find methods.
details discussed in issue #531
d17dbbf
framework/db/ar/CActiveFinder.php
@@ -881,21 +908,48 @@ private function populateRecord($query,$row)
continue;
$childRecord=$child->populateRecord($query,$row);
@qiangxue Owner

When $childRecord is an array, changes to it will not be kept. This is the main difficulty of implementing this feature.

@cebe Owner
cebe added a note

Yep, just recognized that... Should have written tests before implementing it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cebe added some commits
@cebe cebe fixed missing variable typo 7a4d9e0
@cebe cebe fixed missing variable typo 0b5f3c1
@cebe cebe added CActiveRecord::getAsArray()
added CActiveRecord::getAsArray() to be able to get the current state of
asArray for example in a behavior beforeFind().
04d055d
@cebe cebe added test class to test asArray feature
test covers all find methods of CActiveRecord with and without the with
option. So find with CActiveFinder + CActiveRecord is covered nearly
100%.
Currently STAT relations and Composite PK are not explicitly covered.

These tests compare results generated by normal call of CActiveRecord
find*()-methods with the result of find*()-methods in combination
with asArray()
It is not checked if the result is the correct one expected related
to the query criteria.
fc8d737
@cebe
Owner

Just added a test class in fc8d737 which covers nearly every detail of asArray feature. Currently all tests related to querys with with are failing. Will further work on that.

cebe added some commits
@cebe cebe reverted most of CActiveFinder::asArray
replaced with a better solution which might have a bit more memory
overhead but will integrate more easily and does not blow code
4980804
@cebe cebe private _asArray insteadof magic property in trace 9989328
@cebe cebe finished implementation of asArray in CActiveFinder
current approach uses a simplified class for storing ar's
data which is not as good as pure array but much better as direct AR
2d52ea6
@cebe
Owner

Changed implementation to use a simplified AR class for storing the data. This one will be used when fetching data via CActiveFinder. It is not as good as arrays but much better that pure AR.
Here is a little test that shows memory usage(not a good test case but shows a bit what we can expect about memory usage).

<?php

        Yii::import('system.db.ar.CActiveFinder', true);
        Post::model()->getMetaData();
        User::model()->getMetaData();
        $attributes = array(
            'id'=>5,
            'title'=>'test title of a post',
            'create_time'=>time(),
            'author_id'=>25,
            'content'=>'Lorem ipsum dolor sit amet'
        );
        $attributes2 = array(
            'id'=>5,
            'name'=>'the author',
            'email'=>'mail@cebe.cc',
        );

        $old = memory_get_usage();
        $record0=$attributes;
        $mem = memory_get_usage();
        echo "\nmemory usage array only: \n".(abs($mem - $old)/1024).' kb'."\n";

        $old = memory_get_usage();
        $record1=new CActiveRecordArray($attributes,Post::model());
        $mem = memory_get_usage();
        echo "\nmemory usage array: \n".(abs($mem - $old)/1024).' kb'."\n";

        $old = memory_get_usage();
        $record2=Post::model()->populateRecord($attributes,false);
        $mem = memory_get_usage();
        echo "\nmemory usage AR: \n".(abs($mem - $old)/1024).' kb'."\n";

        $old = memory_get_usage();
        $record3=new CActiveRecordArray($attributes,Post::model());
        $record3->addRelatedRecord('author', new CActiveRecordArray($attributes2,User::model()),false);
        $mem = memory_get_usage();
        echo "\nmemory usage array with relation: \n".(abs($mem - $old)/1024).' kb'."\n";

        $old = memory_get_usage();
        $record4=Post::model()->populateRecord($attributes,false);
        $record4->addRelatedRecord('author', User::model()->populateRecord($attributes2,false),false);
        $mem = memory_get_usage();
        echo "\nmemory usage AR with relation: \n".(abs($mem - $old)/1024).' kb'."\n";
memory usage array only: 
0.046875 kb

memory usage array: 
0.8671875 kb

memory usage AR: 
19.46875 kb

memory usage array with relation: 
2.0390625 kb

memory usage AR with relation: 
22.359375 kb
@cebe
Owner

Btw: all test are passing will review and test the whole thing in some of my applications over the week, will give feedback on friday. I'm curious about your feedback, @qiangxue @samdark @mdomba !

@speedlog

Nice work! :) Memory saving is impressive!

@cebe
Owner

did some improvements and added tests for through and stat relation.
Also updated the pull request description, to make it easier for you to review my changes.

@cebe cebe commented on the diff
framework/db/ar/CActiveRecord.php
((10 lines not shown))
+ * $posts=Post::model()->asArray()->findAll();
+ * </pre>
+ * This also works for relations (asuming <code>author</code> and <code>categories</code> are relations):
+ * <pre>
+ * $author=$post->asArray()->author;
+ * $categories=$post->asArray()->categories;
+ * </pre>
+ * The first examples will return an array of attributes each.
+ * The second examples will return a list of attribute arrays each.
+ * When {@link CDbCriteria::with} is set on the query criteria, the resulting array will contain these
+ * relations with the relation name as array key.
+ * @param boolean $array whether find result should be array of attributes instead of active record objects
+ * @return CActiveRecord the active record instance itself.
+ * @since 1.1.11
+ */
+ public function asArray($asArray=true)
@cebe Owner
cebe added a note

btw: param is here to allow usage like this:

public function getTimezones($criteria, $asArray=false) {
    return Timezone::model()->asArray($asArray)->findAll($criteria);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@cebe
Owner

btw: will also write guide documentation about it when PR gets accepted.

@cebe
Owner

another btw: As these changes are very complex and maybe hard and time consuming to review I'd like to offer my help.
If it helps we can meet in a chatroom, irc or even skype voice chat and have a live discussion about it. So I am able to guide you through the changes.

@voidcontext

Hi,

What is the status of this request? Is there any chance that this will be merged into the stable branch?

@cebe
Owner

Currently we did not agree on merging it and as it is a very comlex thing it costs much time to integrate and make sure it has no bad side effects.
From my side it will not make it into the core very soon, not sure if ever. I may merge master into this branch from time to time so you may checkout this branch in your project if you really need this feature.

@samdark
Owner

This one involved great effort to solve but I think we should not merge it.

@samdark samdark closed this
@cebe
Owner

agreed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 11, 2012
  1. @cebe

    issue #531 added asArray property to CActiveFinder

    cebe authored
    added asArray property to activeFinder to support returning query
    results as arrays instead of records.
    To fully support this, the API of CActiveRecord needs to be adjusted to
    make use of this feature, also some places marked with @todo are not implemented yet.
    
    This commit is ment to be a request for comments.
Commits on Jun 12, 2012
  1. @cebe

    removed todo annotations

    cebe authored
    code already did what it should
    
    related to #531
  2. @cebe

    added missing implementation of getPrimaryKey

    cebe authored
    needed a getPrimaryKey() for array of attributes.
    
    related to #531
  3. @cebe

    documentation annotations

    cebe authored
    related to #531
  4. @cebe
  5. @cebe

    issue #531: implemented asArray for CActiveRecord

    cebe authored
    Allows returning arrays as result of find methods.
    details discussed in issue #531
Commits on Jun 15, 2012
  1. @cebe

    fixed missing variable typo

    cebe authored
  2. @cebe

    fixed missing variable typo

    cebe authored
  3. @cebe

    added CActiveRecord::getAsArray()

    cebe authored
    added CActiveRecord::getAsArray() to be able to get the current state of
    asArray for example in a behavior beforeFind().
  4. @cebe

    added test class to test asArray feature

    cebe authored
    test covers all find methods of CActiveRecord with and without the with
    option. So find with CActiveFinder + CActiveRecord is covered nearly
    100%.
    Currently STAT relations and Composite PK are not explicitly covered.
    
    These tests compare results generated by normal call of CActiveRecord
    find*()-methods with the result of find*()-methods in combination
    with asArray()
    It is not checked if the result is the correct one expected related
    to the query criteria.
Commits on Jun 18, 2012
  1. @cebe

    reverted most of CActiveFinder::asArray

    cebe authored
    replaced with a better solution which might have a bit more memory
    overhead but will integrate more easily and does not blow code
  2. @cebe
  3. @cebe

    finished implementation of asArray in CActiveFinder

    cebe authored
    current approach uses a simplified class for storing ar's
    data which is not as good as pure array but much better as direct AR
Commits on Jun 22, 2012
  1. @cebe

    methods of CActiveRecordArray to a better order

    cebe authored
    magic,
    constructor,
    getter,
    others
  2. @cebe

    simplified code of CActiveRecord::getRelated

    cebe authored
    $exists has been checked 2 times
  3. @cebe
  4. @cebe

    criteria has not been reset after findBySql()

    cebe authored
    the if checks whether criteria was not null AND with is empty, so
    criteria can still be not null and we have to reset it.
  5. @cebe
  6. @cebe
This page is out of date. Refresh to see the latest.
View
206 framework/db/ar/CActiveFinder.php
@@ -1,10 +1,10 @@
<?php
/**
- * CActiveRecord class file.
+ * CActiveFinder class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
@@ -27,6 +27,13 @@ class CActiveFinder extends CComponent
*/
public $joinAll=false;
/**
+ * @var boolean whether result of find query should be array of attributes
+ * instead of active records.
+ * This property is internally used.
+ * @since 1.1.11
+ */
+ public $asArray=false;
+ /**
* @var boolean whether the base model has limit or offset.
* This property is internally used.
*/
@@ -73,17 +80,26 @@ public function query($criteria,$all=false)
if($all)
{
$result = array_values($this->_joinTree->records);
- if ($criteria->index!==null)
+ if($criteria->index!==null)
{
$index=$criteria->index;
$array=array();
foreach($result as $object)
- $array[$object->$index]=$object;
+ $array[$object->$index]=$this->asArray?$object->toArray():$object;
$result=$array;
}
+ elseif($this->asArray)
+ {
+ foreach($result as $key=>$object)
+ $result[$key]=$object->toArray();
+ }
}
else if(count($this->_joinTree->records))
- $result = reset($this->_joinTree->records);
+ {
+ $result=reset($this->_joinTree->records);
+ if($this->asArray)
+ $result=$result->toArray();
+ }
else
$result = null;
@@ -99,15 +115,18 @@ public function query($criteria,$all=false)
*/
public function findBySql($sql,$params=array())
{
- Yii::trace(get_class($this->_joinTree->model).'.findBySql() eagerly','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this->_joinTree->model).'.findBySql() eagerly'.($this->asArray?' as array':''),'system.db.ar.CActiveRecord');
if(($row=$this->_builder->createSqlCommand($sql,$params)->queryRow())!==false)
{
- $baseRecord=$this->_joinTree->model->populateRecord($row,false);
+ if($this->asArray)
+ $baseRecord=new CActiveRecordArray($row,$this->_joinTree->model);
+ else
+ $baseRecord=$this->_joinTree->model->populateRecord($row,false);
$this->_joinTree->beforeFind(false);
$this->_joinTree->findWithBase($baseRecord);
$this->_joinTree->afterFind();
$this->destroyJoinTree();
- return $baseRecord;
+ return $this->asArray?$baseRecord->toArray():$baseRecord;
}
else
$this->destroyJoinTree();
@@ -121,14 +140,26 @@ public function findBySql($sql,$params=array())
*/
public function findAllBySql($sql,$params=array())
{
- Yii::trace(get_class($this->_joinTree->model).'.findAllBySql() eagerly','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this->_joinTree->model).'.findAllBySql() eagerly'.($this->asArray?' as array':''),'system.db.ar.CActiveRecord');
if(($rows=$this->_builder->createSqlCommand($sql,$params)->queryAll())!==array())
{
- $baseRecords=$this->_joinTree->model->populateRecords($rows,false);
+ if($this->asArray)
+ {
+ $baseRecords=array();
+ foreach($rows as $row)
+ $baseRecords[]=new CActiveRecordArray($row,$this->_joinTree->model);
+ }
+ else
+ $baseRecords=$this->_joinTree->model->populateRecords($rows,false);
$this->_joinTree->beforeFind(false);
$this->_joinTree->findWithBase($baseRecords);
$this->_joinTree->afterFind();
$this->destroyJoinTree();
+ if($this->asArray)
+ {
+ foreach($baseRecords as $index=>$record)
+ $baseRecords[$index]=$record->toArray();
+ }
return $baseRecords;
}
else
@@ -352,6 +383,7 @@ class CJoinElement
public $model;
/**
* @var array list of active records found by the queries. They are indexed by primary key values.
+ * since version 1.1.11 these will be instances of CActiveRecordArray when {@link CActiveFinder::$asArray} is true.
*/
public $records=array();
/**
@@ -499,7 +531,7 @@ public function lazyFind($baseRecord)
case 1:
$child=reset($this->children);
break;
- default: // bridge(s) inside
+ default: // bridge(s) for through-relation inside
$child=end($this->children);
break;
}
@@ -538,7 +570,10 @@ public function lazyFind($baseRecord)
if(empty($child->records))
return;
if($child->relation instanceof CHasOneRelation || $child->relation instanceof CBelongsToRelation)
- $baseRecord->addRelatedRecord($child->relation->name,reset($child->records),false);
+ {
+ $record=reset($child->records);
+ $baseRecord->addRelatedRecord($child->relation->name,$this->_finder->asArray?$record->toArray():$record,false);
+ }
else // has_many and many_many
{
foreach($child->records as $record)
@@ -547,7 +582,7 @@ public function lazyFind($baseRecord)
$index=$record->{$child->relation->index};
else
$index=true;
- $baseRecord->addRelatedRecord($child->relation->name,$record,$index);
+ $baseRecord->addRelatedRecord($child->relation->name,$this->_finder->asArray?$record->toArray():$record,$index);
}
}
}
@@ -782,13 +817,18 @@ public function beforeFind($isChild=true)
/**
* Calls {@link CActiveRecord::afterFind} of all the records.
+ * Since version 1.1.11 afterFind method is not called when
+ * {@link CActiveFinder::$asArray} is true as there are no records in this case.
*/
public function afterFind()
{
- foreach($this->records as $record)
- $record->afterFindInternal();
- foreach($this->children as $child)
- $child->afterFind();
+ if(!$this->_finder->asArray)
+ {
+ foreach($this->records as $record)
+ $record->afterFindInternal();
+ foreach($this->children as $child)
+ $child->afterFind();
+ }
$this->children = null;
}
@@ -828,7 +868,8 @@ public function runQuery($query)
* Populates the active records with the query data.
* @param CJoinQuery $query the query executed
* @param array $row a row of data
- * @return CActiveRecord the populated record
+ * @return CActiveRecord|CActiveRecordArray the populated record.
+ * since version 1.1.11 this will be an instance of {@link CActiveRecordArray} when {@link CActiveFinder::$asArray} is true.
*/
private function populateRecord($query,$row)
{
@@ -865,7 +906,10 @@ private function populateRecord($query,$row)
if(isset($aliases[$alias]))
$attributes[$aliases[$alias]]=$value;
}
- $record=$this->model->populateRecord($attributes,false);
+ if($this->_finder->asArray)
+ $record=new CActiveRecordArray($attributes,$this->model);
+ else
+ $record=$this->model->populateRecord($attributes,false);
foreach($this->children as $child)
{
if(!empty($child->relation->select))
@@ -885,13 +929,14 @@ private function populateRecord($query,$row)
else // has_many and many_many
{
// need to double check to avoid adding duplicated related objects
- if($childRecord instanceof CActiveRecord)
+ $isActiveRecord=($childRecord instanceof CActiveRecord || $childRecord instanceof CActiveRecordArray);
+ if($isActiveRecord)
$fpk=serialize($childRecord->getPrimaryKey());
else
$fpk=0;
if(!isset($this->_related[$pk][$child->relation->name][$fpk]))
{
- if($childRecord instanceof CActiveRecord && $child->relation->index!==null)
+ if($isActiveRecord && $child->relation->index!==null)
$index=$childRecord->{$child->relation->index};
else
$index=true;
@@ -1515,7 +1560,7 @@ private function queryOneMany()
$record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false);
}
- /*
+ /**
* @param string $joinTableName jointablename
* @param string $keys keys
*/
@@ -1647,6 +1692,119 @@ private function queryManyMany($joinTableName,$keys)
}
foreach($records as $pk=>$record)
- $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$this->relation->defaultValue,false);
+ $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false);
}
-}
+}
+
+/**
+ * CActiveRecordArray is a very lightweight version of active record used internally by {@link CActiveFinder}
+ * to provide asArray functionality.
+ *
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @version $Id$
+ * @package system.db.ar
+ * @since 1.1.11
+ */
+class CActiveRecordArray
+{
+ /**
+ * @var array attributes of the active record
+ */
+ public $attributes;
+
+ private $_pk;
+ private $_related=array();
+
+ /**
+ * PHP getter magic method.
+ * This method is overridden so that AR attributes can be accessed like properties.
+ * @param string $name property name
+ * @return mixed property value
+ * @throws CException if the property is not defined
+ */
+ public function __get($name)
+ {
+ if(isset($this->attributes[$name]))
+ return $this->attributes[$name];
+
+ throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
+ array('{class}'=>get_class($this), '{property}'=>$name)));
+ }
+
+ /**
+ * Constructor.
+ * @param array $attributes attributes of the new active record instance
+ * @param CActiveRecord $model
+ */
+ public function __construct($attributes, $model)
+ {
+ $this->attributes=$attributes;
+ $table=$model->getMetaData()->tableSchema;
+ if(is_string($table->primaryKey))
+ $this->_pk=$this->{$table->primaryKey};
+ else if(is_array($table->primaryKey))
+ {
+ $this->_pk=array();
+ foreach($table->primaryKey as $name)
+ $this->_pk[$name]=$this->$name;
+ }
+ else
+ $this->_pk=null;
+ }
+
+ /**
+ * @return array|string|null the primary key of this active record
+ */
+ public function getPrimaryKey()
+ {
+ return $this->_pk;
+ }
+
+ /**
+ * Do not call this method. This method is used internally by {@link CActiveFinder} to populate
+ * related objects. This method adds a related object to this record.
+ * @param string $name attribute name
+ * @param mixed $record the related record
+ * @param mixed $index the index value in the related object collection.
+ * If true, it means using zero-based integer index.
+ * If false, it means a HAS_ONE or BELONGS_TO object and no index is needed.
+ */
+ public function addRelatedRecord($name,$record,$index)
+ {
+ if($index!==false)
+ {
+ if(!isset($this->_related[$name]))
+ $this->_related[$name]=array();
+ if($record instanceof CActiveRecordArray)
+ {
+ if($index===true)
+ $this->_related[$name][]=$record;
+ else
+ $this->_related[$name][$index]=$record;
+ }
+ }
+ else if(!isset($this->_related[$name]))
+ $this->_related[$name]=$record;
+ }
+
+ /**
+ * Turns this instance to an array which will hold attributes and relations
+ * @return array the array representation of this active record
+ */
+ public function toArray()
+ {
+ $array = $this->attributes;
+ foreach($this->_related as $name=>$data)
+ {
+ if (is_array($data))
+ {
+ $array[$name]=array();
+ foreach($data as $index=>$record)
+ $array[$name][$index]=$record->toArray();
+ }
+ else
+ $array[$name]=($data instanceof CActiveRecordArray)?$data->toArray():$data;
+ }
+ return $array;
+ }
+}
View
142 framework/db/ar/CActiveRecord.php
@@ -4,7 +4,7 @@
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
@@ -32,6 +32,7 @@
* @property mixed $oldPrimaryKey The old primary key value. An array (column name=>column value) is returned if the primary key is composite.
* If primary key is not defined, null will be returned.
* @property string $tableAlias The default table alias.
+ * @property boolean $asArray The current state of find methods results. True if array of attributes, false if active record objects.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
@@ -62,6 +63,7 @@
private $_c; // query criteria (used by finder only)
private $_pk; // old primary key value
private $_alias='t'; // the table alias being used for query
+ private $_asArray=false; // a "scope" for find-methods and relations to return attribute array instead of records
/**
@@ -136,7 +138,7 @@ public function __get($name)
return $this->_attributes[$name];
else if(isset($this->getMetaData()->columns[$name]))
return null;
- else if(isset($this->_related[$name]))
+ else if(isset($this->_related[$name]) && !$this->_asArray)
return $this->_related[$name];
else if(isset($this->getMetaData()->relations[$name]))
return $this->getRelated($name);
@@ -241,7 +243,8 @@ public function __call($name,$parameters)
*/
public function getRelated($name,$refresh=false,$params=array())
{
- if(!$refresh && $params===array() && (isset($this->_related[$name]) || array_key_exists($name,$this->_related)))
+ $exists=isset($this->_related[$name]) || array_key_exists($name,$this->_related);
+ if($exists && !$refresh && $params===array() && !$this->_asArray)
return $this->_related[$name];
$md=$this->getMetaData();
@@ -249,19 +252,18 @@ public function getRelated($name,$refresh=false,$params=array())
throw new CDbException(Yii::t('yii','{class} does not have relation "{name}".',
array('{class}'=>get_class($this), '{name}'=>$name)));
- Yii::trace('lazy loading '.get_class($this).'.'.$name,'system.db.ar.CActiveRecord');
+ Yii::trace('lazy loading '.get_class($this).'.'.$name.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$relation=$md->relations[$name];
if($this->getIsNewRecord() && !$refresh && ($relation instanceof CHasOneRelation || $relation instanceof CHasManyRelation))
return $relation instanceof CHasOneRelation ? null : array();
+ if($exists && ($params!==array() || $this->_asArray)) // save existing
+ $save=$this->_related[$name];
+
if($params!==array()) // dynamic query
{
- $exists=isset($this->_related[$name]) || array_key_exists($name,$this->_related);
- if($exists)
- $save=$this->_related[$name];
-
if($params instanceof CDbCriteria)
- $params = $params->toArray();
+ $params=$params->toArray();
$r=array($name=>$params);
}
@@ -270,6 +272,7 @@ public function getRelated($name,$refresh=false,$params=array())
unset($this->_related[$name]);
$finder=new CActiveFinder($this,$r);
+ $finder->asArray=$this->_asArray;
$finder->lazyFind($this);
if(!isset($this->_related[$name]))
@@ -282,8 +285,9 @@ public function getRelated($name,$refresh=false,$params=array())
$this->_related[$name]=null;
}
- if($params!==array())
+ if($params!==array() || $this->_asArray)
{
+ $this->_asArray=false;
$results=$this->_related[$name];
if($exists)
$this->_related[$name]=$save;
@@ -347,13 +351,14 @@ public function defaultScope()
/**
* Resets all scopes and criterias applied including default scope.
- *
+ * This will also reset {@link asArray()} to false.
* @return CActiveRecord
* @since 1.1.2
*/
public function resetScope()
{
$this->_c=new CDbCriteria();
+ $this->_asArray=false;
return $this;
}
@@ -715,7 +720,7 @@ public function addRelatedRecord($name,$record,$index)
{
if(!isset($this->_related[$name]))
$this->_related[$name]=array();
- if($record instanceof CActiveRecord)
+ if($record instanceof CActiveRecord || is_array($record))
{
if($index===true)
$this->_related[$name][]=$record;
@@ -869,6 +874,8 @@ public function onBeforeFind($event)
/**
* This event is raised after the record is instantiated by a find method.
+ * Please note, that it will not be raised when find was done with {@link asArray()} as
+ * there are no records in that case.
* @param CEvent $event the event parameter
*/
public function onAfterFind($event)
@@ -1289,11 +1296,34 @@ protected function query($criteria,$all=false)
if(!$all)
$criteria->limit=1;
$command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria);
- return $all ? $this->populateRecords($command->queryAll(), true, $criteria->index) : $this->populateRecord($command->queryRow());
+
+ if($this->_asArray && $all)
+ {
+ $this->_asArray=false;
+ if($criteria->index!==null)
+ {
+ $records=array();
+ foreach($command->queryAll() as $attributes)
+ $records[$attributes[$criteria->index]]=$attributes;
+ return $records;
+ }
+ else
+ return $command->queryAll();
+ }
+ elseif($this->_asArray && !$all)
+ {
+ $this->_asArray=false;
+ if(($row=$command->queryRow())!==false)
+ return $row;
+ }
+ else
+ return $all ? $this->populateRecords($command->queryAll(), true, $criteria->index) : $this->populateRecord($command->queryRow());
}
else
{
$finder=new CActiveFinder($this,$criteria->with);
+ $finder->asArray=$this->_asArray;
+ $this->_asArray=false;
return $finder->query($criteria,$all);
}
}
@@ -1392,7 +1422,7 @@ public function setTableAlias($alias)
*/
public function find($condition='',$params=array())
{
- Yii::trace(get_class($this).'.find()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.find()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$criteria=$this->getCommandBuilder()->createCriteria($condition,$params);
return $this->query($criteria);
}
@@ -1406,7 +1436,7 @@ public function find($condition='',$params=array())
*/
public function findAll($condition='',$params=array())
{
- Yii::trace(get_class($this).'.findAll()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findAll()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$criteria=$this->getCommandBuilder()->createCriteria($condition,$params);
return $this->query($criteria,true);
}
@@ -1421,7 +1451,7 @@ public function findAll($condition='',$params=array())
*/
public function findByPk($pk,$condition='',$params=array())
{
- Yii::trace(get_class($this).'.findByPk()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findByPk()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$prefix=$this->getTableAlias(true).'.';
$criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params,$prefix);
return $this->query($criteria);
@@ -1437,7 +1467,7 @@ public function findByPk($pk,$condition='',$params=array())
*/
public function findAllByPk($pk,$condition='',$params=array())
{
- Yii::trace(get_class($this).'.findAllByPk()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findAllByPk()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$prefix=$this->getTableAlias(true).'.';
$criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params,$prefix);
return $this->query($criteria,true);
@@ -1454,7 +1484,7 @@ public function findAllByPk($pk,$condition='',$params=array())
*/
public function findByAttributes($attributes,$condition='',$params=array())
{
- Yii::trace(get_class($this).'.findByAttributes()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findByAttributes()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$prefix=$this->getTableAlias(true).'.';
$criteria=$this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params,$prefix);
return $this->query($criteria);
@@ -1471,7 +1501,7 @@ public function findByAttributes($attributes,$condition='',$params=array())
*/
public function findAllByAttributes($attributes,$condition='',$params=array())
{
- Yii::trace(get_class($this).'.findAllByAttributes()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findAllByAttributes()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$prefix=$this->getTableAlias(true).'.';
$criteria=$this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params,$prefix);
return $this->query($criteria,true);
@@ -1485,18 +1515,28 @@ public function findAllByAttributes($attributes,$condition='',$params=array())
*/
public function findBySql($sql,$params=array())
{
- Yii::trace(get_class($this).'.findBySql()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findBySql()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$this->beforeFind();
if(($criteria=$this->getDbCriteria(false))!==null && !empty($criteria->with))
{
$this->_c=null;
$finder=new CActiveFinder($this,$criteria->with);
+ $finder->asArray=$this->_asArray;
+ $this->_asArray=false;
return $finder->findBySql($sql,$params);
}
else
{
+ $this->_c=null;
$command=$this->getCommandBuilder()->createSqlCommand($sql,$params);
- return $this->populateRecord($command->queryRow());
+ if($this->_asArray)
+ {
+ $this->_asArray=false;
+ if(($row=$command->queryRow())!==false)
+ return $row;
+ }
+ else
+ return $this->populateRecord($command->queryRow());
}
}
@@ -1508,18 +1548,36 @@ public function findBySql($sql,$params=array())
*/
public function findAllBySql($sql,$params=array())
{
- Yii::trace(get_class($this).'.findAllBySql()','system.db.ar.CActiveRecord');
+ Yii::trace(get_class($this).'.findAllBySql()'.($this->_asArray?' as array':''),'system.db.ar.CActiveRecord');
$this->beforeFind();
if(($criteria=$this->getDbCriteria(false))!==null && !empty($criteria->with))
{
$this->_c=null;
$finder=new CActiveFinder($this,$criteria->with);
+ $finder->asArray=$this->_asArray;
+ $this->_asArray=false;
return $finder->findAllBySql($sql,$params);
}
else
{
+ $this->_c=null;
+ $index=($criteria!==null)?$criteria->index:null;
$command=$this->getCommandBuilder()->createSqlCommand($sql,$params);
- return $this->populateRecords($command->queryAll());
+ if($this->_asArray)
+ {
+ $this->_asArray=false;
+ if($index!==null)
+ {
+ $records=array();
+ foreach($command->queryAll() as $attributes)
+ $records[$attributes[$index]]=$attributes;
+ return $records;
+ }
+ else
+ return $command->queryAll();
+ }
+ else
+ return $this->populateRecords($command->queryAll(),true,$index);
}
}
@@ -1667,6 +1725,44 @@ public function together()
}
/**
+ * Makes find methods and relations return array of attributes instead of active record objects.
+ * This can be usefull when active record is used to build a complex query but the result does
+ * not need the magic of active record and performance will increase by not instantiating all record classes.
+ * Usage example for find methods:
+ * <pre>
+ * $post=Post::model()->asArray()->findByPk(1);
+ * $posts=Post::model()->asArray()->findAll();
+ * </pre>
+ * This also works for relations (asuming <code>author</code> and <code>categories</code> are relations):
+ * <pre>
+ * $author=$post->asArray()->author;
+ * $categories=$post->asArray()->categories;
+ * </pre>
+ * The first examples will return an array of attributes each.
+ * The second examples will return a list of attribute arrays each.
+ * When {@link CDbCriteria::with} is set on the query criteria, the resulting array will contain these
+ * relations with the relation name as array key.
+ * @param boolean $array whether find result should be array of attributes instead of active record objects
+ * @return CActiveRecord the active record instance itself.
+ * @since 1.1.11
+ */
+ public function asArray($asArray=true)
@cebe Owner
cebe added a note

btw: param is here to allow usage like this:

public function getTimezones($criteria, $asArray=false) {
    return Timezone::model()->asArray($asArray)->findAll($criteria);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ $this->_asArray=$asArray;
+ return $this;
+ }
+
+ /**
+ * Returns the current state of find methods results.
+ * @return boolean True if array of attributes, false if active record objects.
+ * @see asArray
+ */
+ public function getAsArray()
+ {
+ return $this->_asArray;
+ }
+
+ /**
* Updates records with the specified primary key(s).
* See {@link find()} for detailed explanation about $condition and $params.
* Note, the attributes are not checked for safety and validation is NOT performed.
View
489 tests/framework/db/ar/CActiveRecordAsArrayTest.php
@@ -0,0 +1,489 @@
+<?php
+
+Yii::import('system.db.CDbConnection');
+Yii::import('system.db.ar.CActiveRecord');
+
+require_once(dirname(__FILE__).'/../data/models.php');
+
+/**
+ * These tests compare results generated by normal call of CActiveRecord find*()-methods
+ * with the result of find*()-methods in combination with asArray()
+ * It is not checked if the result is the correct one expected related to the query criteria.
+ *
+ * Currently uncovered:
+ * - composite pk
+ */
+class CActiveRecordAsArrayTest extends CTestCase
+{
+ protected $backupStaticAttributes = true;
+
+ private $_connection;
+
+ protected function setUp()
+ {
+ if(!extension_loaded('pdo') || !extension_loaded('pdo_sqlite'))
+ $this->markTestSkipped('PDO and SQLite extensions are required.');
+
+ $this->_connection=new CDbConnection('sqlite::memory:');
+ $this->_connection->active=true;
+ $this->_connection->pdoInstance->exec(file_get_contents(dirname(__FILE__).'/../data/sqlite.sql'));
+ CActiveRecord::$db=$this->_connection;
+ }
+
+ protected function tearDown()
+ {
+ $this->_connection->active=false;
+ }
+
+
+ /**
+ * asserts that relations have been loaded to array
+ * @param $array
+ * @param array $relations
+ */
+ protected function assertNotRelationLoaded($array, $relations=array())
+ {
+ foreach($relations as $relation)
+ {
+ $this->assertArrayNotHasKey($relation, $array, 'relation "'.$relation.'" should not have been fetched.');
+ }
+ }
+
+ /**
+ * asserts equality of attribute array and $model->attributes
+ * @param array $array
+ * @param CActiveRecord $record
+ */
+ protected function assertArrayMatchesRecord($array, $record, $scope='record')
+ {
+ $this->assertTrue(is_array($array), 'result of asArray query has to be an array ('.$scope.'). Found '.gettype($array).(is_object($array)?'('.get_class($array).')':'').' instead.');
+ $this->assertTrue($record instanceof CActiveRecord, 'record has to be instanceof CActiveRecord ('.$scope.'). Found '.gettype($record).(is_object($record)?'('.get_class($record).')':'').' instead.');
+
+ // attributes
+ foreach($record->attributes as $attribute => $value)
+ {
+ $this->assertArrayHasKey($attribute, $array, 'attribute "'.$attribute.'" is not present in array ('.$scope.').');
+ $this->assertEquals($value, $array[$attribute], 'array value of attribute "'.$attribute.'" does not match record value ('.$scope.').');
+ }
+
+ // relations
+ foreach($record->relations() as $relationName => $relation)
+ {
+ if ($record->hasRelated($relationName))
+ {
+ $this->assertArrayHasKey($relationName, $array, 'relation "'.$relationName.'" should not have been fetched ('.$scope.').');
+ if ($relation[0] == CActiveRecord::BELONGS_TO || $relation[0] == CActiveRecord::HAS_ONE)
+ {
+ if (($record->$relationName)===null)
+ $this->assertNull($array[$relationName], 'relation "'.$relationName.'" should be NULL, but is '.gettype($array).' instead ('.$scope.').');
+ else
+ {
+ $this->assertNotNull($array[$relationName], 'relation "'.$relationName.'" should not be NULL ('.$scope.').');
+ $this->assertArrayMatchesRecord($array[$relationName], $record->$relationName, $scope.'=>relation "'.$relationName.'"');
+ }
+ }
+ elseif ($relation[0] == CActiveRecord::STAT)
+ $this->assertEquals($array[$relationName], $record->$relationName, 'value of stat relation has to be the same on record and array result. ('.$scope.'=>relation "'.$relationName.'")');
+ else
+ $this->assertAllArraysMatchRecords($record->$relationName, $array[$relationName], $scope.'=>relation "'.$relationName.'"');
+ }
+ else
+ $this->assertArrayNotHasKey($relationName, $array, 'relation "'.$relationName.'" should not have been fetched ('.$scope.').');
+ }
+ }
+
+ /**
+ * @param CActiveRecord[] $records
+ * @param array $arrays
+ */
+ public function assertAllArraysMatchRecords($records, $arrays, $scope='record')
+ {
+ $this->assertEquals(count($records), count($arrays), 'array count should be equal to record count ('.$scope.').');
+ foreach($records as $key => $record) {
+ $this->assertArrayHasKey($key, $arrays, 'array result should have same keys as record result ('.$scope.'):');
+ $this->assertArrayMatchesRecord($arrays[$key], $record, $scope);
+ }
+ }
+
+
+ public function testModel()
+ {
+ $model=Post::model();
+ $this->assertTrue($model instanceof Post);
+ $this->assertFalse($model->getAsArray());
+ $this->assertTrue($model->asArray()->getAsArray());
+ $this->assertFalse($model->asArray(false)->getAsArray());
+ $model->asArray();
+
+ $post=new Post;
+ $this->assertFalse($post->getAsArray());
+ }
+
+ /**
+ * test empty results
+ */
+ public function testFindEmptyResults()
+ {
+ $this->assertFalse(Post::model()->getAsArray());
+
+ // find
+ $this->assertNull(Post::model()->asArray()->find('id=6')); // CActiveRecord
+ $this->assertNull(Post::model()->asArray()->find(array('condition'=>'t.id=6', 'with'=>'author'))); // CActiveFinder
+ $this->assertFalse(Post::model()->getAsArray());
+ // findAll
+ $this->assertTrue(Post::model()->asArray()->findAll('id=6')===array()); // CActiveRecord
+ $this->assertTrue(Post::model()->asArray()->findAll(array('condition'=>'t.id=6', 'with'=>'author'))===array()); // CActiveFinder
+ $this->assertFalse(Post::model()->getAsArray());
+
+ // findByAttributes
+ $this->assertNull(Post::model()->asArray()->findByAttributes(array('author_id'=>5)));
+ $this->assertFalse(Post::model()->getAsArray());
+ // findAllByAttributes
+ $this->assertTrue(Post::model()->asArray()->findAllByAttributes(array('author_id'=>5))===array());
+ $this->assertFalse(Post::model()->getAsArray());
+
+ // findByPk
+ $this->assertNull(Post::model()->asArray()->findByPk(array()));
+ $this->assertNull(Post::model()->asArray()->findByPk(null));
+ $this->assertNull(Post::model()->asArray()->findByPk(6));
+ $this->assertFalse(Post::model()->getAsArray());
+ // findAllByPk
+ $this->assertTrue(Post::model()->asArray()->findAllByPk(array(6,7))===array());
+ $this->assertFalse(Post::model()->getAsArray());
+
+ // findBySql
+ $this->assertNull(Post::model()->asArray()->findBySql('select * from posts where id=:id',array(':id'=>6)));
+ $this->assertFalse(Post::model()->getAsArray());
+ // findAllBySql
+ $this->assertTrue(Post::model()->asArray()->findAllBySql('select * from posts where id>:id',array(':id'=>6))===array());
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+
+ /**
+ * @return array a matrix of conditions and ids
+ */
+ public function dataFindParams()
+ {
+ $ids = array(1, 2, 3);
+ $params = array(
+ array('', array()), // default arguments to find()
+ array(array('with'=>array('author','categories','commentCount')), array()), // commentCount is a stat relation
+ array(array('with'=>array('author'=>array('with'=>'groups'),'categories')), array()), // groups is a through relation
+ array(array('with'=>array('author','categories','commentCount'), 'together'=>true), array()),
+ array(array('with'=>array('author'=>array('with'=>'groups'),'categories'=>array('with'=>'postCount')), 'together'=>true), array()),// postCount is a many many stat relation
+ );
+ $data = array();
+ foreach($ids as $id) {
+ foreach($params as $param) {
+ $data[] = array_merge($param, array($id));
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * @return array a matrix of conditions
+ */
+ public function dataFindAllParams()
+ {
+ /** @var CDbCriteria[] $limits */
+ $limits = array(
+ array('limit'=>3,'offset'=>1),
+ array('index'=>'id'),
+ );
+ /** @var CDbCriteria[] $withs */
+ $withs = array(
+ array(),
+ array('with'=>array('author','categories','commentCount')), // commentCount is a stat relation
+ array('with'=>array('author'=>array('with'=>'groups'),'categories')), // groups is a through relation
+ array('with'=>array('author','categories','commentCount'), 'together'=>true),
+ array('with'=>array('author'=>array('with'=>'groups'),'categories'), 'together'=>true),
+ array('with'=>array('author','categories'=>array('index'=>'id', 'with'=>'postCount'))),// postCount is a many many stat relation
+ );
+
+ $data = array(
+ array('', array()), // default arguments to find()
+ array('id>2', array()),
+ );
+ foreach($limits as $limit)
+ {
+ foreach($withs as $with)
+ {
+ $c = new CDbCriteria($limit);
+ $c->mergeWith($with);
+ $data[] = array($c, array());
+ //$data[serialize($c->toArray())] = array($c, array());
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * test find() with various parameters
+ * @dataProvider dataFindParams
+ */
+ public function testFind($condition, $params, $id)
+ {
+ if (is_string($condition))
+ {
+ $condition .= (($condition=='')?'':' AND ').'t.id=:id';
+ $params[':id'] = $id;
+ }
+ else
+ {
+ if (isset($condition['condition']))
+ $condition['condition'] .= (($condition['condition']=='')?'':' AND ').'t.id=:id';
+ else
+ $condition['condition'] = 't.id=:id';
+ $condition['params'][':id'] = $id;
+ }
+
+ $post=Post::model()->find($condition, $params);
+ $posta=Post::model()->asArray()->find($condition, $params);
+
+ $this->assertArrayMatchesRecord($posta, $post);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+ /**
+ * test findAll() with various parameters
+ * @dataProvider dataFindAllParams
+ */
+ public function testFindAll($condition, $params)
+ {
+ $posts=Post::model()->findAll($condition, $params);
+ $this->assertGreaterThan(0,count($posts));
+ $postsa=Post::model()->asArray()->findAll($condition, $params);
+
+ $this->assertAllArraysMatchRecords($posts, $postsa);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+ /**
+ * test findByPk() with various parameters
+ * @dataProvider dataFindParams
+ */
+ public function testFindByPk($condition, $params, $id)
+ {
+ $post=Post::model()->findByPk($id, $condition, $params);
+ $this->assertEquals($id,$post->id);
+ $posta=Post::model()->asArray()->findByPk($id, $condition, $params);
+
+ $this->assertArrayMatchesRecord($posta, $post);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+ /**
+ * test findAllByPk() with various parameters
+ * @dataProvider dataFindAllParams
+ */
+ public function testFindAllByPk($condition, $params)
+ {
+ if ($condition instanceof CDbCriteria && $condition->offset>=0)
+ $condition->offset=0;
+
+ $posts=Post::model()->findAllByPk(4, $condition, $params);
+ $this->assertGreaterThan(0,count($posts));
+ $postsa=Post::model()->asArray()->findAllByPk(4, $condition, $params);
+
+ $this->assertAllArraysMatchRecords($posts, $postsa);
+
+ $this->assertFalse(Post::model()->getAsArray());
+
+ $posts=Post::model()->findAllByPk(array(4,3,2), $condition, $params);
+ $postsa=Post::model()->asArray()->findAllByPk(array(4,3,2), $condition, $params);
+ $this->assertGreaterThan(0,count($posts));
+
+ $this->assertAllArraysMatchRecords($posts, $postsa);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+
+ /**
+ * test findByAttributes() with various parameters
+ * @dataProvider dataFindParams
+ */
+ public function testFindByAttributes($condition, $params, $id)
+ {
+ $post=Post::model()->findByAttributes(array('author_id'=>$id),array('order'=>'id DESC'), $condition, $params);
+ $posta=Post::model()->asArray()->findByAttributes(array('author_id'=>$id),array('order'=>'id DESC'), $condition, $params);
+
+ $this->assertArrayMatchesRecord($posta, $post);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+ /**
+ * test findAllByAttributes() with various parameters
+ * @dataProvider dataFindAllParams
+ */
+ public function testFindAllByAttributes($condition, $params)
+ {
+ $posts=Post::model()->findAllByAttributes(array('author_id'=>2), $condition, $params);
+ $this->assertGreaterThan(0,count($posts));
+ $postsa=Post::model()->asArray()->findAllByAttributes(array('author_id'=>2), $condition, $params);
+
+ $this->assertAllArraysMatchRecords($posts, $postsa);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+ /**
+ * test findBySql() with various parameters
+ * @dataProvider dataFindParams
+ */
+ public function testFindBySql($condition, $params, $id)
+ {
+ if (is_array($condition) || $condition instanceof CDbCriteria)
+ Post::model()->getDbCriteria()->mergeWith($condition);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ $post=Post::model()->findBySql('select * from posts where id=:id',array(':id'=>$id));
+
+ if (is_array($condition) || $condition instanceof CDbCriteria)
+ Post::model()->getDbCriteria()->mergeWith($condition);
+
+ $posta=Post::model()->asArray()->findBySql('select * from posts where id=:id',array(':id'=>$id));
+
+ $this->assertArrayMatchesRecord($posta, $post);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+ /**
+ * test findAllBySql() with various parameters
+ * @dataProvider dataFindAllParams
+ */
+ public function testFindAllBySql($condition, $params)
+ {
+ if (is_array($condition) || $condition instanceof CDbCriteria)
+ Post::model()->getDbCriteria()->mergeWith($condition);
+
+ $posts=Post::model()->findAllBySql('select * from posts where id>:id',array(':id'=>2));
+ $this->assertGreaterThan(2,count($posts));
+
+ if (is_array($condition) || $condition instanceof CDbCriteria)
+ Post::model()->getDbCriteria()->mergeWith($condition);
+
+ $postsa=Post::model()->asArray()->findAllBySql('select * from posts where id>:id',array(':id'=>2));
+
+ $this->assertAllArraysMatchRecords($posts, $postsa);
+
+ $this->assertFalse(Post::model()->getAsArray());
+ }
+
+
+ public function testRelation()
+ {
+ $post=Post::model()->findByPk(3);
+ $this->assertFalse($post->getAsArray());
+
+ $this->assertArrayMatchesRecord(
+ $post->asArray()->author,
+ $post->author
+ );
+ $this->assertFalse($post->getAsArray());
+
+ $this->assertArrayMatchesRecord(
+ $post->asArray()->author(array('with'=>'posts')),
+ $post->author(array('with'=>'posts'))
+ );
+ $this->assertFalse($post->getAsArray());
+
+ $this->assertArrayMatchesRecord(
+ $post->asArray()->author(array('with'=>'groups')), // groups is a through relation
+ $post->author(array('with'=>'groups'))
+ );
+ $this->assertFalse($post->getAsArray());
+
+ $this->assertArrayMatchesRecord(
+ $post->asArray()->author(array('with'=>'posts', 'together'=>true)),
+ $post->author(array('with'=>'posts', 'together'=>true))
+ );
+ $this->assertFalse($post->getAsArray());
+
+ $this->assertArrayMatchesRecord(
+ $post->asArray()->author(array('with'=>'groups', 'together'=>true)), // groups is a through relation
+ $post->author(array('with'=>'groups', 'together'=>true))
+ );
+ $this->assertFalse($post->getAsArray());
+
+ $post1=Post::model()->findByPk(1);
+ $this->assertFalse($post1->getAsArray());
+
+ $this->assertAllArraysMatchRecords(
+ $post1->categories,
+ $post1->asArray()->categories
+ );
+ $this->assertFalse($post1->getAsArray());
+ $this->assertGreaterThan(0,count($post1->categories));
+
+ $this->assertAllArraysMatchRecords(
+ $post1->categories(array('with'=>'posts')),
+ $post1->asArray()->categories(array('with'=>'posts'))
+ );
+ $this->assertFalse($post1->getAsArray());
+
+ $this->assertAllArraysMatchRecords(
+ $post1->categories(array('with'=>'posts', 'together'=>true)),
+ $post1->asArray()->categories(array('with'=>'posts', 'together'=>true))
+ );
+ $this->assertFalse($post1->getAsArray());
+ }
+
+ public function testDeepNestedWith()
+ {
+/* $post=Post::model()->findByPk(3);
+ $this->assertFalse($post->getAsArray());
+
+ $this->assertArrayMatchesRecord(
+ $post->asArray()->author(array('with'=>'posts', 'together'=>true)),
+ $post->author(array('with'=>'posts', 'together'=>true))
+ );
+
+ $this->assertFalse($post->getAsArray());*/
+ }
+ /**
+ * test empty results
+ */
+ public function testRelationEmptyResults()
+ {
+ $user=User::model()->findByPk(4);
+ $this->assertFalse($user->getAsArray());
+
+ $post=Post::model()->findByPk(4);
+ $this->assertFalse($post->getAsArray());
+
+ $cat=Category::model()->findByPk(3);
+ $this->assertFalse($cat->getAsArray());
+
+ // MANY_MANY
+ $this->assertTrue($post->asArray()->categories===array());
+ $this->assertFalse($post->getAsArray());
+ $this->assertTrue($post->asArray()->categories(array('with'=>'parent'))===array());
+ $this->assertFalse($post->getAsArray());
+
+ // HAS_MANY
+ $this->assertTrue($user->asArray()->posts===array());
+ $this->assertFalse($user->getAsArray());
+ $this->assertTrue($user->asArray()->posts(array('with'=>'author'))===array());
+ $this->assertFalse($user->getAsArray());
+
+ // HAS_ONE
+ $this->assertNull($post->asArray()->firstComment);
+ $this->assertFalse($post->getAsArray());
+ $this->assertNull($post->asArray()->firstComment(array('with'=>'author')));
+ $this->assertFalse($post->getAsArray());
+
+ // BELONGS_TO
+ $this->assertNull($cat->asArray()->parent);
+ $this->assertFalse($cat->getAsArray());
+ $this->assertNull($cat->asArray()->parent(array('with'=>'children')));
+ $this->assertFalse($cat->getAsArray());
+ }
+}
Something went wrong with that request. Please try again.