Yii2 virtual attribute out of an attribute of a SqlDataProvider

434 Views Asked by At

My AddressController:

public function actionIndex() {
    $searchModel = new AddressSearch;
    $dataProvider = $searchModel->search($_GET);

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

My model AddressSearch:

class AddressSearch extends Model {

    public function search($params) {
        $dataProvider = new SqlDataProvider([
            'sql' => '
                SELECT
                    name
                FROM...

View:

GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        'name2',

I have an attribute called name in this dataProvider. I would like to create a new virtual attribute called name2 out of name by preg_replace()-ing a few parts of it. The function itself is working. I have tried a lot of different things, but I still can't make it to fill the attribute name2 with data. name2 is always empty. Can you please point me to the right direction? Many thanks!

UPDATE: based on the brilliant ideas of @Imaginaroom and @rob006 I've done the following:

  • moved getName2() and the attributes I'm filling with SqlDataProvider from base model Address to AddressSearch
  • deleted the empty base model Address because I don't need it anyway. Less is more!
  • in search() I've added:

    foreach ($dataProvider->getModels() as $row) {
        $model = new AddressSearch;
        $model->setAttributes($row, false);
        $models[] = $model;
    }
    $dataProvider->setModels($models);
    

It works! Many thanks guys! It's fantastic that you are there and help!!!

2

There are 2 best solutions below

6
AwesomeGuy On BEST ANSWER

The problem is that you're using SqlDataProvider which returns rows from the table as array instead of model instance. So that's why your getter (virtual-attribute) does not work in GridView - GridView does not work on Address model instance, but on raw array without name2 field.

You should change your SearchModel to use ActiveQuery:

$dataProvider = ActiveDataProvider([
    'query' => Address::find()->select(['name']) //and whatever your query contains besides this
])
...

UPDATE: If you don't want to do it like this, you can add your logic directly in GridView like so:

GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        [
            'header' => 'Name 2',
            'value' => function($model) {
                if (isset($this->_name2)) {
                    return $this->_name2;
                }

                return $this->_name2 = preg_replace([...
            }
        ]
6
rob006 On

If you really need Address model instance and use SqlDataProvider at the same time, you may convert array to model instance manually:

$models = [];
foreach ($dataProvider->getModels() as $row) {
    $model = Address::instantiate($row);
    Address::populateRecord($model, $row);
    $models[] = $model;
}
$dataProvider->setModels($models);

You can place this in AddressSearch::search() or create custom SqlDataProvider and override prepareModels().