phpyiigridview-sorting

CGridView column based on relation displays incorrect data


I am currently working on a web application (written in PHP, based on Yii), which, among other things, lets you connect different price values for products. Each product can have multiple prices, but the system is built in a way that I can easily (and correctly) determine the type of the price field - so while each product can have multiple price fields, it can only have one of each type of price fields.

The part where I stuck is when I have to display the stored value of these fields in a list, and order the list view by them - I can display them correctly and sort them by one column, but as soon as I try to make the list to be sortable by all of the columns (not at the same time, of course), the rows start to display the wrong values.

Here are the relevant pieces of code:

In the model, at relations:

'productPriceConnects' => array(Product::HAS_MANY, 'ProductPriceConnect', 'product_id'),
'price1' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
    'joinType' => 'LEFT JOIN',
    'on' => 'priceBeszerzesi.product_price_field_id=:product_price_field_id AND priceBeszerzesi.active = 1',
    'params' => array(':product_price_field_id' =>ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 6))->id),
    'alias' => 'priceBeszerzesi',
),
'price2' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
    'joinType' => 'LEFT JOIN',
    'on' => 'priceEladasi.product_price_field_id=:product_price_field_id AND priceEladasi.active = 1',
    'params' => array(':product_price_field_id' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 1))->id),
    'alias' => 'priceEladasi'
),
'price3' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
    'joinType' => 'LEFT JOIN',
    'on' => 'priceAkcios.product_price_field_id=:product_price_field_id AND priceAkcios.active = 1',
    'params' => array(':product_price_field_id' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 5))->id),
    'alias' => 'priceAkcios'
),

In the model, at search:

...
$criteria->with('price1', 'price2', 'price3);

...

$criteria->compare('price1.price', $this->beszerzesi, true);
$criteria->compare('price2.price', $this->eladasi, true);
$criteria->compare('price3.price', $this->akcios, true);

...
$sort->attributes = array(
    'price1' =>array(
        'asc' => 'priceBeszerzesi.price ASC',
        'desc' => 'priceBeszerzesi.price DESC',
    ),
    'price2' =>array(
        'asc' => 'priceEladasi.price ASC',
        'desc' => 'priceEladasi.price DESC',
    ),
    'priceAkcios' =>array(
        'asc' => 'price3.price ASC',
        'desc' => 'price3.price DESC',
    ),
    '*'
);

...

return new CActiveDataProvider($this, array(
        'criteria' => $criteria,
        'sort' => $sort,
        'pagination' => array(
            'pageSize' => 10,
        ),
    )
);

The columns' data in gridview:

'eladasi' => array(
    'name' => 'price2',
    'header' => 'Eladási ár',
    'type' => 'raw',
    'headerHtmlOptions' => array('class' => 'auto-width text-center'),
    'value' => '!empty($data->price2->price) ? $data->price2->price == "" ? "-" : $data->price2->price : "-"',
    'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
'akcios' => array(
    'name' => 'priceAkcios',
    'header' => 'Akciós',
    'value' => '!empty($data->price3->price) ? $data->price3->price == "" ? "-" : $data->price3->price : "-"',
    'headerHtmlOptions' => array('class' => 'auto-width text-center'),
    'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),
    'beszerzesi' => array(
    'name' => 'price1',
    'header' => 'Beszerzési ár',
    'type' => 'raw',
    'value' => '!empty($data->price1->price) ? $data->price1->price == "" ? "-" : $data->price1->price : "-"',
    'headerHtmlOptions' => array('class' => 'auto-width text-center'),
    'htmlOptions' => array('class' => 'border-right auto-width text-center'),
),

This code makes it possible to sort the list by all three of the relation-dependent columns, but every column displays the same value - the value of the last relation in the with array. If the last element in the array is price3, then the columns display the value of the price3 relation. When I remove all relation names from the with array expect one, I can sort the list by that column, but not the others.

My question is this: Is there any way to 1) surely add any number of relations to a model, connecting to the same db field, but depending on conditions, 2) and display these values WHILE enabling sorting based on them?


Solution

  • Find solution below:

    I created these tables at my system and used your relation and gridview code. I made some changes in that code and now in below code searching and sorting is working perfectly.

    I defined three variable in model class i.e.

    public $beszerzesi;
    public $eladasi;
    public $akcios;
    

    Then I changed the param's name in relation arraye used with left join. This was the main issue in your code. You used same name for parameters i.e. :product_price_field_id I assigned different name for each parameters. While yii prepare sql query it will replace the parameters which are assigned to query. In your case it was replacing same value for all three parameters.

    Also i made some changes in sort and compare attributes passed in CActiveDataProvider. You can find all changes in below model file.

    Product.php

    <?php
    
    /**
     * This is the model class for table "product".
     *
     * The followings are the available columns in table 'product':
     * @property integer $id
     * @property string $name
     */
    class Product extends CActiveRecord
    {
        public $beszerzesi;
        public $eladasi;
        public $akcios;
    
        /**
         * @return string the associated database table name
         */
        public function tableName()
        {
            return 'product';
        }
    
        /**
         * @return array validation rules for model attributes.
         */
        public function rules()
        {
            // NOTE: you should only define rules for those attributes that
            // will receive user inputs.
            return array(
                array('name', 'required'),
                array('name', 'length', 'max' => 100),
                // The following rule is used by search().
                // @todo Please remove those attributes that should not be searched.
                array('id, name, beszerzesi, eladasi,akcios', 'safe', 'on' => 'search'),
            );
        }
    
        /**
         * @return array relational rules.
         */
        public function relations()
        {
            // NOTE: you may need to adjust the relation name and the related
            // class name for the relations automatically generated below.
            return array(
                'productPriceConnects' => array(Product::HAS_MANY, 'ProductPriceConnect', 'product_id'),
                'price1' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
                    'joinType' => 'LEFT JOIN',
                    'on' => 'priceBeszerzesi.product_price_field_id=:product_price_field_id1 AND priceBeszerzesi.active = 1',
                    'params' => array(':product_price_field_id1' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 6))->id),
                    'alias' => 'priceBeszerzesi',
                ),
                'price2' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
                    'joinType' => 'LEFT JOIN',
                    'on' => 'priceEladasi.product_price_field_id=:product_price_field_id2 AND priceEladasi.active = 1',
                    'params' => array(':product_price_field_id2' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 1))->id),
                    'alias' => 'priceEladasi'
                ),
                'price3' => array(Product::HAS_ONE, 'ProductPriceConnect', 'product_id',
                    'joinType' => 'LEFT JOIN',
                    'on' => 'priceAkcios.product_price_field_id=:product_price_field_id3 AND priceAkcios.active = 1',
                    'params' => array(':product_price_field_id3' => ProductPriceField::model()->findByAttributes(array('active' => 1, 'type' => 1, 'category' => 5))->id),
                    'alias' => 'priceAkcios'
                ),
            );
        }
    
        /**
         * @return array customized attribute labels (name=>label)
         */
        public function attributeLabels()
        {
            return array(
                'id' => 'ID',
                'name' => 'Name',
            );
        }
    
        /**
         * Retrieves a list of models based on the current search/filter conditions.
         *
         * Typical usecase:
         * - Initialize the model fields with values from filter form.
         * - Execute this method to get CActiveDataProvider instance which will filter
         * models according to data in model fields.
         * - Pass data provider to CGridView, CListView or any similar widget.
         *
         * @return CActiveDataProvider the data provider that can return the models
         * based on the search/filter conditions.
         */
        public function search()
        {
            // @todo Please modify the following code to remove attributes that should not be searched.
    
            $criteria = new CDbCriteria;
    
            $criteria->with = array('price1', 'price2', 'price3');
    
            $criteria->compare('id', $this->id);
            $criteria->compare('name', $this->name, true);
    
            $criteria->compare('priceBeszerzesi.price', $this->beszerzesi, true);
            $criteria->compare('priceEladasi.price', $this->eladasi, true);
            $criteria->compare('priceAkcios.price', $this->akcios, true);
    
    //        $criteria->attributes = ;
    
            return new CActiveDataProvider($this, array(
                    'criteria' => $criteria,
                    'sort' => array(
                        'attributes' => array(
                            'beszerzesi' => array(
                                'asc' => 'priceBeszerzesi.price',
                                'desc' => 'priceBeszerzesi.price DESC',
                            ),
                            'eladasi' => array(
                                'asc' => 'priceEladasi.price',
                                'desc' => 'priceEladasi.price DESC',
                            ),
                            'akcios' => array(
                                'asc' => 'priceAkcios.price',
                                'desc' => 'priceAkcios.price DESC',
                            ),
                            '*'
                        )
                    ),
                    'pagination' => array(
                        'pageSize' => 10,
                    ),
                )
            );
        }
    
        /**
         * Returns the static model of the specified AR class.
         * Please note that you should have this exact method in all your CActiveRecord descendants!
         * @param string $className active record class name.
         * @return Product the static model class
         */
        public static function model($className = __CLASS__)
        {
            return parent::model($className);
        }
    }
    

    gridview code

    <?php $this->widget('zii.widgets.grid.CGridView', array(
        'id' => 'product-grid',
        'dataProvider' => $model->search(),
        'filter' => $model,
        'columns' => array(
            'id',
            'name',
            'eladasi' => array(
                'name' => 'eladasi',
                'header' => 'Eladási ár',
                'type' => 'raw',
                'headerHtmlOptions' => array('class' => 'auto-width text-center'),
                'value' => '!empty($data->price2->price) ? $data->price2->price == "" ? "-" : $data->price2->price : "-"',
                'htmlOptions' => array('class' => 'border-right auto-width text-center'),
            ),
            'akcios' => array(
                'name' => 'akcios',
                'header' => 'Akciós',
                'value' => '!empty($data->price3->price) ? $data->price3->price == "" ? "-" : $data->price3->price : "-"',
                'headerHtmlOptions' => array('class' => 'auto-width text-center'),
                'htmlOptions' => array('class' => 'border-right auto-width text-center'),
            ),
            'beszerzesi' => array(
                'name' => 'beszerzesi',
                'header' => 'Beszerzési ár',
                'type' => 'raw',
                'value' => '!empty($data->price1->price) ? $data->price1->price == "" ? "-" : $data->price1->price : "-"',
                'headerHtmlOptions' => array('class' => 'auto-width text-center'),
                'htmlOptions' => array('class' => 'border-right auto-width text-center'),
            ),
            array(
                'class' => 'CButtonColumn',
            ),
        ),
    )); ?>
    

    You can found step by step guide for the searching and sorting on relation data at Searching and sorting by related model in CGridView | Wiki | Yii PHP Framework