Drupal 8 开发备忘录

逆流の鱼, 6 一月, 2020

It has been a few months since Drupal 8 was released and sites built with it are starting to crop up. I myself have had the pleasure of working with it, and more Drupal 8 projects are certainly on the horizon. From a developer's perspective, this version is substantially different from the previous one, and we will need to learn a handful of new ways of doing things. To ease the process, I have put together this list of how-tos with tasks that I commonly encounter during development. I hope you will find it helpful.

Generating URLs

In Drupal 7, its path was the definitive way to retrieve a router item. In Drupal 8, on the other hand, routes have internal names, and these names serve as identifiers for routes. Therefore, the route's name should be used when generating URLs. This approach eliminates problems when you need to change the path of a route later on. The following ways are available to generate a URL for a route.

  use Drupal\Core\Url;
  $url = new Url($route_name, $params)​;

  // Or using the static method on the class.
  $url = Url::fromRoute($route_name, $params)​

$route_name is the internal route name specified in routing.yml, and $params is an array of path arguments expected by the route (e.g. the user id in the path of the user profile). Remember, these will return a URL object, not a string. If you need the path as a string, convert the URL to string using the toString() method, or use the url() convenience method on the \Drupal class to get the path directly. The latter one is already marked deprecated in D8.

  $path = $url->toString();

  // Or using the convenience method in the Drupal class.
  $path = \Drupal::url($route_name)

The methods above require a registered route. To generate a non-routed URL, such as that of a CSS file, do the following.

  use Drupal\Core\Url;
  $url = Url::fromUri('internal:/mypath/to/style.css');

Generating HTML links

Gone are the days of simply using a one-letter function to generate a link. Once you have retrieved the URL object as outline above, you can use that to generate an HTML link in one of the following ways.

  $my_link = \Drupal::l($text, $url); // Deprecated
  $my_link = \Drupal::service('link_generator')->generate($text, $url);

The core Link class can also be used to construct a link in the form of either a render array or HTML string. Use the toRenderable() or the toString() method on the returned object to get the link as a render array or string, respectively.

  use Drupal\Core\Link;

  $renderable_link = Link::fromTextAndUrl($text, $url);

  $link_render_array = $renderable_link->toRenderable();
  $link_string = $renderable_link->toString();

If you are in a controller, and inheriting from ControllerBase, you can use the l() method of the class.

  class MyController extends ControllerBase {
    public function myPageCallback() {
      $my_link = $this->l($text, $url);
    }
  }

In any other class, you can add the LinkGeneratorTrait trait to expose the l() method to the class.

  class MyClass {
    use Drupal\Core\Routing\LinkGeneratorTrait;

    public function myMethod() {
      $my_link = $this->l($text, $url);
    }
  }

Accessing services

In Drupal 8, you will access a lot of functionalities through services. Once you know which service you need, you can access them like so.

  $my_service = \Drupal::service('my_service_name');

In a handful of classes, such as controllers inheriting from ControllerBase, the service container will be available via the container() method. Once you have the service container, you can use its get() method to retrieve any service object. 

  class MyController extends ControllerBase {
    public function myMethod() {
      $container = $this->container();
      $my_service = $container->get('my_service_name');
    }
  }

The \Drupal class has static convenience methods for the most often needed services. For example:

  \Drupal::entityManager();  // Returns the entity.manager service.
  \Drupal::database();  // Returns the database service.
  \Drupal::urlGenerator();  // Returns the url_generator service.
  \Drupal::translation();  // Returns the string_translation service.

Injecting services

Whenever you create a class, the preferred method to make services available in that class is by injecting them as opposed to using the \Drupal class. This will allow for better portability and testability.

To inject a service into one of your controller classes, implement the ContainerInjectionInterface interface. This means you will need to write a factory method called create(), which will receive the service container as its argument, and it is supposed to initiate a new instance of the class. While doing so, it can access any service in the service container, and pass it in to the class' constructor. In the following example, the entity.query service is injected into a controller.

  use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  use Symfony\Component\DependencyInjection\ContainerInterface;
  use Drupal\Core\Entity\Query\QueryFactory;

  class MyController extends ControllerBase implements ContainerInjectionInterface {
    protected $entityQuery;

    /**
     * Constructor. Stores injected services.
     */
    public function __construct(QueryFactory $entity_query) {
      $this->entityQuery = $entity_query;
    }

    /**
    * {@inheritdoc}
    */
    public static function create(ContainerInterface $container) {
      return new static(
        $container->get('entity.query')
      );
    }
  }

If you create your own service class, and need to access other services in that class, you can specify which services you will need in the services.yml file. The requested services will be passed to the constructor of your service class when it is instantiated. Following is an example services.yml file that injects a service.

  services:
    mymodule.myservice:
      class: Drupal\my_module\MyService
      arguments: ['@entity.query']

Your class can then store the injected service in its property for later use.

  class MyService {
    protected $entityQuery;

    public function __construct(QueryFactory $entity_query) {
      $this->entityQuery = $entity_query;
    }
  }

Getting the current user

The current user object was always available as a global variable in D7. This is not the case any more in D8. The \Drupal class has a method that returns a proxy object for the current user. Being a proxy, this object does not have all the methods that are available on loaded user entities.

  $account = \Drupal::currentUser();

If you have access to the service container, for instance in a controller, you can use the current_user service instead. 

  class MyController extends ControllerBase {
    public function myPageCallback() {
      $user = $this->container->get('current_user');
    }
  }

Getting the user name and ID

In Drupal 8, very rarely will you access object properties directly. In most cases, there are accessor methods that return the needed properties. Each entity will have an id() method that returns the entity's identifier property: the user id on user object, and the node id on node objects.

The user entity has an accessor to retrieve the user name, getDisplayName(). This will return the formatted user name, or Anonymous for non-logged-in users. To get the raw account name as it is stored in the database, use getAccountName().

  $user = \Drupal::service('current_user');
  $uid = $user->id();
  $formatted_name = $user->getDisplayName();
  $raw_name = $user->getAccountName();

Getting the current path

To get the current path, use the path.current service. Its getPath() method will return the path as a string without the leading slash.

  $path = \Drupal::service('path.current')->getPath();

This path will be the Drupal path of the current page (the equivalent of checking $_GET['q'] in D7), which may or may not be the actually requested URL if there is an alias for it. To get the path requested by the user, do the following. If the page is requested via an alias, this will return the alias.

  $path = \Drupal::request()->getRequestUri();

Getting the current router item

What used to be done with menu_get_item() in D7 can now be achieved with the current_route_match service. The getRouteName() method of that service will return the internal name of the route that is serving the current page.

  $route_name = \Drupal::service('current_route_match')->getRouteName();

Looking up an alias for a path

For this purpose, a service called path.alias_manager can be used. Its getAliasByPath() method will do the alias lookup for us. It will return the original path if there are no aliases. Note that the path passed in to this method will need to have the leading slash.

  $alias = \Drupal::service('path.alias_manager')->getAliasByPath($my_path);

Redirecting

drupal_goto() is gone in D8. Instead, you need to return a Symfony RedirectResponse object in the page callback in order to redirect the user.

  use Symfony\Component\HttpFoundation\RedirectResponse;

  class MyController extends ControllerBase {
    public function myPageCallback() {
      return new RedirectResponse(\Drupal::url($route_name));
    }
  }

If you are in a controller that inherits from ControllerBase, you can use the redirect() method on the class, which lets you set the status code as well.

  use Drupal\Core\Controller\ControllerBase;

  class MyController extends ControllerBase {
    public function myPageCallback() {
      return $this->redirect($route_name, $params, $options, $status_code);
    }
  }

 

原文链接

评论