- Drupal 9 Module Development
- Daniel Sipos Antonio De Marco
- 1929字
- 2021-06-11 18:36:04
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.
- 無(wú)界資本:互聯(lián)網(wǎng)+時(shí)代的資本重生之路
- 電商文案策劃與視覺營(yíng)銷實(shí)戰(zhàn)(第2版·微課版)
- 電商多平臺(tái)客服實(shí)戰(zhàn):淘寶、京東、拼多多
- 電子商務(wù)基礎(chǔ)(第4版)
- 電子商務(wù)綜合實(shí)訓(xùn)
- 跨境電商多平臺(tái)運(yùn)營(yíng)實(shí)戰(zhàn)基礎(chǔ)(第3版)
- 微信公眾號(hào)運(yùn)營(yíng):粉絲及平臺(tái)數(shù)據(jù)分析和營(yíng)銷
- 巧用ChatGPT做跨境電商
- 一本書學(xué)會(huì)做房地產(chǎn)經(jīng)紀(jì)人
- 社群粉絲經(jīng)濟(jì)玩轉(zhuǎn)法則
- 手機(jī)這樣玩才賺錢:移動(dòng)互聯(lián)就是一部手機(jī)打天下
- 物聯(lián)網(wǎng)商業(yè)時(shí)代
- 電子商務(wù)運(yùn)營(yíng)
- 一本書讀懂互聯(lián)網(wǎng)+
- 社會(huì)資本對(duì)在線品牌社區(qū)消費(fèi)者知識(shí)分享的影響研究