primary key as index when using findAll()

I am doing this typical operation with AR:

$foo = bar::model()->findAll();

But I would like the returned array to have the primary key as index instead of zero based indexing.

Is there a shortcut to do this (also for findAllByAttributes() and findAllByPk())?

If you want the id of the objects as the array keys you have to do it manually.

Why would you want that? Perhaps there is another way…

I need this quite often in my projects, right now I am doing it manually by looping though the array and building a new one with the PK as index. When you use relational AR you can actually specify which column you would like to use as index, only when using the findAll methods there is no such parameter.

I agree that this would be very useful in many situations. It’s cumbersome to loop over a result just to find a specific record. Maybe add it to CDbCriteria as property index?


$posts=Post::model()->findAll(array('index'=>'id'));

+1! This would be extremely useful to me. Before I found yii, I always returned id indexed arrays from db-gathering functions.

The first argument for findAll is the $condition (used by the WHERE clause in sql) so I don’t think you can do it like that. But if we add a 3rd argument to findAll (or alternatively make a new function) it could work like this:




public array findAll(mixed $condition='', array $params=array(), bool $index=false)


$condition	mixed  	query condition or criteria.

$params		array 	parameters to be bound to an SQL statement.

$index		bool	primary key(s) is used as index in the returned array. Multiple keys is seperated with '_'



Usage:




$posts=Post::model()->findAll('', array(), true);

// returns:

// Array

// (

//   [34] => item_with_primary_key_of_34

//   [56] => item_with_primary_key_of_56

// )

// or:

// Array

// (

//   [34_3] => item_with_primary_key_of_34_and_3

//   [34_6] => item_with_primary_key_of_34_and_6

// )



My 2 cent :)

You can also supply a CDbCriteria or an array with parameters for CDbCriteria as first argument (note the mixed before $condition). That’s why i suggested to add another parameter to CDbCriteria. Syntax could be like:


'index'=> true // uses Pk as index

'index'=> columnX // uses specified column value as index

'index'=> 'pk1,pk2' // uses specified Pk columns separated by ',' as index (more common than _)



Add following code to your model class will make findAll() return array with primary key index.




	public function populateRecords($data,$callAfterFind=true)

	{

		$records=array();

		$md=$this->getMetaData();

		$table=$md->tableSchema;

		foreach($data as $n=>$attributes)

		{

			$record=$this->instantiate($attributes);

			foreach($attributes as $name=>$value)

			{

				if(property_exists($record,$name))

					$record->$name=$value;

				else if(isset($this->getMetaData()->columns[$name]))

					$record->setAttribute($name,$value);

			}

			$record->attachBehaviors($record->behaviors());

			if($callAfterFind)

				$record->afterFind();

			$records[$record->getPrimaryKey()]=$record;

		}

		return $records;

	}



But this just override the method of CActiveRecord.

It will not work when you use findAll() in relational model.

Yes, I see your point, but all the other parameters of CDbCriteria seems directly related to SQL queries, so it seems to me a little “wrong” to put it there. We don’t want to screw with the query itself, only the way yii returns the result. I might not be right, but do you see my logic? Seems more natural to do it in the function that uses the criteria (ie findAll).

Yeah it doesn’t fit perfectly. But having another parameter would make the method signature more complicated - just for some special scenario (which we find useful, but others might never use it…). Something that Qiang always tries to avoid. And i agree with that.

this functionality is very important for me - i use it a lot to find a record from very large arrays returned from the db quickly and easily. thanks for your heads-up Light on the populateRecords method call to override. Here is my take on this:

protected $_returnIndex;

public function populateRecords( $data, $callAfterFind=true )


{


	$records = array();


	foreach ( $data as $attributes ) {


		$records[ $this->_returnIndex ] = $this->populateRecord( $attributes, $callAfterFind );


	}


	return $records;


}





public function setReturnIndex ( $index )


{


	$this->_returnIndex = $index;


	return $this;


}

I then use this as follows:

$records = MyModel::model()->setReturnIndex(‘chosenIndex’)->findAll();

What do people think? Seems a simple and flexible way to do this without breaking any other code…

dan :)

Sorry for the poor code in my post above, here is my amended version:

protected $_returnIndex = null;





public function populateRecords( $data, $callAfterFind=true )


{


	$records = array();


	foreach ( $data as $attributes ) {


		if ( is_null( $this->_returnIndex ) ) {


			$records[] = $this->populateRecord( $attributes, $callAfterFind );


		} else {


			$records[ $attributes[$this->_returnIndex] ] = $this->populateRecord( $attributes, $callAfterFind );


		}


	}


	return $records;


}





public function setReturnIndex ( $index=null )


{


	$this->_returnIndex = $index;


	return $this;


}