Problem/Motivation
Is not possible to use two tokens of same type when replacing tokens in a string. For example:
$string = 'Node [node1:title] points to [node2:title]';
$string = token_replace($string, array('node1' => $node1, 'node2' => $node2));
Proposed resolution
All modules that implements hook_tokens() expect their tokens (declared in hook_token_info()) to have a certain type name ('node', 'user', etc) so we can't change this behavior because all modules implementing this hook should be updated.
Instead, the approach is to add an 'alias' to the type, and internally strip this alias and call token_generate with the clean type name.
For example, the first example using this approach:
$string = 'Node [node{pointer}:title] points to [node{pointed}:title]';
$string = token_replace($string, array('node{pointer}' => $node1, 'node{pointed}' => $node2));
The modified code is token_replace() function. After getting all standard tokens a new loop is added to process the token alias. For each token type a new token_generate call is made, passing the stripped token (without the alias name chunk) and the filtered data array with the object related to this alias.
For the previous example two additional token_generate() calls are made:
- first call: $type is 'node', $tokens is first token alias tokens stripped ('title' => '[node:title]'), and $data is the filtered data ('node' => $node1).
- second call: $type is 'node', $tokens is first token alias tokens stripped ('title' => '[node:title]'), and $data is the filtered data ('node' => $node1).
Remaining tasks
Add tests to test functionality in deep to allow start a discussion on this approach. Having test coverage would work very well as examples and basis for such a discussion.
Add documentation for this feature.
User interface changes
No UI changes.
API changes
Added new token_alias_scan() function similar to token_scan but scans for token alias.
Additional notes
| Comment | File | Size | Author |
|---|---|---|---|
| #95 | 192068-95.patch | 9.2 KB | unstatu |
Issue fork drupal-1920688
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #1
dave reidLet's test this out.
Comment #3
dave reidTry that again...
Comment #4
dave reidComment #6
dave reidThis one should pass.
Comment #8
dave reid#6: 1920688-token-replace-multiple-instances-same-type.patch queued for re-testing.
Comment #10
dave reidI'm not able to replicate these failures locally. :/
Comment #11
dave reidLooks like #350407: Anonymous should not appear in the users table at all blocks token replacement for anonymous users.
Comment #12
dave reidDisregard #11. Looks like it was fixed in #1634280: drupal_anonymous_user() should return a User entity.
Comment #13
xanoWhen #1968214: 'unify' hook_token_info() gets in, we can require data that is passed on to token_replace() to have a "type" parameter which refers to any of the types exposed in hook_token_info(). This way, data names and types are separated, and with a few modifications we can easily pass on an arbitrary number of data objects of the same type to token_replace().
Comment #14
dave reidNo, that would not be compatible with the current approach here.
Comment #15
xanoMy apologies for posting that. It was a quick idea I had right before I had to leave and I posted it without checking your patches, which is what I did just now.
I understand the approach you're taking here. I haven't tested the patched but I believe it should work well. I have two questions:
1) What if the type of the data cannot easily be obtained, such as when the data is not an object?
2) Does this mean that we can remove the data type from needs-data in a follow-up, as we are using a different method of determining datas' types now?
Comment #16
dave reidThis change would start to transition the token system to requiring standardized objects that we can rely on, like TypedData. In the case that the data is not an object, we should ensure there is some kind of wrapper object for the data. For example UNIX timestamps in this patch are converted to DateTime objects, which is easier to then do multiple replacement for since we can check why type of item it is.
Likely yes, we would change the needs-data to something more useful, like 'global' => TRUE/FALSE to indicate if the token type requires data to be provided or not.
Comment #17
xanoI wonder if we should not use $entity->entityType rather than instanceof to determine an entity's type.
Comment #18
rudiedirkx commentedIs this what Drupal 6 did with
token_replace_multiple()? It's not possible in D7?Comment #19
berdirInteresting re-roll ;)
Comment #21
berdirComment #24
tunicAdded a new patch with the approach described in #2367105: Support token alias (multiple instances of the same token type) for multiple token instances.
In short this patch adds token alias support. An example:
Internally the code extracts the different alias and call Token::generate using a certain context that includes:
The patch is not complete because it doesn't handle the hook_tokens_alter(), but the appoach can be easily extended to cover this case. It can be optimized also. But first let's see if this is a good approach or there are issues that I'm not aware of.
Comment #27
marvil07 commentedThis is a good idea, but after a while (a couple of years!), the last patch is not applying anymore, so marking as NW.
Comment #28
tunicRerolled!
Not including interfiff because file have been moved.
The patch is just porting the previouspatch code to the new Token.php file but adding the new BubbleableMetadata parameter when calling replace.
Again, this is just a proof of concept, there's much room for improvement.
BTW, now that token system is a service, I suppose a module could implement this idea and provide a modified token service, right? It'd have to extend the Token class.
Comment #29
tunicIt seems is not needed to modify the regexp, in fact TokenScanTest fails if yes.
With original regexps multiple token support works ok.
Comment #30
tunicAn example of how to use it:
Comment #33
rudiedirkx commentedThe
type{name}syntax seems very clunky. Why is it even necessary? Every entity contains its metadata, right? So Token would know$data['client']is a User, and$data['node2']is a Node. Why not parse all tokens and look for their types in$data?Comment #34
berdirToken supports more than entities but also things like dates and any other data structure.
The patch contains a ksm(), that's why everything explodes.
Comment #35
tunicOk, big fail with last patch.
Let's rework the regexps. Standard scan should not match the aliased tokens, and alias scan should not match empty type tokens (for example: [:empty type token])
Comment #36
manuel garcia commentedComment #37
facine commentedHi, I get this error if add an explication of how to use this in a field description.
I attach a new patch with a solution that works for me.
Comment #40
facine commentedRerolled for 8.3.x-dev
Comment #44
ckaotikCould we get away with just aliasing in the token string, and passing the data with arbitrary names?
That way, the code offering multiple same-type parameters can just "dump" them into the token service. Example scenario: Passing both the current and specific node revision straight from
\Drupal::routeMatch()->getParameters()->all()on/node/{node}/revisions/{node_revision}/view(Route: entity.node.revision).The user would still need to know that
[node:*]and[node_revision:*]tokens are available, and what data type they are (so the correct token type is used). But code wouldn't need to change if additional data is already passed in.I've also made some updates for code style.
Comment #47
duneblI confirm #44 is working nicely and apply cleanly
Comment #48
berdir#44 is a two-year old patch for Drupal 8 without tests, there really is no point in RTBC'ing that as there is no way that will get committed like that. This just generates work for maintainers :)
Comment #49
tunicBerdir, if the functionality is ok for maintainers I'll be happy to work on this. Currently, I don't know if is interesting, mainly because it got little attention. I'll try to address any comments (like adding tests).
However, the first thing to do is confirm the approach is ok and optimize the code. Given that it changes (or extends) how tokens are shaped I think we need some discussion here.
Comment #50
berdirYeah, it's though to bring any token related changes in core.
FWIW, it's a bit of a chicken/egg situation in regards to test and documentation versus discussion. IMHO, having test coverage would work very well as examples and basis for such a discussion. If we end up changing the syntax then it will take a few minutes to update examples in a test string. And it will help to verify that the regular expressions are working correctly.
So if you do want to bring this forward, then I'd strongly suggest to work on tests.
Comment #51
tunicOk, thank you very much for your comments Berdir, I'll focus on tests.
Comment #52
tunicComment #53
tunicChanged implementation to a simpler one (approach is the same but I don't why the previous implementation was so complex).
Tests added!
Comment #54
tunicFix coding standards.
Comment #55
tunicA missing comma that triggered a coding standards error.
Comment #56
tunicFixing wrong function name in @cover clause.
Comment #58
dunebl@tunic There is a bug in #56... it can't work without the 2 following changes
should be
Comment #59
tunic#58, the original token is stored in the $text_token_aliases and it is part of the return value of the alias_scan function.
I guess you were confused by the alias_scan documentation because it was wrong. I've updated the patch fixing the documentation.
Comment #60
dunebl#59: No, when using the code without correction I got a warning at this line:
$data_for_current_token[$unaliased_token_name] = $data[$aliased_token_name];And the aliased replacement didn't occurs.
After the 2 proposed corrections in #58, the code runs fine and the (aliased) replacements occurs.
Maybe you can use a debugger and you will see that the index doesn't exists in this part of the mentioned lined
$data[$aliased_token_name];Comment #61
tunic#60, can you provide the test case you are using?
Comment #62
duneblSomething like this:
You can paste this code in the devel "Execute PHP code" page after having replaced the 2 IDs of the nodes by your existing ids.
Comment #63
tunicThanks, DuneBL, indeed it was wrong. In fact, everything was wrong, even the tests.
I've refactorized the code to fix it, including proper tests. Thi include a change on the format of alias_scan to allow a small simplifcation on the replace function.
DuneBL, keep in mind that when you use an aliased token you need to provide the type and the alias in the data array. So, instead
'pc'=>Drupal\node\Entity\Node::load(2)it should be'node{pc}'=>Drupal\node\Entity\Node::load(2).Your test case would be:
With the last patch, this test case runs ok!
Comment #64
duneblThank you!
Looks like it is working well.
Comment #65
berdirThis can't be RTBC when tests are failing or more specific, not running due to coding standards.
Comment #66
tunicSorry Berdir, I totally forgot about coding standards, shame on me.
Comment #68
artem_sylchukQuick re-roll attempt as it no longer applies to 9.4
Comment #69
Munavijayalakshmi commentedRe-rolled patch applied successfully and cleanly.
Comment #70
Munavijayalakshmi commentedComment #71
artem_sylchukTests didn't pass.
Comment #72
artem_sylchukOne more attempt
Comment #76
smustgrave commentedOpen thread in the MR.
Also why in the MR did you add phpstan-baseline update? Don't see that in the patch before.
Comment #77
bhanu951 commented@smustgrave
Updated baseline due to this PHPStan error https://www.drupal.org/pift-ci-job/2558692
Comment #78
smustgrave commentedThat seems like a valid warning that may need to be addressed vs ignored.
Would have to compare to previous patch though and see what’s different in the MR.
Comment #79
berdirAgreed, new problems should not be ignored but fixed.
Comment #81
ro-no-lo commentedHi, for everyone coming to this issue after 10+ years, I have created a small Token extension module, were aliased tokens can be used in a programmatically way. It is a different approach, in syntax, but if you are desperate you might give it a try.
https://www.drupal.org/project/token_aliases
(You may have to use it from the git directly.)
Usage:
Comment #82
grayle commentedThere's a bug with the latest MR. The variables should be reset a loop deeper.
I do not have time to make a patch or MR, but the main block should look like this:
Otherwise you get very weird issues. Easy way to test:
The thing is that you can't use the same fields for all the aliases, gotta have some diffs. Then it goes haywire.
Comment #87
unstatu commentedI have created a new MR (https://git.drupalcode.org/project/drupal/-/merge_requests/8706) with the following changes:
- Changed the test to return different tokens depending on the passed entity so we don't rely on the onConsecutiveCalls() approach. IMO the new approach is more consistent.
- Reseted the $alias_tokens_mapping array on the inner loop as suggested in #82
Comment #88
unstatu commentedAdding a patch with the latest status of https://git.drupalcode.org/project/drupal/-/merge_requests/8706
Comment #89
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #90
smustgrave commentedHiding patches from bot.
MR should be updated to 11.x also as current development branch.
Comment #91
unstatu commentedThanks for the feedback, smustgrave. On it.
Comment #92
unstatu commentedI have changed the base branch from the issue. I'm going to change the base dev branch from the MR as well
Comment #94
unstatu commentedCreated a new MR based on 11.x and fixed the bot reported problems: https://git.drupalcode.org/project/drupal/-/merge_requests/8707
Comment #95
unstatu commentedThe pipeline finally passed. This patch matches the latest state of the MR https://git.drupalcode.org/project/drupal/-/merge_requests/8707 with the pipeline in green.
Comment #96
smustgrave commentedRan the test-only feature https://git.drupalcode.org/issue/drupal-1920688/-/jobs/2066476 which shows the coverage.
Think instead of updating the baseline though should fix the error around alias_tokens_mapping, maybe setting a default like
$data_for_current_token = [];
$unaliased_tokens = [];