25 мая 2020 г.

В Laravel не обязательно передавать название действия, которое вы авторизуете

Вы знаете, что можете авторизовать любое действие в контроллере через метод authorize, в котором вы должны передать действие как первый аргумент и модель как второй.

Но если вы не передадите действие в этот метод, Laravel предположит название этого действия из навзвания метода контроллера. Таким образом в этом примере:

public function update(Post $post)
{          
    $this->authorize('update', $post);
}

вы можете опустить название метода Policy, т.к. оно совпадает с названием метода контроллера, из которого вы вызываете authorize:

public function update(Post $post)
{          
    $this->authorize($post);
}  

Круто, правда? Но...


Как это может работать?

Становится намного яснее если взглянуть на трейт AuthorizesRequests, который подключается в App\Http\Controllers\Controller, что означает в любом контроллере, который наследует этот класс. Так выглядит метод authorize:

public function authorize($ability, $arguments = [])
{
    [$ability, $arguments] = $this->parseAbilityAndArguments($ability, $arguments);

    return app(Gate::class)->authorize($ability, $arguments);
}

Переменные $ability и $arguments идут из метода parseAbilityAndArguments:

protected function parseAbilityAndArguments($ability, $arguments)
{
    if (is_string($ability) && strpos($ability, '\\') === false) {
        return [$ability, $arguments];
    }

    $method = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function'];

    return [$this->normalizeGuessedAbilityName($method), $ability];
}

Переменные $ability и $arguments возвращаются сразу если мы непосредственно передаем действие, которое хотим авторизовать: $ability является строчкой и не содержит \\, что говорит о том, что это не путь к классу. Здесь начинается самое интересное:

Вы можете видеть использование функции debug_backtrace, где вся магия и происходит. Это обычная функция PHP, которая показывает что на данный момент было вызвано в виде массива. Второй аргумент говорит сколько таких вызовов нам нужно знать. Нам нужно знать всего 3. Первый - это текущий метод, второй - метод authorize и третий сам метод контроллера, из которого мы вызываем authorize. Все что нам нужно, это взять название последнего. Вот и все! Таким образом Laravel и предполагает название действия, которое вы хотите авторизовать.