12.6. Роут с параметром.

Роут с параметром

Мы можем использовать параметры в URL для роутов. Они работают также как contextual filters во Views. Мы можем например передавать в URL ID различных сущностей, текстовые строки или последовательной ID разделенных запятой или плюсами. В этом уроке мы будем передавать ID ноды и выводить title и body этой ноды в контенте.

Примеры кода можно посмотреть на github:
https://github.com/levmyshkin/drupalbook8

Давайте добавим route в файл нашего модуля drupalbook.routing.yml:

drupalbook.display_node:
  path: '/display-node/{node}'
  defaults:
    _controller: '\Drupal\drupalbook\Controller\DisplayNode::content'
    _title_callback: '\Drupal\drupalbook\Controller\DisplayNode::getTitle'
  requirements:
    _custom_access: '\Drupal\drupalbook\Controller\DisplayNode::access'
  options:
    parameters:
      node:
        type: entity:node

Здесь в path мы передаем во втором аргументе {node}, в урле мы будем писать обычный ID: /display-node/101, но в наш контроллер будет приходить уже готовый объект ноды. Для этого мы указываем в options параметры, что в эти параметры должно передаваться и что будет на выходе

 options:
    parameters:
      node: #имя аргумента, то что находится между {}, это может быть  node1, node2, если мы будем передавать два разных аргумента.
        type: entity:node # то что будет на выходе, мы можем использовать объект этой сущности внутри контроллера.

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

Тепреь когда мы разобрались с описанием роута, давайте перейдем к написанию класса для этого роута.

modules/custom/drupalbook/src/Controller/DisplayNode.php:

<?php

/**
 * @file
 * Contains \Drupal\drupalbook\Controller\DisplayNode.
 */

namespace Drupal\drupalbook\Controller;

use Drupal\Core\Access\AccessResult;
use Drupal\node\NodeInterface;

/**
 * Provides route responses for the DrupalBook module.
 */
class DisplayNode {

  /**
   * Returns a simple page.
   *
   * @return array
   *   A simple renderable array.
   */
  public function content(NodeInterface $node) {
    $element = array(
      '#markup' => $node->body->value,
    );
    return $element;
  }

  /**
   * Checks access for this controller.
   */
  public function access(NodeInterface $node) {
    $user = \Drupal::currentUser();
    if ($node->getType() == 'article' && !in_array('authenticated', $user->getRoles())) {
      return AccessResult::forbidden();
    }
    return AccessResult::allowed();
  }

  /**
   * Returns a page title.
   */
  public function getTitle(NodeInterface $node) {
    return $node->getTitle();
  }

}

Я создал отдельный класс для нешего роута, чтобы не мешать все в один класс контроллер. Давайте разберемся, что значит каждая из этих строк.

namespace Drupal\drupalbook\Controller;

Указываем где лежит наш файл контроллера.

use Drupal\Core\Access\AccessResult;
use Drupal\node\NodeInterface;

Подключаем файлы классов AccessResult - мы будем использовать его для вывода ошибки 403 и NodeInterface - мы будем использовать его для получения объекта ноды в параметр методов нашего контроллера.

  public function content(NodeInterface $node) {
    $element = array(
      '#markup' => $node->body->value,
    );
    return $element;
  }

Здесь обратите внимание на параметр, мы получаем здесь объект ноды, друпал сам преобразуем ID из URL и передает нам объект, так чтобы мы лишний раз не подгружали объект нода, а получали готовый там где нам нужно. $node->body->value так мы получаем значение поля из ноды, но о работе с объектами мы разберемся подробно в следующим уроке, когда будем разбирать Entity API. И в конце мы возвращаем массив с #markup для вывода на страницу текста наше ноды.

  public function access(NodeInterface $node) {
    $user = \Drupal::currentUser();
    if ($node->getType() == 'article' && !in_array('authenticated', $user->getRoles())) {
      return AccessResult::forbidden();
    }
    return AccessResult::allowed();
  }

Сначала мы используем метод друпала currentUser(), чтобы получить объект текущего пользователя, дальше мы будем использовать этот объект, чтобы получить роли текущего пользователя. В проверке if, проверяем контент тип нашей ноды и роли пользователя, для анонимных пользователей будет выдаваться 403 ошибка, если все хорошо, то мы идем дальше и возвращаем allowed(), то есть просто одобряем нашу проверку. Давайте откроем класс AccessResult и посмотрим какие еще методы есть у этого класса. Для этого в PhpStorm нужно дважды нажать shift и ввести названия класса:

Drupal 8 AccessResult

Здесь можно найти следующие методы для проверки прав доступа:

neutral
allowed
forbidden
allowedIf
forbiddenIf
allowedIfHasPermission
allowedIfHasPermissions
isAllowed
isForbidden
isNeutral
orIf
andIf

Вы можете поэксперментировать например с allowedIfHasPermission() и по задавать различные права доступа для разных ролей для вашего роута. Например, создать новый permission в своем модуле и использовать его в контроллере. Хотя проще, конечно, использовать permission в yml файле роута. Но через AccessResult класс можно гибко описать логику для доступу к контенту, например "доступ к контенту разрешен авторизированным пользователям с 12 до 16 часов, а пользователям с ролью premium аккаунт круглосуточно". Поэксперментируйте и реализуйте такое разграничение прав доступа к нодам статей.

Как видите друпал предоставляет сразу несколько путей для реализации доступа к контенту, что удобно, потому что 99% процентов кейсов используют один permission и этого вполне хватает для разграничений прав доступа.

  public function getTitle(NodeInterface $node) {
    return $node->getTitle();
  }

Здесь мы просто возвращаем название ноды как title, но можно и расширить возможности нашего callback'а. Например так:

public function getTitle(NodeInterface $node) {
    $user = \Drupal::currentUser();
    if ($node->getType() == 'article' && !in_array('authenticated', $user->getRoles())) {
     return 'Premium content:  ' . $node->getTitle();
   }
   else {
     return 'Free access content:  ' . $node->getTitle();
   }

}

Или вывести дату публикации в title:

public function getTitle(NodeInterface $node) {
  return $node->getTitle() . ' from ' . date('d.m.Y', $node->getCreatedTime());
}

Как видите Drupal предоставляет возможность гибко настроить ваш роут и контроллер. Так что когда у вашего клиента возникнут самые безумные идеи по выводу материалов на сайт, вы всегда сможете это сделать с помощью Drupal API и немного PHP кода. 

Примеры кода можно посмотреть на github:
https://github.com/levmyshkin/drupalbook8