Passing more than one Security attribute to AccessDecisionManager::decide() is not supported – Symfony 5

Что делать, если после обновления Symfony до версии 5.0.* вдруг стала появляться ошибка:

Passing more than one Security attribute to "Symfony\Component\Security\Core\Authorization\AccessDecisionManager::decide()" is not supported.


Испокон веков в Symfony можно было в config/packages/security.yaml указать сразу несколько ролей для того, чтобы ограничить доступ к конкретному разделу:

security:
    access_control:
        - { path: ^/, roles: [IS_AUTHENTICATED_ANONYMOUSLY, ROLE_USER] }
        - { path: ^/some-secret-place, roles: [ROLE_MANAGER, ROLE_OPERATOR] }

Это позволяло дать доступ к одному и тому же разделу пользователям с разными ролями.

Но вот настало время, когда разработчики подумали: «Непонятно, что означает такой вид записи: "ROLE_MANAGER и ROLE_OPERATOR" или всё же "ROLE_MANAGER или ROLE_OPERATOR" 🤔» (пруф).

This PR deprecates passing more than one attribute to isGranted() and decide() to remove this confusing bit in Security usage.

Так вот начиная с версии Symfony 5.0.* (я поймал конкретно в версии 5.0.7, например) следует либо вовсе отказаться от такого вида записей, либо же использовать следующий синтаксис:

security:
    access_control:
        - { path: ^/, allow_if: "is_granted('IS_AUTHENTICATED_ANONYMOUSLY') or is_granted('ROLE_USER')"}
        - { path: ^/some-secret-place, allow_if: "is_granted('ROLE_MANAGER') or is_granted('ROLE_OPERATOR')"}

Чтобы такой вид записи (c allow_if) точно работал, убедитесь, что у вас установлен этот компонент:

composer require symfony/expression-language

Сразу после установки этого компонента и корректировки записи в security.yaml всё должно заработать.

Или можно сделать то же самое с помощью иерархии ролей, если вы действительно хочите ограничить доступ именно таким образом:

role_hierarchy:
        ROLE_EXAMPLE: [ROLE_MANAGER, ROLE_OPERATOR]
security:
    access_control:
        - { path: ^/some-secret-place, roles: ROLE_EXAMPLE}

Важно отметить, что если где-то в проекте вы используете в аннотациях записи вида:

/**
 * Some useful function
 *
 * @IsGranted({"ROLE_MANAGER", "ROLE_OPERATOR"})
 *
 * @return JsonResponse
 */
public function doSomethingUseful(): JsonResponse
{
    if ($this->isGranted(['ROLE_MANAGER', 'ROLE_OPERATOR'])) {
        // do something
    }
}

То тут также может возникнуть подобная ситуация. В таком случае следует использовать запись такого вида:

if ($this->isGranted("has_role('ROLE_MANAGER') or has_role('ROLE_OPERATOR')")) {
    // ...
}

if ($this->isGranted('ROLE_MANAGER') || $this->isGranted('ROLE_OPERATOR')) {
    // ...
}

Или вместо @IsGranted использовать аннотацию вида:

@Security("is_granted('ROLE_MANAGER') or is_granted('ROLE_OPERATOR')")