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:
Если в модели первичный ключ составной ($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 и откуда что берётся.