Getting Setting read-only property error in index with related data [solved]

Hello everyone.

Im getting this exception “Invalid Call – [yii\base\InvalidCallException] Setting read-only property: app\models\AsignaturaSearch::profesor” when I search data in the index view generated with Gii.

Here’s the code:

Model Asignatura.php

class Asignatura extends \yii\db\ActiveRecord
{
        
    public static function tableName()
    {
        return 'asignatura';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['nombre'], 'required'],
            [['id_profesor'], 'integer'],
            [['nombre', 'descripcion'], 'string', 'max' => 45],
            [['id_profesor'], 'exist', 'skipOnError' => true, 'targetClass' => Profesor::className(), 'targetAttribute' => ['id_profesor' => 'id']],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'nombre' => 'Nombre',
            'descripcion' => 'Descripcion',
            'id_profesor' => 'Id Profesor',
        ];
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getProfesor()
    {
        return $this->hasOne(Profesor::className(), ['id' => 'id_profesor']);
    }

    /**
     * @return \yii\db\ActiveQuery
     */
    public function getNotas()
    {
        return $this->hasMany(Nota::className(), ['id_asignatura' => 'id']);
    }
}

Model AsignaturaSearch.php

class AsignaturaSearch extends Asignatura
{
    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['id', 'id_profesor'], 'integer'],
            [['nombre', 'descripcion', 'profesor'], 'safe'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Asignatura::find();

        $query->joinWith(['profesor']);
        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $dataProvider->sort->attributes['profesor'] = [
        // The tables are the ones our relation are configured to
        'asc' => ['profesor.nombre' => SORT_ASC],
        'desc' => ['profesor.nombre' => SORT_DESC],
        ];
        
        if (!($this->load($params) && $this->validate())) 
        {
            return $dataProvider;
        }
        
        $this->load($params);

        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }

        // grid filtering conditions
        $query->andFilterWhere([
            'id' => $this->id,
            'id_profesor' => $this->id_profesor,
        ]);

        $query->andFilterWhere(['like', 'nombre', $this->nombre])
            ->andFilterWhere(['like', 'descripcion', $this->descripcion])
            ->andFilterWhere(['like', 'profesor.nombre', $this->profesor]);

        return $dataProvider;
    }
}

Controller AsignaturaController.php

public function actionIndex()
    {
        if(Yii::$app->user->isGuest) {
            return $this->redirect(Url::toRoute('site/login'));
        }
        $searchModel = new AsignaturaSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

view index.php

<?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],
            'nombre',
            'descripcion',
            ['attribute' => 'profesor',
            'value' => 'profesor.nombre'],
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>

I don’t know where is the error. I hope anyone can help me, I’ll be forever gratefull.

<?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],
            'nombre',
            'descripcion',
//          ['attribute' => 'profesor',
//          'value' => 'profesor.nombre'],
            'profesor.nombre',
// OR
            ['attribute' => 'profesor.nombre',
             'label' => 'Profesor'],
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>

profesor is a read-only property implemented by getProfesor() getter method that defines a relation. You can’t use it as a filed in a search form or as a filter field in a grid view header.

Thank you for your answer @softark. I’ve been reading this wiki [How to create/update a model with its related items using Listbox or CheckboxList] https://www.yiiframework.com/wiki/836/how-to-createupdate-a-model-with-its-related-items-using-listbox-or-checkboxlist. Maybe I could do something with it. Any thoughts?

Sorry, but I don’t get what you want. Please be specific.

I need to filter by the attributes of profesor.

Check this wiki:
https://www.yiiframework.com/wiki/653/displaying-sorting-and-filtering-model-relations-on-a-gridview

1 Like

Man I can’t believe it. I was missing this line in the AsignaturaSearch model

public $profesor;

I feel so ashamed Lol.
Thanks for reminding me this.

Well, you should not use the word profesor. Use another word like profesor_name or profesor_nombre. Because Asignatura and AsignaturaSearch already has profesor that is the name of the relation to Profesor model. You’d better not overwrite it.

Asignatura has a name profesor and it is the name of the relation to Profesor. And as AsignaturaSearch is extended from Asignatura, it also has profesor that is a kind of read-only property that defines the relation to Profesor. You should not overwrite it by public $profesor.

If you want to use a property of Profesor in AsignaturaSearch, you can define a property like profesor_name or profesor_nombre in AsignaturaSearch.