Is this bug of Yii?

Hi all!

It seems to me that I find interesting bug in Yii.

In my ActiveRecord models I often use getters to get some value from related tables.

As example:




class Clients extends CActiveRecord{

//...

public function relations()

   {

    return array(

        

        'regions' => array(self::BELONGS_TO, 'Regions', 'habitation_regions_id'),

                );

   }

//...

public function getRegion()

   {

    return $this->regions->title;

   }

} 



Then, somwhere in my views:




//view file admin.php

<?php $this->widget('zii.widgets.grid.CGridView', array(

	'id'=>'client-grid',

        'cssFile'=>Yii::app()->baseURL.'/css/MyGridview.css',

        'rowCssClass'=>array('row0', 'row1'),

	'dataProvider'=>$model->search(),

	'filter'=>$model,

	'columns'=>array(

             array(

		'class'=>'CCheckBoxColumn',

                'selectableRows'=>2,

		  ),

	     array(

		'name'=>'fullname',

		'value'=>'$data->fullname',

		'filter'=>'',

		  ),

             array(

                'name' => 'region',

                'value' => '$data->region',//method getRegion() in my Clients model

                'filter'=> CHtml::listData(Regions::model()->findAll(), 'id', 'title')

                  ),

       	),

    )); ?>



Code of controller’s action:




//ClientController.php

 public function actionAdmin()

    {

        $this->layout = '//layouts/column1';

        $model = new Clients('search');

        $model->unsetAttributes(); // clear any default values

        if (isset($_GET['Client'])){

            $model->attributes = $_GET['Client'];

            }

        $this->render('admin', array('model' => $model));

    }



Then, when I call URL http://myhost/myapplication/index.php?r=client/admin, it calls Exception, which says “Trying to get property of non-object” about my method getRegion() in model Clients.php. Field ‘habitation_regions_id’ is not empty and it’s values a valid. I tried to redeclare my getRegion() method in such way:




//Clients.php model

public function getRegion()

{

return Regions::model()->findByPk($this->habitation_regions_id)->title;

}



It returns the same error - trying to get property of non-object (‘value’=>’$data->region’ in my admin.php view in CGridView column)

So, I began to try any variants to understand error:




public function getRegion()

{

return $this->regions->title; //the same error

}



I tried such variant:




public function getRegion()

{

return getttype(Regions::model()->findByPk($this->habitation_regions_id))//returns 'object'!

}



So, if it’s object, why it says ‘trying to get property of non-object’ ?

At last, I tried such variant:




public function getRegion()

{

$region_model = Regions::model()->findByPk($this->habitation_regions_id);

return $region_model['title'];

}



You know what? It works! I can get propertie of the object as element of array, but cannot get propertie of object as property of object! I know that CActiveRecord eventually implements IteratorAggregate and ArrayAccess interfaces, but they both must works. What do you think about it?

I would rename your relation "regions" to "region" as it is expected to retrieve ONE region

then remove your method getRegion() and use your new relation by calling $data->region->title

I tried this variant. Returns the same exception.

And as i say, it’s example, and I simplified the example.

I think it could be the following error: If you use a data provider like you do in your gridview than you show several models meaning that you call getRegions several times. I believe that one of your Client models doesn’t have a corresponding Region model thus calling the title property of a non-object. If you use array access than you will simply receive a null value if that happens. But that is just my wild guess and the only thing that comes to my mind




array(

    'name' => 'region',

    'value' => '$data->region instanceof Regions ? $data->region-title : ""',//method getRegion() in my Clients model

    'filter'=> CHtml::listData(Regions::model()->findAll(), 'id', 'title')

),




May I suggest that your read this:

http://www.yiiframework.com/wiki/227/guidelines-for-good-schema-design/

That would save you from some head aches. :)

No, for testing this error I checked all values of field ‘habitation_regions_id’ and all the id’s of Regions table.

Thank you, I will take this into account.

This would be solution for that problem. Have you tried that? @Layne

I know you don’t think it is an id error, but using that piece of code would immediately show you if there is Client row without a title. I would still give it a try

I tried, all the values are empty strings. But all the fields ‘habitation_regions_id’ are valid integer values, and there are such id’s and titles in Regions model table.

Ok, this is getting weird. What irritates me the most is that you say that you can get the properties via array access.

The last thing that comes to my mind is the __get() function. When an attribute is called CActiveRecord’s magic __get() method is invoked which first checks if there is an attribute in the $model->getAttributes() array and if not it looks for the attribute in the database. If it isn’t there it looks for any relation specified with the name ‘region’. So if there is any property called “region” getRegion() will never be invoked and the value of this property will be returned

You could rename getRegion() to getRegionTitle() to be sure.

If that’s not the case I’ll give up :) Let’s hope the best

Interesting idea, but there are no any properties called ‘region’, tried to rename method, the same error :)

It’s not so critical - in fact, I can get properties via array access anywhere in my code ;)

so what is your code right now?

As a said, in my code now I use array access for object properties :)

I know it’s been quite some time since this topic was raised but I run into same problem today. I would like to share the solution I used to get it work. So here’s some code based on Layne’s example.




class Clients extends CActiveRecord{

	private $_regionTitle;

//...

	public function relations()

	{

		return array(

			'regions' => array(self::BELONGS_TO, 'Regions', 'habitation_regions_id'),

		);   

	}

//...

	public function getRegionTitle()

	{

		if ($this->_regionTitle === null && $this->regions !== null)

			$this->_regionTitle = $this->regions->title;

		return $this->_regionTitle;   

	}

} 



From this point you should be able to access regionTitle field of Clients class , that contains the coresponding region title based on defined relation.