Задать url для actionColumn в gridView

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
Sereja3578
Сообщения: 204
Зарегистрирован: 2016.09.21, 11:15
Контактная информация:

Задать url для actionColumn в gridView

Сообщение Sereja3578 »

Всем привет, подскажите, как можно задать свой url для actionColumn в gridView в yii2?

На сколько я понимаю для этого там есть две функции, первая:

Код: Выделить всё

'buttons' => [
   'update' => function ($url, $model, $key) {

      return Html::a('', $url, ['class' => 'glyphicon glyphicon-pencil']);

   }
],

Здесь можно для ссылки создать свой url или использовать url получаемый функцией в качестве параметра - по умолчанию он создается функцией urlCreator и равен /module/controller/action, либо можно в функции urlCreator, создать свой url:

Код: Выделить всё

'urlCreator' => function ($action, $model, $key, $index) {
   ...

   return $updateUrl;

}
Но я хочу создать url типа /test-1/question-1/answer-1, для этого мне не достаточно данных конкретной модели (answer), так как мне нужно знать не только id ответа (answer), но и id вопроса и теста. В само представление я эти параметры передаю, но как их передать в эти функции? В эти функции уже передаются другие параметры, и добавить новые не выходит, да и подменить тоже не получается, если допустим сделать так:

Код: Выделить всё

'buttons' => [
   'update' => function ($myUrl, $model, $key) {

      return Html::a('', $myUrl, ['class' => 'glyphicon glyphicon-pencil']);

   }
],
То все равно в функции $myUrl равен не тому url который я создал а url созданному по умолчанию функцией urlCreator. В общем вопрос в том, как в любую из этих функций передать дополнительные параметры?
Onotole
Сообщения: 1808
Зарегистрирован: 2012.12.24, 12:49

Re: Задать url для actionColumn в gridView

Сообщение Onotole »

Код: Выделить всё

'buttons' => [
   'update' => function ($url, $model, $key) use($myVariable) {
    
      return Html::a('', ['/module/controller/action', 'param' => $myVariable] ['class' => 'glyphicon glyphicon-pencil']);

   }
], 
Аватара пользователя
Sereja3578
Сообщения: 204
Зарегистрирован: 2016.09.21, 11:15
Контактная информация:

Re: Задать url для actionColumn в gridView

Сообщение Sereja3578 »

Onotole писал(а):

Код: Выделить всё

'buttons' => [
   'update' => function ($url, $model, $key) use($myVariable) {
    
      return Html::a('', ['/module/controller/action', 'param' => $myVariable] ['class' => 'glyphicon glyphicon-pencil']);

   }
],
Благодарю тебя мудрый человек) Я блин уже голову сломал с этим. Просто не знал вообще про существование use. Я думал им только подключаются классы на каждой странице. А есть какие-то может статьи на тему этой фитчи почитать на примете?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Задать url для actionColumn в gridView

Сообщение ElisDN »

Sereja3578 писал(а):А есть какие-то может статьи на тему этой фитчи почитать на примете?
http://php.net/manual/ru/
Аватара пользователя
Sereja3578
Сообщения: 204
Зарегистрирован: 2016.09.21, 11:15
Контактная информация:

Re: Задать url для actionColumn в gridView

Сообщение Sereja3578 »

О, Дмитрий, здравствуйте) Рад вас тут видеть. Читаю ваш блог частенько.

На счет мануала это конечно хорошо, но вопрос я этот задал ибо не имею ни малейшего представления, какой вопрос составить чтобы гугл понял что я от него хочу) Искал по запросу "use с анонимными функциями в php" но нашел только одну статью и то не особо объясняющую механику работы данной фишки.

Хотя в целом суть ясна.
Аватара пользователя
girmate
Сообщения: 1534
Зарегистрирован: 2015.10.27, 12:52

Re: Задать url для actionColumn в gridView

Сообщение girmate »

Поддерживаю вопрос. Тоже искал.
Осторожно! Вы общаетесь с новичком ;)
Restlin
Сообщения: 139
Зарегистрирован: 2011.09.09, 18:12

Re: Задать url для actionColumn в gridView

Сообщение Restlin »

Про анонимные функции и передачу параметров из вне в оф. документации же есть:
http://php.net/functions.anonymous
3й пример сверху как раз про use.

Что касается решаемой задачи, то я бы использовал свойство controller у ActionColumn
Вот его описание - http://www.yiiframework.com/doc-2.0/yii ... ler-detail
Таким образом переопределил бы на все кнопки, а не вписывал бы имя контроллера в каждое конкретное действие.
Это сделано, чтобы избежать такого кода:

Код: Выделить всё

'buttons' => [
    'update' => function ($url, $model, $key) use($myVariable) {
        return Html::a('', ['/module/controller/update', 'param' => $myVariable] ['class' => 'glyphicon glyphicon-pencil']);
    },
     'delete' => function ($url, $model, $key) use($myVariable) {
          return Html::a('', ['/module/controller/delete', 'param' => $myVariable] ['class' => 'glyphicon glyphicon-cancel']);
    }
], 
а сделал бы так:

Код: Выделить всё

'controller' => '/modeule/controller', //свойство с именем используемого контроллера
'buttons' => [
   'update' => function ($url, $model, $key) use($myVariable) {
      return Html::a('', ['update', 'param' => $myVariable] ['class' => 'glyphicon glyphicon-pencil']);
   },
   'delete' => function ($url, $model, $key) use($myVariable) {
      return Html::a('', ['delete', 'param' => $myVariable] ['class' => 'glyphicon glyphicon-cancel']);
   }
]
 
Не удержался и допишу. На мой взгляд адресация к подчиненным данным (ответам) должна идти только по id, а уже id вопроса и должна быть записана в таблице с ответами. По крайней мере в нашей системе тестирования сделано так.
Аватара пользователя
girmate
Сообщения: 1534
Зарегистрирован: 2015.10.27, 12:52

Re: Задать url для actionColumn в gridView

Сообщение girmate »

Извиняюсь за оффтоп. Немного почитал про use. Понял, что это называется замыканием в анонимной функции. Используется когда необходимо получить доступ к переменной глобальной видимости внутри функции. Но все равно непонятно, почему нельзя использовать:

Код: Выделить всё

....
'update' => function ($url, $model, $key) use($myVariable) {
....
 
в Вашем примере, например, вот так:

Код: Выделить всё

....
'update' => function ($url, $model, $key, $myVariable) {
....
 
Я постараюсь сам ответить на свой вопрос. Так нельзя записать, потому что внутри этой функции никак нельзя будет использовать такую переменную, если ее передавать аргументом в анонимную функцию [почему-то]. Почему? Чуть-чуть помогите мне это понять. Я думаю что потому что эта функция не воспринимает больше никаких аргументов по умолчанию. То есть где-то это определено.

И сразу второй вопрос: что именно попадает в $url и $key. Ну что там может быть, например. С доков по английски не понимаю как понятно перевести на простой язык. То есть что понимается под ключом провайдера данных, и что за url туда приходит. Я просто вижу, что это много где используется и хочу в полной мере научиться пользоваться этим инструментом. Заранее спасибо.
Последний раз редактировалось girmate 2016.10.03, 10:52, всего редактировалось 1 раз.
Осторожно! Вы общаетесь с новичком ;)
Restlin
Сообщения: 139
Зарегистрирован: 2011.09.09, 18:12

Re: Задать url для actionColumn в gridView

Сообщение Restlin »

Постараюсь ответить:
1) Когда мы описываем анонимную функцию в описании колонок для GridView, мы только описываем саму функцию и ее входные параметры, но на сам процесс вызова мы повлиять не можем. Процесс вызова скрыт от нас и помещен в стандартные классы фреймворка, а именно:
вот исходники ActionColumn https://github.com/yiisoft/yii2/blob/ma ... Column.php, вызов происходит на 216 строке:

Код: Выделить всё

return call_user_func($this->buttons[$name], $url, $model, $key);
.
Передается всего 3 параметра, таким образом, если мы не перепишем вызов (уже значительно более трудоемкая задача), то стандартный вызов будет всегда с 3мя описанными в API атрибутами.

2) Теперь о стандартных переданных значениях:
$model - думаю все понятно;
$key - это первичный ключ модели (уникальное поле, id);
$url - url, вычисленная в тех же исходниках:
https://github.com/yiisoft/yii2/blob/ma ... Column.php на 215 строке
путем вызова функции createUrl(ее поведение в принципе тоже можно переписать).
По-умолчанию получится url с такими параметрами [текущий_контроллер/имя_кнопки,'id'=>$key].
Onotole
Сообщения: 1808
Зарегистрирован: 2012.12.24, 12:49

Re: Задать url для actionColumn в gridView

Сообщение Onotole »

По тому, что вызывается call_user_func
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Задать url для actionColumn в gridView

Сообщение ElisDN »

girmate писал(а):Так нельзя записать, потому что внутри этой функции никак нельзя будет использовать такую переменную, если ее передавать аргументом в анонимную функцию [почему-то]. Почему? Чуть-чуть помогите мне это понять. Я думаю что потому что эта функция не воспринимает больше никаких аргументов по умолчанию. То есть где-то это определено.

И сразу второй вопрос: что именно попадает в $url и $key. Ну что там может быть, например. С доков по английски не понимаю как понятно перевести на простой язык. То есть что понимается под ключом провайдера данных, и что за url туда приходит. Я просто вижу, что это много где используется и хочу в полной мере научиться пользоваться этим инструментом в полной мере. Заранее спасибо.
Открываем класс колонки:

Код: Выделить всё

class ActionColumn extends Column
{
    ...
    public function createUrl($action, $model, $key, $index)
    {
        if (is_callable($this->urlCreator)) {
            return call_user_func($this->urlCreator, $action, $model, $key, $index, $this);
        } else {
            $params = is_array($key) ? $key : ['id' => (string) $key];
            $params[0] = $this->controller ? $this->controller . '/' . $action : $action;
            return Url::toRoute($params);
        }
    }

    protected function renderDataCellContent($model, $key, $index)
    {
        ...
        if ($isVisible && isset($this->buttons[$name])) {
            $url = $this->createUrl($name, $model, $key, $index);
            return call_user_func($this->buttons[$name], $url, $model, $key);
        } else {
            return '';
        }
        ...
    }
}
Внизу видим строки:

Код: Выделить всё

$url = $this->createUrl($name, $model, $key, $index);
return call_user_func($this->buttons[$name], $url, $model, $key);
В первой вызывается метод createurl, который по умолчанию просто генерирует ссылки на действия view, update и delete указанного или текущего контроллера:

Код: Выделить всё

$params = is_array($key) ? $key : ['id' => (string) $key];
$params[0] = $this->controller ? $this->controller . '/' . $action : $action;
return Url::toRoute($params);
Во второй строке видим, как call_user_func вызывает нашу анонимную функцию:

Код: Выделить всё

return call_user_func($this->buttons[$name], $url, $model, $key);
которую мы определили:

Код: Выделить всё

'buttons' => [
    'update' => function ($url, $model, $key) use ($myVariable) { ... },
],
Видим, что call_user_func передаёт в неё аргументы $url, $model и $key. Никакой $myVariable там нет.

Далее переходим в сам грид и ищем, откуда появляется $key:

Код: Выделить всё

class GridView extends BaseListView
{
    ...
    public function renderTableBody()
    {
        $models = array_values($this->dataProvider->getModels());
        $keys = $this->dataProvider->getKeys();
        $rows = [];
        foreach ($models as $index => $model) {
            $key = $keys[$index];
            ...
            $rows[] = $this->renderTableRow($model, $key, $index);
            ...
        }
        ...
    }
}
Значит из провайдера помимо массива моделей $models извлекается и массив неких ключей $keys. Поищем теперь метод getKeys() в провайдере. Он есть в базовом классе:

Код: Выделить всё

abstract class BaseDataProvider extends Component implements DataProviderInterface
{
    private $_keys;
    private $_models;

    abstract protected function prepareModels();

    abstract protected function prepareKeys($models);

    public function prepare($forcePrepare = false)
    {
        if ($forcePrepare || $this->_models === null) {
            $this->_models = $this->prepareModels();
        }
        if ($forcePrepare || $this->_keys === null) {
            $this->_keys = $this->prepareKeys($this->_models);
        }
    }

    public function getModels()
    {
        $this->prepare();
        return $this->_models;
    }

    public function getKeys()
    {
        $this->prepare();
        return $this->_keys;
    }
    ...
}
При вызове getModels() или getKeys() вызывается подготовительный метод prepare, который только при первом вызове заполняет _models моделями и _keys некими ключами из моделей prepareKeys($this->_models). Значит нам интересен метод prepareKeys. Он абстрактный, поэтому искать его нужно в дочерних классах. Поэтому переходим в ActiveDataProvider:

Код: Выделить всё

class ActiveDataProvider extends BaseDataProvider
{
    public $key;
    ...
    protected function prepareKeys($models)
    {
        $keys = [];
        if ($this->key !== null) {
            foreach ($models as $model) {
                if (is_string($this->key)) {
                    $keys[] = $model[$this->key];
                } else {
                    $keys[] = call_user_func($this->key, $model);
                }
            }
            return $keys;
        } elseif ($this->query instanceof ActiveQueryInterface) {
            /* @var $class \yii\db\ActiveRecord */
            $class = $this->query->modelClass;
            $pks = $class::primaryKey();
            if (count($pks) === 1) {
                $pk = $pks[0];
                foreach ($models as $model) {
                    $keys[] = $model[$pk];
                }
            } else {
                foreach ($models as $model) {
                    $kk = [];
                    foreach ($pks as $pk) {
                        $kk[$pk] = $model[$pk];
                    }
                    $keys[] = $kk;
                }
            }
            return $keys;
        } else {
            return array_keys($models);
        }
    }
}
Здесь видим, что по умолчанию с пустым $key сработает elseif и извлечёт из модели значение первичного ключа. То есть метод getKeys() этого провайдера вернёт гриду массив первичных ключей его моделей. Вместо объектного $model->getPrimaryKey() здесь используется ручной вызов как поля массива $model[$pk], чтобы поддерживать и $query->asArray().

Соответственно, в наш верхний код:

Код: Выделить всё

$params = is_array($key) ? $key : ['id' => (string) $key];
$params[0] = $this->controller ? $this->controller . '/' . $action : $action;
return Url::toRoute($params);
внутри $key прилетит первичный ключ текущей модели ($key = 5) и сработает секция ['id' => (string) $key], сформировав $url:

Код: Выделить всё

Url::toRoute(['update', 'id' => '5']);
Если в модели первичный ключ составной ($key = ['post_id' => 5, 'tag_id' => 7]), то сработает is_array($key) и адрес сформируется составной напрямую:

Код: Выделить всё

Url::toRoute(['update', 'post_id' => 5, 'tag_id' => 7]);
Если же мы хотим для текущего грида вручную заменить первичный ключ, то достаточно у провайдера настроить public $key и сработает первый if:

Код: Выделить всё

protected function prepareKeys($models)
{
    if ($this->key !== null) {
        foreach ($models as $model) {
            if (is_string($this->key)) {
                $keys[] = $model[$this->key];
            } else {
                $keys[] = call_user_func($this->key, $model);
            }
        }
        return $keys;
    } ...
}
Там можно указать строкой имя нужного поля или анонимную функцию, в которую вызов call_user_func($this->key, $model) передаст текущую модель. Например, так:

Код: Выделить всё

$dataProvider = new ActiveDataProvider([
    'query' => $query,
    'key' => function ($model) {
        return [
            'id' => $model->id, 
            'category_id' => $model->category_id, 
        ];
    }
]);
Тогда getKeys() вместо $model->id вернёт гриду такие массивы и автоматически у всех ссылок кнопок просмотра, редактирования и удаления поменяются адреса:

Код: Выделить всё

Url::toRoute(['update', 'id' => '5', 'category_id' => 7]);
Соответственно, в $key либо по умолчанию попадает первичный ключ модели, либо переопределённый в провайдере. А $url генерируется как адрес к нужному действию с использованием этого ключа $key.

И, кстати, у ActionColumn есть поле urlCreator, куда можно вписать свою анонимную функцию:

Код: Выделить всё

'urlCreator' => function ($action, $model, $key, $index, $column) { ... }
если хотите генерировать ссылки полностью вручную. В этом случае будет производиться её вызов через тот же call_user_func:

Код: Выделить всё

class ActionColumn extends Column
{
    ...
    public function createUrl($action, $model, $key, $index)
    {
        if (is_callable($this->urlCreator)) {
            return call_user_func($this->urlCreator, $action, $model, $key, $index, $this);
        } else {
            $params = is_array($key) ? $key : ['id' => (string) $key];
            $params[0] = $this->controller ? $this->controller . '/' . $action : $action;
            return Url::toRoute($params);
        }
    }
]
Теперь понятно, что там за $key с $url и откуда что берётся.
Последний раз редактировалось ElisDN 2016.10.03, 13:16, всего редактировалось 2 раза.
Аватара пользователя
girmate
Сообщения: 1534
Зарегистрирован: 2015.10.27, 12:52

Re: Задать url для actionColumn в gridView

Сообщение girmate »

Дмитрий, огромное спасибо. Мне нужно время чтобы это вдумчиво прочитать. И, наверное, за всю историю форума Вы первый раз дали НАСТОЛЬКО развернутый ответ. Спасибо всем.
Осторожно! Вы общаетесь с новичком ;)
Аватара пользователя
girmate
Сообщения: 1534
Зарегистрирован: 2015.10.27, 12:52

Re: Задать url для actionColumn в gridView

Сообщение girmate »

Дмитрий, в общем все понятно. Мне достаточно было бы и более простого ответа типа:
- в $model попадает текущая модель, а точнее будет строка модели, для текущей итерации.
- в $key находится текущий первичный ключ модели
- в $url генерится путь к экшену для конкретной операции в гриде.

Но я, надеюсь, Ваш труд не пропадет даром. Ведь повсюду, читая доки, можно встретить что-то типа:

Код: Выделить всё

renderDataCellContent ( $model, $key, $index )
function ($url, $model, $key) {
 
и так далее. Я просто хотел знать что туда изначально попадает, а не во что преобразуется.
Осторожно! Вы общаетесь с новичком ;)
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Задать url для actionColumn в gridView

Сообщение ElisDN »

girmate писал(а):Почему? Чуть-чуть помогите мне это понять. Я думаю что потому что эта функция не воспринимает больше никаких аргументов по умолчанию. То есть где-то это определено.

И сразу второй вопрос: что именно попадает в $url и $key. Ну что там может быть, например... То есть что понимается под ключом провайдера данных, и что за url туда приходит.
girmate писал(а):Дмитрий, в общем все понятно. Мне достаточно было бы и более простого ответа... Я просто хотел знать что туда изначально попадает...
Более простой ответ - самому залезть в исходники и посмотреть, где что определено и что откуда попадает. Но Вы этого делать не умеете. Вот я и слазил опять вместо Вас и показал, как это сделать.
Аватара пользователя
girmate
Сообщения: 1534
Зарегистрирован: 2015.10.27, 12:52

Re: Задать url для actionColumn в gridView

Сообщение girmate »

ElisDN писал(а): Более простой ответ - самому залезть в исходники и посмотреть, где что определено и что откуда попадает. Но Вы этого делать не умеете. Вот я и слазил опять вместо Вас и показал, как это сделать.
Да, Дмитрий, безусловно, Вы не пожалели потраченного времени и я это сильно ценю. Еще раз спасибо. Я действительно не сильно понимаю исходники - база слабовата.
Осторожно! Вы общаетесь с новичком ;)
skit
Сообщения: 135
Зарегистрирован: 2012.10.08, 12:50
Откуда: Сибирь
Контактная информация:

Re: Задать url для actionColumn в gridView

Сообщение skit »

girmate писал(а): 2016.10.05, 21:26 Мне достаточно было бы и более простого ответа типа
А мне не достаточно более простого ответа.
Когда люди поймут, что планеты не вокруг них крутятся? Как знаете: спасибо разобрался! - как, что?
Спасибо всем, кто понимает, что интернет, это общество пользователей интернета, а не мамкина титька. Захотел, пососал, а там хоть трава не расти.
Не в обиду, просто температура, да и накипело немного.

Дима спасибо! Очень подробно, как раз то что нужно!
Ответить