This is a part of #2430823: Token tree rendering uses a lot of theme functions and can not be render cached. We can improve how our theme functions are being called to a great extent. In this issue, let's deal with tree_table theme hook.

I spent most of yesterday studying this and the token_tree hook. The tree_table theme hook is called from two places - devel controller and them token_tree hook itself. The functionality is mostly same except that it sets different columns (devel controller sends value too). It seemed to me that the business logic to build the token tree is in theme_token_tree and the devel controller. The theme_tree_table just did some preprocessing.

Some of the business logic and processing is common between devel controller and token_tree, except it sets different columns. It seemed to me that a render element is a good way to move this logic to one place. With a render element, we remove the entire tree_table hook and by inheriting the table render element, we have very little else to do. The CSS preprocessing and other parts of the business logic now lives in the render element and the controllers just need to call it. It is not too hard to deal with differing columns and other behaviour as well.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hussainweb created an issue. See original summary.

hussainweb’s picture

Status: Active » Needs review
FileSize
11.35 KB

Patch as per IS.

hussainweb’s picture

This probably needs some cleanup and tweaking, not to mention documentation, but I will appreciate a review first and views on the direction. FYI, I tested this manually as well and the token tree behaviour works fine. I found something that we could add in our tests (asserting restricted elements) but that could be a separate issue.

Status: Needs review » Needs work

The last submitted patch, 2: replace_tree_table_with-2645740-2.patch, failed testing.

hussainweb’s picture

Status: Needs work » Needs review
FileSize
483 bytes
11.41 KB

This should fix it.

hussainweb’s picture

Berdir’s picture

I'd love to find a way to merge theme_token_tree into the new type as well.

It doesn't look like we're that far away from that? If we add the token tree options as another argument to it then we can build that from the new class as well, possibly in another prepended #pre_render callback. Maybe optionally, if #token_tree is empty, so that if someone really wants to do something differently, they can.

We could keep the theme function and deprecate it or even remove it entirely (in a follow-up, optionally) (now that we switched to link almost everywhere, almost nobody uses it outside of token.module anyway).

Then we could see to make the new #type easier to use by having better defaults. For example #columns, we could generate that based on having the value flag set or not. Either show description or values. Or even always show description? devel doesn't even show the name right now, but I don't see why not, that seems useful information?

hussainweb’s picture

I did think of that, but then it seemed that this render element would be doing too much. The only functionality left in token_tree and devel controller are orthogonal and something which is suitable to a controller. That way, the render element is focused on doing only one thing - rendering the token tree and the responsibility of building the render array (and the tree) goes to the controller.

The bulk of building the tree is already handled by the tree builder service. The part which now remains in theme_token_tree can be moved to the tree builder service as a helper method, IMHO (in a follow-up). With that, we can remove the theme hook entirely and replace the current places it is called with the render element and the new helper method.

This helps us keep both things separate - the render element TokenTreeTable to pre-render the tree structure generated by buildTree or the new helper method analogous to theme_token_tree.

This is what the devel controller really does:

    $token_tree = [
      $token_type => [
        'tokens' => $this->treeBuilder->buildTree($token_type, $options),
      ],
    ];

The other calls would do something like this:
Before:

    $token_tree = array(
      '#theme' => 'token_tree',
      '#token_types' => 'all',
      '#click_insert' => FALSE,
      '#show_restricted' => TRUE,
    );

After:

    $token_tree = array(
      '#type' => 'token_tree_table',
      '#token_tree' => $treeBuilder->getTokenTree('all'),
      '#click_insert' => FALSE,
      '#show_restricted' => TRUE,
    );

This way, we keep two distinct pieces of functionality separate and clean.

hussainweb’s picture

As for the rest of your points, I agree. I eventually plan to remove the theme hook (like I mentioned before). I will also work on auto-generating #columns if not set. We can decide on whether to always show description as well. The new #columns property makes it very easy to add/remove columns. It will just be a one line change once we decide.

Berdir’s picture

I see, that's a good point.

However, there is one thing that we need to include: Caching.

Adding some sort of render cache again here is very important. There's a problem with render caching, and that's that it doesn't apply on POST requests currently. token in 7.x explicitly works around that by having its own functions that support that. We had to disable that at some point because it didn't work anymore when things were converted to services.

I'm not sure if we still need to care about that since almost all cases will just display a link. And maybe we can improve render caching in core to optionally support caching on POST requests too. Standard render caching however requires everything that we want to be cached to live on #pre_render or #lazy_builder.

In your example, it wouldn't be possible to render cache $treeBuilder->getTokenTree('all') since that's then always called. We could add additional caching in there of course but then we have two cache get/set requests. That's the main reason I'd like to see that moved to a #pre_render or similar callback.

Also, as a small variation of your suggestion, maybe we should do something similar to what views does: $treeBuilder->buildRenderable($some, $arguments) since it will then also have to set up #cache properly based on the requested types.

Berdir’s picture

Note that #lazy_builder is even better than #pre_render for cache. It plays nice with big_pipe for example and allows to postpone rendering of that part of the page. But it has the requirement that arguments to it must be passed a long as a string (so that they can be stored as a placeholder in HTML), so I think it won't work for the case when displaying a token tree with data/values.

hussainweb’s picture

I was thinking of handling caching in a follow-up, TBH. The caching doesn't work at the moment anyway and I wanted to focus on getting to the same level of features first. Of course, it is important to plan ahead so that we can be sure of the design.

Also, I am trying to approach this one step at a time. I am not used to massive refactors and would like to split it so that it is easy to review at each checkpoint and move ahead. If you don't mind, I would rather close this and work on caching in a follow-up.

I like the idea of buildRenderable. I was thinking of something similar but the name is perfect and since we have precedent, it sounds great. That can actually replace the whole render array structures with just one call.

Lastly, I want to explore why we have to use POST at all here. We are not modifying the data anywhere in the token/tree route. I wanted to explore this direction in a follow-up as well, just to avoid confusion. What do you think?

Berdir’s picture

I don't mean that we need to solve caching in this issue. That can absolutely be a follow-up and should be.

But, we need to go into a direction that will *allow* us to properly render-cache things later on. And "'#token_tree' => $treeBuilder->getTokenTree('all')," would be a problem for that.

If we go with buildRenderable() then we're a bit more flexible as we could add explicit caching ourself. With the drawback that our render array that we return would then just contain #markup and it would not be possible further alter it. So I still think it would be better to move all logic into #pre_render. But we can discuss that in a follow-up if you'd prefer that.

I'm usually a big fan of small steps too, I just want us to keep the big picture in mind, so we don't move off track in small steps :)

The problem with POST is that ajax requests are always POST. We started changing core to avoid that, but we didn't manage to competely switch. That means standard caching doesn't work anyway in a dialog. I actually forgot about that. Which means we have two options to get caching for the dialog:

* Add support for POST render caching in core. The easiest would be to have an optional post => TRUE flag in #cache or something similar. I think I can get Wim Leers interested in that :)
* Explicitly cache ourself in buildRenderable(), with the drawback that we will return a different render array in case of cache hits and lose the ability to customize it in the caller.

hussainweb’s picture

I looked into the dialog and AJAX system and POST is hardcoded with no one to override it as far as I can see. I don't think it is worth writing the AJAX requests ourselves...

About buildRenderable(), I was thinking it would return a render array with a '#token_tree' and cache tags set accordingly. It would still go to the TokenTreeTable render element (that is something which is renderable, right?) I am thinking you are talking about this in combination with pre_render, but I don't see why we have to use that. Can you point me to an example where it is used for caching? I can't find any documentation in relation with caching.

Further, if we use getTokenTree(), we can still use it along with buildRenderable() with cache settings specified in render array. If we have to use our own caching, we can cache at the buildTree() level (skipping the cache if $options['values'] is TRUE). I think if we have to cache, it should be close to the logic where we do bulk of the processing (buildTree). If we are using render cache, buildRenderable is a better choice. In both cases, we can pass back the bubbleable metadata to the render array. We just discard the metadata right now. This could help in render caching.

I think I am still missing something with pre_render and lazy_builder and so I can't really recommend one way or another. I do wish that render cache dealt with it. Considering that the only time it won't be cached will be in the dialog, which is only accessible to admin users, and not all that often used, I feel it is better to use render cache. If and when core starts supporting GET requests for dialog, we will have performance boost even for dialogs.

Berdir’s picture

Status: Needs review » Fixed

I think I am still missing something with pre_render and lazy_builder and so I can't really recommend one way or another.

What render caching does is to cache going from the input (the render array we specify) to the output (the HTML string).

It sounds more complicated than it actually is. The problem simply is that we already add the token_tree data to the input. Then it's loaded and there. And render caching can do nothing to avoid that. Yes, we can introduce separate caching there but I'd like to avoid that if possible, it makes the service more complex and we have two load a big data structure from cache, unserialize it, and then render the element.. but if we have a cache hit there, then we will never even look at #token_tree and loading that was wasting memory and CPU.

So the idea with render caching is to do as little as possible upfront and postpone everything to callbacks/lazy builders.

But, as I already said above, after thinking about this, this is a useful first step, so I committed this now. As a next issue, I would recommend to introduce buildRenderable() and kill the theme hook with that. And then, last, we look into caching + optimization to make caching as useful as possible.

  • Berdir committed 4c6bf11 on 8.x-1.x authored by hussainweb
    Issue #2645740 by hussainweb: Replace tree_table with a render element...
hussainweb’s picture

Thanks for committing and your reviews.

I think I now finally see what you meant all along. I see your concerns and quite agree and for a moment I thought that would make it harder to cache. But I thought on this a bit and then realized that this way makes sense. What we are caching here is render logic, which is quite separate from the token tree we are trying to build in buildTree. Earlier, the two were in the same place and that made things complex but we can now split them as we should and cache separately.

I am still working out how all of this play together and will follow your advice and work on the next step. Thanks for all your help and explanations. I really appreciate it.

hussainweb’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.