Foreach Problem: Listing All Model Attributes (Even Null)

Hi,

Im writing an API with YII tutorial "How-To: Create a REST API", but Im with some troubles with actionList.

I have one table Profile, that I would like to list only the field Name and the Distance (calculated between 2 coordinates.)

The problem is that the response of Json is returning all fields of the table (as null).

I have already declared "public $distance;" in Profile Model, and its accesible by $model->distance.

I would like to know how can I rewrite the foreach to get only the "select" fields from CActiveDataProvider.

As I use it in others tables, should be a generic way.

Here is my code:


public function actionList() {

...

	$criteria = new CDbCriteria;

	$criteria->select = 'name, ( 3959 * acos( cos( radians(' . $latitude . ') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(' . $longitude . ') ) + sin( radians(' . $latitude . ') ) * sin( radians( lat ) ) ) ) * 1.609344 AS distance';


	$models = new CActiveDataProvider('Profile', array(

	             'criteria' => $criteria,)

	           );


	foreach($models as $model){

		$rows[] = $model->attributes;

	}

	

	$this->_sendResponse(200, CJSON::encode($rows));

...

}



Json Response: (Not returning the distance.)


[{"name":"Profile 1","id":null,"email":null,"website":null,"telephone":null,"address1":null,"address2":null,"postcode":null,"city":null,"state":null,"status":null,"lat":null,"lng":null,"created":null,"modified":null}]

Use array_filter before assign.


	foreach($models as $model){

		$rows[] = array_filter($model->attributes);


	}

Hi Charles Tang,

It worked well, but I still have one problem.

How can I have the distance in the Json too? It only return fields that are in database =/

I already declared the "public $distance;" at Profile Model.

The Json Response now is:


[{"name":"Profile 1"}]

Thanks

This may be a little off topic but why are you using a dataprovider instead of plain active record if you are just returning JSON anyway? Seems like you could cut out some unnecessary overhead?

Also have you dumped out $rows and ensured that the data you seek was actually included?

Thanks about the concern, I changed it =)

Yeah the distance is actually included (I can access it by $model->distance;), but its not parsing to the Json =/

Code now:


public function actionList() {

...

	$criteria = new CDbCriteria;

	$criteria->select = 'name, ( 3959 * acos( cos( radians(' . $latitude . ') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(' . $longitude . ') ) + sin( radians(' . $latitude . ') ) * sin( radians( lat ) ) ) ) * 1.609344 AS distance';


	$models = Profile::model()->findAll($criteria);


	foreach($models as $model){

		$rows[] = $model->attributes;

	}

	

	$this->_sendResponse(200, CJSON::encode($rows));

...

}



Thx

That because


$model->attributes

is not a real property, and actually a magic call of


$model->getAttributes()

if you want the result to include the custome property (which is not include in the db table schema)

you should call it like this


$model->getAttributes(array('name', 'distance'));

Hi Charles,

Thanks for the answers, you helped a lot!

Anothers 2 doubts appeared in my project.

My model Profile have 2 relations: events (Has Many) and users (Many to Many).

  1. Im trying to do a Profile model FindAll only with Events (id and name) related, but its returning all fields from events and also users .

  2. I would like to know if there is a simpler way to write that code.

Code:


$criteria = new CDbCriteria;

$criteria->select='id, name';

$models = Profile::model()->with(array(

						'events'=>array( 

						'select'=>'id, name'

						),

				))->findAll($criteria);

$attributes = array('id', 'name');


foreach($models as $model){

	foreach($model->getMetaData()->relations as $name=>$relation){

		$obj = $model->getRelated($name);

		$attrToTake = empty($model->attributes[$name]) ? NULL : $model->attributes[$name];

		$related[$name] = $obj instanceof CActiveRecord ? $obj->getAttributes($attrToTake) : $obj;

	}

	$rows[] = array_merge(array_filter($model->getAttributes($attributes))), $related);

}


$this->_sendResponse(200, CJSON::encode($rows));

Json Response:


{"id":"1","name":"Profile 1","distance":"5.5","events":[...],"users":[...]}]...

Hi, here is a code Idea that Im working to fix last post issues.

-> About performance: Why my FindAll, even without ‘Users’ relation, is returning at data?

-> That code can be written better?

Code Idea:




$criteria = new CDbCriteria;

$criteria->select='id, name';

$models = Profile::model()->with(array('events'=>array( 

					'select'=>'id, name'

				),

			))->findAll($criteria);

$fields = array('id', 'name');


foreach($models as $model){

	$related = array();

	$attributes = array_filter($model->getAttributes($fields));

	

	foreach($model->getMetaData()->relations as $name=>$relation)

		if (!empty($model->$name->attributes))

			$related[$name] = array_filter($model->$name->getAttributes());

				

	$rows[] = array_merge($attributes, $related);

}