PHP, Yii2 GridView filtering on relational value

Filtering a gridview by a column is damn easy in Yii 2.0. Please add the filter attribute to a gridview column having lookup values, as under:

[
        "class" => yii\grid\DataColumn::className(),
        "attribute" => "status_id",
        'filter' => ArrayHelper::map(Status::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
        "value" => function($model){
            if ($rel = $model->getStatus()->one()) {
                return yii\helpers\Html::a($rel->name,["crud/status/view", 'id' => $rel->id,],["data-pjax"=>0]);
            } else {
                return '';
            }
        },
        "format" => "raw",
], 

I'm stuck with this problem too, and my solution is rather different. I have two simple models:

Book:

class Book extends ActiveRecord
{
    ....

    public static function tableName()
    {
        return 'books';
    }

    public function getAuthor()
    {
        return $this->hasOne(Author::className(), ['id' => 'author_id']);
    }

And Author:

class Author extends ActiveRecord
{

    public static function tableName()
    {
        return 'authors';
    }

    public function getBooks()
    {
        return $this->hasMany(Book::className(), ['author_id' => 'id']);
    }

But my search logic is in different model. And i didn't find how can i implement search without creating additional field author_first_name. So this is my solution:

class BookSearch extends Model
{
    public $id;
    public $title;
    public $author_first_name;

    public function rules()
    {
        return [
            [['id', 'author_id'], 'integer'],
            [['title', 'author_first_name'], 'safe'],
        ];
    }

    public function search($params)
    {
        $query = Book::find()->joinWith(['author' => function($query) { $query->from(['author' => 'authors']);}]);
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'pagination' => array('pageSize' => 50),
            'sort'=>[
                'attributes'=>[
                    'author_first_name'=>[
                        'asc' => ['author.first_name' => SORT_ASC],
                        'desc' => ['author.first_name' => SORT_DESC],
                    ]
                ]
            ]
        ]);

        if (!($this->load($params) && $this->validate())) {
            return $dataProvider;
        }
        ....
        $query->andWhere(['like', 'author.first_name', $this->author_first_name]);
        return $dataProvider;
    }
}

This is for creating table alias: function($query) { $query->from(['author' => 'authors']);}

And GridView code is:

<?php echo GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        [
            'attribute' => 'id',
            'filter' => false,
        ],
        [
            'attribute' => 'title',
        ],
        [
            'attribute' => 'author_first_name',
            'value' => function ($model) {
                    if ($model->author) {
                        $model->author->getFullName();
                    } else {
                        return '';
                    }
                },
            'filter' => true,
        ],
        ['class' => 'yii\grid\ActionColumn'],
    ],
]); ?>

I will appreciate any critiques and advice.


This is based on the description in the guide. The base code for the SearchModel comes from the Gii code generator. This is also assuming that $this->TableB has been setup using hasOne() or hasMany() relation. See this doc.

1. Setup search model

In TableASearch model add:

public function attributes()
{
    // add related fields to searchable attributes
    return array_merge(parent::attributes(), ['TableB.attrB1']);
}

public function rules() 
{
    return [
        /* your other rules */
        [['TableB.attrB1'], 'safe']
    ];
}

Then in TableASearch->search() add (before $this->load()):

$dataProvider->sort->attributes['TableB.attrB1'] = [
      'asc' => ['TableB.attrB1' => SORT_ASC],
      'desc' => ['TableB.attrB1' => SORT_DESC],
 ];

$query->joinWith(['TableB']); 

Then the actual search of your data (below $this->load()):

$query->andFilterWhere([
    'like',
    'TableB.attrB1',
     $this->getAttribute('TableB.attrB1')
]);

2. Configure GridView

Add to your view:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        /* Other columns */
       'TableB1.attrB1',
        /* Other columns */        
     ]
]);