Updated: Comment #23

Problem/Motivation

When a theme suggestion is called directly, like theme('comment__node__article', …), the Twig debug output does not show the base template - in this case comment.html.twig, or the derived template comment--node.html.twig.

Proposed resolution

Add code to twig.engine to handle this scenario and display all possible template suggestions in the correct order

Remaining tasks

Write tests.

User interface changes

Twig debug output will display the base template and derived template suggestions when theme suggestions are called directly. Suggestions added via preprocess (theme_hook_suggestions) will display in the correct order.

API changes

None

#2118743: Twig debug output does not display all suggestions when an array of suggestions is passed to #theme
#1751194: Introduce hook_theme_suggestions[_HOOK]() and hook_theme_suggestions[_HOOK]_alter()

Comments

Cottser’s picture

Status: Active » Needs review
FileSize
1.42 KB
PASSED: [[SimpleTest]]: [MySQL] 58,055 pass(es). View

Patch, then I will set to CNW and tag for tests.

Cottser’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

We have some Twig debug tests already in \Drupal\system\Tests\Theme\TwigDebugMarkupTest, so we can add tests there for this case.

Cottser’s picture

Issue tags: +Novice

I also think adding tests for this should be a good novice automated testing task :)

cilefen’s picture

FileSize
3.26 KB
FAILED: [[SimpleTest]]: [MySQL] 57,535 pass(es), 1 fail(s), and 0 exception(s). View

This is an attempt to add the test, using theme('comment__node__article', ...). I passed in a comment object and it looks as though theme() finds the comment template but the debug output is not there so the test fails.

Cottser’s picture

Status: Needs work » Needs review

Thanks @cilefen, looks good at a glance. Setting the issue to 'needs review' so that this gets tested.

If you can also upload a test-only patch that would be great - see the screenshot near the bottom of https://drupal.org/contributor-tasks/write-tests. Uploading a test-only patch shows very clearly on the issue that the test is doing its job and failing appropriately.

cilefen’s picture

I will do that as soon as possible but I think 2029225-4.patch will not pass testing as-is.

cilefen’s picture

FileSize
4.17 KB
PASSED: [[SimpleTest]]: [MySQL] 57,565 pass(es). View
2.75 KB
PASSED: [[SimpleTest]]: [MySQL] 57,555 pass(es). View

The strange thing is -- the tests alone are passing for me. Let's see what the test bot says.

cilefen’s picture

2029225-7-tests.patch and 2029225-7-complete.patch are probably both going to pass--and that's bad. I am not sure 2029225.patch is doing what was intended.

Is the purpose of this issue to show the possible template files and the selected file indicated with an asterisk in the debug area? It is happening with theme('node', ...) but not with theme('comment__node', ...).

Cottser’s picture

FileSize
1.17 KB
1.43 KB
FAILED: [[SimpleTest]]: [MySQL] 56,879 pass(es), 1 fail(s), and 0 exception(s). View

This is very good and is a perfect example of why we write tests :) the fix in #1 was not correct for the case of a theme hook with no additional suggestions. Node inserts additional template suggestions in its preprocess function (template_preprocess_node()) by adding to the 'theme_hook_suggestions' key in the $variables array. Sidenote: see #1751194: Introduce hook_theme_suggestions[_HOOK]() and hook_theme_suggestions[_HOOK]_alter() for a new plan to handle that functionality.

Here is an updated "fix" patch, I think you should be able to get a failing/passing test for this with some minor tweaks. It might be helpful to use debug() to check the $output when running tests if you aren't already.

Status: Needs review » Needs work

The last submitted patch, 2029225-fix-9.patch, failed testing.

Cottser’s picture

Status: Needs work » Needs review
FileSize
1.44 KB
PASSED: [[SimpleTest]]: [MySQL] 57,222 pass(es). View

Needed a quick reroll after #1843650: Remove the process layer (hook_process and hook_process_HOOK), the \Drupal\forum\Tests\ForumTest failure was random.

cilefen’s picture

FileSize
4.3 KB
PASSED: [[SimpleTest]]: [MySQL] 57,532 pass(es). View
2.87 KB
FAILED: [[SimpleTest]]: [MySQL] 57,674 pass(es), 2 fail(s), and 0 exception(s). View

Status: Needs review » Needs work
Issue tags: -Needs tests, -Novice, -Twig

The last submitted patch, 2029225-complete-12.patch, failed testing.

Cottser’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests, +Novice, +Twig

#12: 2029225-complete-12.patch queued for re-testing.

Fabianx’s picture

+++ b/core/includes/theme.incundefined
@@ -994,6 +994,13 @@ function theme($hook, $variables = array()) {
+  if ($hook != $original_hook) {
+    $variables += array(
+      'theme_hook_base' => $hook,
+    );

Can we always set this and move the logic to twig_render_template instead?

Cottser’s picture

Status: Needs review » Needs work

@Fabianx - Sure, we could do that. But I'm also realizing:

+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
@@ -67,6 +68,30 @@ function testTwigDebugMarkup() {
+    $output = theme('comment__node_article', comment_view($comment));
+    $this->assertTrue(strpos($output, "CALL: theme('comment__node_article')") !== FALSE, 'Theme call information found.');
+    $this->assertTrue(strpos($output, 'x comment' . $extension) !== FALSE, 'Comment template file shown as current template.');
+    $this->assertTrue(strpos($output, '* comment--node-article' . $extension) !== FALSE, 'Full name template file found.');
+    $template_filename = $templates['comment']['path'] . '/' . $templates['comment']['template'] . $extension;
+    $this->assertTrue(strpos($output, "BEGIN OUTPUT from '$template_filename'") !== FALSE, 'Full path to current template file found.');

This should probably use the standard suggestion format of comment__node__article - two underscores as delimiters.

And then the debug output should show:

<!-- THEME DEBUG -->
<!-- CALL: theme('comment__node__article') -->
<!-- FILE NAME SUGGESTIONS:
   x comment.html.twig
   * comment--node.html.twig
   * comment--node--article.html.twig
-->

And all of these possibilities should be shown, even if comment--node.html.twig or comment--node--article.html.twig templates are implemented. This is not the case with the current patch.

Currently if you call comment__node__article and have a comment--node--article.html.twig template you don't get any information about suggestions because theme_hook_called == theme_hook_derived, or in other words an implementation was found for the hook passed into theme().

To accomplish this, I think our next steps will be to update theme() and/or twig_render_template() and expand test coverage. There is some code in theme() used to determine the derivative suggestions, but for the debug output we can't stop once an implementation is found.

    // Iteratively strip everything after the last '__' delimiter, until an
    // implementation is found.
    while ($pos = strrpos($hook, '__')) {
      $hook = substr($hook, 0, $pos);
      if (isset($hooks[$hook])) {
        break;
      }
    }
cilefen’s picture

I see - I will rewrite the test.

cilefen’s picture

FileSize
2.98 KB
FAILED: [[SimpleTest]]: [MySQL] 57,601 pass(es), 10 fail(s), and 0 exception(s). View
Fabianx’s picture

Status: Needs work » Needs review

.

Status: Needs review » Needs work

The last submitted patch, 2029225-test-18.patch, failed testing.

cilefen’s picture

Status: Needs work » Needs review
FileSize
2.99 KB
FAILED: [[SimpleTest]]: [MySQL] 57,541 pass(es), 3 fail(s), and 0 exception(s). View

I missed a hyphen.

Status: Needs review » Needs work

The last submitted patch, 2029225-test-21.patch, failed testing.

Cottser’s picture

Title: Twig debug output does not show the base hook when a theme suggestion is called directly » Twig debug output does not show the base or derived hooks when a theme suggestion is called directly
Status: Needs work » Needs review
FileSize
2.11 KB
5.1 KB
PASSED: [[SimpleTest]]: [MySQL] 57,536 pass(es). View

Thanks @cilefen, that's really helpful. Go TDD :)

This patch doesn't add any code to theme() - the added code only fires when twig_debug is on. This also fixes the incorrect order of suggestions in the debug output - they should start with the most specific suggestions. That change could be moved to a separate issue but at this point I think it's a small enough change.

The fix code being added could really use some refactoring but I wanted to get it up here. It works for the test case provided in #21 as well as the case of node__foo__bar and displays the suggestions in the correct order of how the templates are matched, most specific to least specific.

I'd like to see more test coverage for the order that the suggestions are displayed in, particularly for the more complex node case below. Having consistent debug markup is going to be very important for good TX.

<!-- THEME DEBUG -->
<!-- CALL: theme('comment__node__article') -->
<!-- FILE NAME SUGGESTIONS:
   * comment--node--article.html.twig
   * comment--node.html.twig
   x comment.html.twig
-->
<!-- THEME DEBUG -->
<!-- CALL: theme('node__foo__bar') -->
<!-- FILE NAME SUGGESTIONS:
   * node--foo--bar.html.twig
   * node--foo.html.twig
   * node--3.html.twig
   * node--page.html.twig
   x node.html.twig
-->
<!-- THEME DEBUG -->
<!-- CALL: theme('node') -->
<!-- FILE NAME SUGGESTIONS:
   * node--2.html.twig
   * node--page.html.twig
   x node.html.twig
-->
cilefen’s picture

FileSize
6.99 KB
PASSED: [[SimpleTest]]: [MySQL] 58,061 pass(es). View
4.88 KB
FAILED: [[SimpleTest]]: [MySQL] 57,295 pass(es), 4 fail(s), and 0 exception(s). View

This one checks the order of template file suggestions and adds theme('node__foo__bar', ..).

markcarver’s picture

I was debating on whether or not to create a new issue or not. I think we should just expand the scope to include this change too. I won't change the status of the issue and will leave the decision up to @Cottser.

After reading http://www.webomelette.com/how-to-theme-fields-drupal-7 via Planet Drupal, I realized one of the most frustrating things a themer has is figuring out where these templates are located if they want to override them. Not sure _how_ we want to do this, but maybe we could append the currently "selected" template (noted by "X") with the full (base) path to the template:

<!-- THEME DEBUG -->
<!-- CALL: theme('node__foo__bar') -->
<!-- FILE NAME SUGGESTIONS:
   * node--foo--bar.html.twig
   * node--foo.html.twig
   * node--3.html.twig
   * node--page.html.twig
   x node.html.twig (/core/modules/node/templates/node.html.twig)
-->
Cottser’s picture

We already display the full path to the template file being used, check your debug markup :)

Example can be seen in the Twig settings/debug change notice: https://drupal.org/node/1922666

markcarver’s picture

Omg, duh. It was a random thought. Never mind me.

Cottser’s picture

Issue tags: -Needs tests
FileSize
1.35 KB
7.03 KB
FAILED: [[SimpleTest]]: [MySQL] 58,732 pass(es), 0 fail(s), and 49 exception(s). View

Here's a reroll with some improved inline comments. Looking at this again I'm okay with the fix but will leave it up to someone else to review :)

Thanks again @cilefen for working on the tests, you rock!

joelpittet’s picture

Issue tags: -Novice

I've had a look at this patch and it seems to work as designed. The test's expected reqults could be built before the assert to make them easier to read but that's about it.

Status: Needs review » Needs work

The last submitted patch, 2029225-28.patch, failed testing.

Cottser’s picture

Thanks @joelpittet! We could maybe simplify the test and not use the comment example :)

cilefen’s picture

FileSize
3.32 KB
4.99 KB
PASSED: [[SimpleTest]]: [MySQL] 58,509 pass(es). View

Here it is without the comment template test.

I noticed in getInfo, the test name is set in lower case like this:
'name' => 'Twig debug markup'

But other tests in the Twig category use caps, so it would be this:
'name' => 'Twig Debug Markup'

Is that something we should change, and if so, change now?

cilefen’s picture

FileSize
3.97 KB

Whoa, interdiff fail, use this one instead.

cilefen’s picture

FileSize
3.97 KB

Whoa, interdiff double-fail. Thanks for your patience!

cilefen’s picture

Status: Needs work » Needs review
Cottser’s picture

@dawehner pointed out on IRC that the debug output also doesn't match up when an array of suggestions is passed into theme().

Created a separate issue for that: #2118743: Twig debug output does not display all suggestions when an array of suggestions is passed to #theme

Cottser’s picture

Issue summary: View changes

Update to reflect more recent status

Cottser’s picture

Issue summary: View changes

Add related issue for an array of suggestions

joelpittet’s picture

Assigned: Unassigned » joelpittet
Issue summary: View changes

Assigning for review.

Cottser’s picture

FileSize
4.99 KB
FAILED: [[SimpleTest]]: [MySQL] Setup environment: Test cancelled by admin prior to completion. View
Cottser’s picture

FileSize
5.05 KB
PASSED: [[SimpleTest]]: [MySQL] 64,586 pass(es). View
1.36 KB

Updating the added test case to not use theme() or _theme(). Going to cancel the last one.

The last submitted patch, 38: 2029225-38.patch, failed testing.

joelpittet’s picture

+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
@@ -51,8 +51,7 @@ function testTwigDebugMarkup() {
-    $this->assertTrue(strpos($output, 'x node--1' . $extension) !== FALSE, 'Node ID specific template shown as current template.');
-    $this->assertTrue(strpos($output, '* node' . $extension) !== FALSE, 'Base template file found.');
+    $this->assertTrue(strpos($output, 'x node--1' . $extension . PHP_EOL . '   * node--page' . $extension . PHP_EOL . '   * node' . $extension) !== FALSE, 'Suggested template files found in order and node ID specific template shown as current template.');

@@ -60,8 +59,16 @@ function testTwigDebugMarkup() {
-    $this->assertTrue(strpos($output, '* node--2' . $extension) !== FALSE, 'Node ID specific template suggestion found.');
-    $this->assertTrue(strpos($output, 'x node' . $extension) !== FALSE, 'Base template file shown as current template.');
+    $this->assertTrue(strpos($output, '* node--2' . $extension . PHP_EOL . '   * node--page' . $extension . PHP_EOL . '   x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.');
...
+    $this->assertTrue(strpos($output, "CALL: _theme('node__foo__bar')") !== FALSE, 'Theme call information found.');
+    $this->assertTrue(strpos($output, '* node--foo--bar' . $extension . PHP_EOL . '   * node--foo' . $extension . PHP_EOL . '   * node--3' . $extension . PHP_EOL . '   * node--page' . $extension . PHP_EOL . '   x node' . $extension) !== FALSE, 'Suggested template files found in order and base template shown as current template.');

These assertTrue can be easier written with assertRaw, which does exactly the same thing.
I'm mentioning this because the output string is quite hard to read.

That and the CALL: should be replaced with something else like "Rendered theme:"

I don't know if you'd rather take care of those here or in a follow-up issue?

Cottser’s picture

Thanks @joelpittet!

We can't use assertRaw here because we aren't doing a drupalGet(). I agree the lines are a bit long/hard to read but not sure how to improve upon them, we really want to check the order that things are output. See #23/#24.

I'd rather the "CALL" stuff be changed in #2029225: Twig debug output does not show the base or derived hooks when a theme suggestion is called directly.

joelpittet’s picture

Assigned: joelpittet » Unassigned
Status: Needs review » Reviewed & tested by the community

Ok well this does all the things it needs to do:)

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed to 8.x. Thanks!

Status: Fixed » Closed (fixed)

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