官术网_书友最值得收藏!

Tokens

The last thing we will cover in this chapter is the Token API in Drupal 9. We will cover a few bits of theory and, as usual, demonstrate them via examples on our existing Hello World module code. We will do this in the context of the emails we are sending out for error logs.

It would be nice if we could include some personalized information in the mail text without having to hardcode it in the module code or configuration. For example, in our case, we might want to include the username of the current user that is triggering the error log that is being emailed.

Let's first understand how the Token API works, before going into our Hello World module.

The Token API

Tokens in Drupal are a standard formatted placeholder, which can be found inside a string and replaced by a real value extracted from a related object. The format tokens use is type:token, where type is the machine-readable name of a token type (a group of related tokens), and token is the machine-readable name of a token within this group.

The power of the Token API in Drupal is not only given by its flexibility but also by the fact that it is already a popular API. It is flexible because you can define groups that contain related tokens, linked by the data object that contains their value (for example, a node object or user object). It is popular because in previous versions of Drupal, it was the contributed module many others were dependent on to define their own tokens, and it is now available in Drupal core with many tokens already defined out of the box. So, you'll find many existing tokens that you can use in your code, and if not, you can define your own.

There are three main components of this API—at least from the point of view of a Drupal 9 module developer. These components are two hooks—hook_token_info() and hook_tokens()—and the Token service, which is used to perform the replacement.

The first hook is used to define one or more token types and tokens. It essentially registers them with the system. The second is fired at the moment a token is found inside a string (a replacement is attempted by the service) and is used to do the replacement of the tokens based on the data that is passed to it from the service. For example, the User module defines two token types and a number of tokens inside user_token_info(). With user_tokens(), it checks whether the token is one of its own and tries to replace it with the contextual data (either a user object or the currently logged-in account object). To read the documentation related to each of these in detail and to see an extended example, you can find them either on the Drupal.org API page or inside the token.api.php file. There, you will also find alter hooks that correspond to these two and can be used to alter either the defined token information or logic to replace these tokens written by other modules or Drupal core.

The Token service is what we can use as module developers if we have to replace tokens found inside a string. We will see how this is used in the next section.

Using tokens

To quickly demonstrate how we can use tokens, let's include in our hello_world_log mails some information about the current user at the time the email is being sent out. This will naturally coincide with the user that is signed in at the time the error is being logged.

For this, we will need to alter our hook_mail() implementation. In there, we will use the current_user service, add another string to our mail body, and, of course, replace a token:

/**

* Implements hook_mail().

*/

function hello_world_mail($key, &$message, $params) {

  switch ($key) {

    case 'hello_world_log':

      $message['from'] = \Drupal::config('system.site')-      >get('mail');

      $message['subject'] = t('There is an error on your       website');

      $message['body'][] = $params['message'];

      $user_message = 'The user that was logged in: [current-      user:name].';

      $message['body'][] = \Drupal::token()->replace($user_      message, ['current-user' => \Drupal::currentUser()]);

      break;

  }

}

As you can see, we are adding a new "paragraph" to our email. This is a simple string that informs us about the user that was logged in. However, in doing so, we use the Token service (statically) to replace that piece of string with the token value. The replace() method of the service takes a string and optionally an array of data objects keyed by the type (group) of the tokens they should be used for.

The choice of token and type in this case is important. The User module defines the user and current-user types. The difference between the two, if you check inside user_tokens(), is that the latter simply delegates to the former after it loads a full user entity. We could, alternatively, have done that ourselves and then passed the user type, but why should we? If somebody has done that for us already, we should not have to do it again. And what we pass to the current-user token type as a data object to be used in the replacement process is the AccountProxy (current user session).

So, that's it. Now, the email message will get an extra line that contains the dynamically generated username of the currently logged-in user at the time the error happened. Under the hood, the Token service scans the string, extracts the token, and calls all hook_tokens() implementations. The User module is the one that can return the replacement for this token based on the user object it receives.

Defining new tokens

We just saw how we can programmatically use existing tokens inside our strings and get them replaced with minimal effort. All we need is the token service and the data object that can be used to replace the token. Keep in mind that there are tokens that don't even require any data objects due to their global nature. The hook_tokens() implementation will take care of that—let's see how.

In the previous chapter, we created functionalities for a dynamic Hello World message: either calculated on the fly or loaded from a configuration object. How about we expose that message as a token? That would make its usage more flexible because our string becomes exposed to the entire token system.

As mentioned earlier, we will start with the hook_token_info() implementation:

/**

* Implements hook_token_info().

*/

function hello_world_token_info() {

  $type = [

    'name' => t('Hello World'),

    'description' => t('Tokens related to the Hello World      module.'),

  ];

  $tokens['salutation'] = [

    'name' => t('Salutation'),

    'description' => t('The Hello World salutation value.'),

  ];

  return [

    'types' => ['hello_world' => $type],

    'tokens' => ['hello_world' => $tokens],

  ];

}

In here, we will need to define two things—the types and the tokens. In our case, we are defining one of each. The type is hello_world and comes with a human-readable name and description in case it needs to be rendered somewhere in the UI. The token is salutation and belongs to the hello_world type. It also gets a name and description. At the end, we return an array that contains both.

What follows is the hook_tokens() implementation in which we handle the replacement of our token:

/**

* Implements hook_tokens().

*/

function hello_world_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {

  $replacements = [];

  if ($type == 'hello_world') {

    foreach ($tokens as $name => $original) {

      switch ($name) {

        case 'salutation':

          $replacements[$original] = \Drupal::service('hello_          world.salutation')->getSalutation();

          $config = \Drupal::config('hello_world.custom_          salutation');

          $bubbleable_metadata->addCacheableDependency($conf           ig);

          break;

      }

    }

  }

  return $replacements;

}

There is a bit more going on here, but I'll explain everything. This hook gets fired whenever a replacement of tokens is attempted on a string. And it's fired for each type that has been found inside that string, $type being the first argument. Inside $tokens, we get an array of tokens located in that string, which belong to $type. The $data array contains the objects needed to replace the tokens (and passed to the replace() method), keyed by the type. This array can be empty (as it will be in our case).

Inside the function, we loop through each token of this group and try to replace it. We only know of one, and we use our HelloWorldSalutation service to determine the replacement string.

Finally, the function needs to return an array of all replacements found (which can be multiple if multiple tokens of the same type are found inside a string).

The bubbleable_metadata parameter is a special cache metadata object that describes this token in the cache system. It is needed because tokens get cached, so if any dependent object changes, the cache needs to be invalidated for this token as well. By default, all objects inside the $data array are read and included in this object. However, in our case, it is empty, yet we still depend on a configuration object that can change—the one that stores the overridden salutation message. So, we will need to add a dependency on that configuration object even if the actual value for the salutation we compute uses the same HelloWorldSalutation service we used before. So, we have a simple example here, but with a complex twist. We will talk more about caching later in the book.

That's all there is to defining our token. It can now also be used inside strings and replaced using the Token service. Something like this:

$final_string = \Drupal::token()->replace('The salutation text is: [hello_world:salutation]');

As you can see, we pass no other parameters. If our token was dependent on an entity object, for example, we would have passed it in the second parameter array and made use of it inside hook_tokens() to compute the replacement.

Tokens recap

The token system is an important part of Drupal because it allows us to easily transform raw data into useful values using placeholder strings. It is a widely used and flexible system that many contributed modules build upon. The great thing about tokens is the UI component. There are modules that will allow users to define strings in the UI but make it possible to fill them up with various tokens that it will replace. Also, this is something you can do as a module developer.

主站蜘蛛池模板: 鄂州市| 岱山县| 祁阳县| 怀宁县| 炎陵县| 万州区| 蓝山县| 怀化市| 漳平市| 乌兰察布市| 鹿邑县| 凤阳县| 左云县| 紫阳县| 利川市| 包头市| SHOW| 浦江县| 新巴尔虎右旗| 利辛县| 潼关县| 宜春市| 体育| 名山县| 休宁县| 凤翔县| 界首市| 汕尾市| 昆明市| 读书| 营口市| 上高县| 体育| 呼伦贝尔市| 万州区| 平果县| 商南县| 囊谦县| 报价| 高清| 渭南市|