Part of meta-issue #1788918: [GTD] [META] Prepare for Twig main engine core inclusion until December 1

We will need to extend the twig function findTemplate to check Drupal's theme registry for the location of the current template if we want to be able to include one template from within another, example: image_formatter.twig
This is how we will be calling theme_image_style or theme_image based on weather there is a style or not...

<figure class="{{ item.attributes.class }}" {{ item.attributes }}>
  {% if style %}
    {% include 'image_style.twig' with {image: item.image } %}
  {% else %}
    {% include 'image.twig' %} 
  {% endif %}
  {% if item.caption %}
    <figcaption>
      {{ item.caption }}
    </figcaption>
  {% endif %}
</figure>

It currently throws an error saying that image.twig can not be found. But if you give it the full path to the image, like so:

<figure class="{{ item.attributes.class }}" {{ item.attributes }}>
  {% if style %}
    {% include 'core/themes/stark/templates/image/image_style.twig' with {image: item.image } %}
  {% else %}
    {% include 'core/themes/stark/templates/theme.inc/image.twig' %}
  {% endif %}
  {% if item.caption %}
    <figcaption>
      {{ item.caption }}
    </figcaption>
  {% endif %}
</figure>

It works just fine.

DX todo

  1. find all template paths for current theme
  2. find all template paths for parent themes(if any)
  3. find all core template files for core stark theme
  4. cache it or store into config() or EntityConfig
  5. addPath() with computed paths

Background Information
#1777532-4: Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme dependencies

Comments

jenlampton’s picture

Project: Drupal 8 with twig - abandoned sandbox »
Issue tags: +Twig engine

moving to the active sandbox.

jenlampton’s picture

Component: Code » Twig engine

updating component

andypost’s picture

Any reason to have current theme_registry or is there some battleplan to fix it with new EntityConfig?

podarok’s picture

We do not actually need extend findTemplate()
Looks like we just have to rewrite twig() function from twig.engine

with $loader->addPath($path) after creating $loader instance

function twig($reset = TRUE) {
  static $twig;
  if (empty($twig) || $reset) {
    // We will have our own loader.
    $loader = new Twig_Loader_Filesystem(DRUPAL_ROOT);
    $twig = new Twig_Environment($loader, array(
        'cache' => DRUPAL_ROOT . '/compilation_cache',
        'cache' => FALSE,
        'base_template_class' => '\\Drupal\\Core\\Template\\TwigTemplate',
        // @TODO: REMOVE ME!!!!!!!!
        'autoescape' => FALSE,
        'strict_variables' => TRUE,
    ));
    $twig->addNodeVisitor(new TwigNodeVisitor);
    $twig->addTokenParser(new DrupalHideTokenParser());

    $functions = array('t', 'attributes', 'drupal_attributes', 'drupal_render', 'render', 'myrender', 'hide', 'show', 'unset', 'count');
    foreach ($functions as $function) {
      /* @TODO We decided to always use filters at DrupalCon Munich. */
      $twig->addFunction($function, new Twig_Function_Function($function));
      $twig->addFilter($function, new Twig_Filter_Function($function));
      // @TODO remove URL once http://drupal.org/node/1778610 is resolved.
      $twig->addFunction('url', new Twig_Function_Function('url'));
    }
    $twig->disableStrictVariables();
  }

  return $twig;
}

podarok’s picture

Title: Extend Twig_Loader_Filesystem->findTemplate() to scan the theme registry for location of current templates » Extend Twig_Loader_Filesystem instance to get .twig templates paths with ierarchy for theme denendencies

better title and

My proposal for todo

  1. find all template paths for current theme
  2. find all template paths for parent themes
  3. find all core template files for core stark theme
  4. cache it or store into config() or EntityConfig
  5. addPath() with founded paths
podarok’s picture

Title: Extend Twig_Loader_Filesystem instance to get .twig templates paths with ierarchy for theme denendencies » Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme denendencies
Priority: Normal » Major

typo and status

soulston’s picture

Title: Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme denendencies » Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme dependencies

title typo

podarok’s picture

Issue summary: View changes

Updated issue summary.

podarok’s picture

Issue summary: View changes

Updated issue summary.

podarok’s picture

Issue summary: View changes

Updated issue summary.

vlad.dancer’s picture

+1 for #5

vlad.dancer’s picture

Issue summary: View changes

Updated issue summary.

Rene Bakx’s picture

Introducing a new static instance or singleton doesn't sound like a very smart plan when drupal is making the transition into a DIC system.

For Symfony2 the including of templates from bundle1 in bundle2 is solved by extending the include tab with a given structure.

something like this : incude "@bundle::Folder:folder:template.twig

I guess a system like this could work in drupal too, just need to substitute bundle for theme.

In the override of the include, we just need to check if the to be included file excist and include it. Perhaps the theme registry (if it survives into d8) could be injected into the twig environment, so it becomes easier to see if the requested incude file actually is part of a twig theme instead of traversing all themes and such.

vlad.dancer’s picture

As I understand bundle in Symfony it is like module in Drupal? So, U mean to search templates in modules?

podarok’s picture

incude "@bundle::Folder:folder:template.twig

Such syntax looks like not good due to theme dependency system
If one theme is child for another(parent) handling with ...folder:folder... is weird
I vote for #5 cause

include "foo.twig"

children
and

include "foo.twig"

parent(inactive, overridden) theme
is more simpler and better to undestand for front-end designers

podarok’s picture

Introducing a new static instance or singleton doesn't sound like a very smart plan when drupal is making the transition into a DIC system.

Yes, I agree with that
But as I know DIC is not ready yet
#1706064: Compile the Injection Container to disk
#1759152: Add a database service to the DIC
And If You can be more specific and provide for us a few examples how we can use DIC workaround - I`ll be happy with that

For now - singleton workaround works well and can be extended for multiple instances possibility between feature freeze and code freeze stages and do not looks like blocking issue for us till feature freeze

Rene Bakx’s picture

@vlad, Bundles are indeed the symfony equivalent of modules, but my comment was more from a syntax point of view.

@podarok,

Say you have a theme called 'bartik', and your own theme called 'thingee' which is a subtheme of bartik. With the naming structure suggested you could simply do something like.

{% extends '@bartik:node.twig' %} or {% include '@bartik:blocks:block_contact.twig' %}

Where I admit, it is more work to type in a template, but in a blink of an eye it is perfectly clear where the template is extended from, rather then make a magical system where you traverse themes underwater to see if it is a subtheme, and if the parent actually has the template you want to extend or include. Without our thrusty old theme registry it would mean that every time a change is made, the file system must do the traversing again and again. Performance wise, I rather would type a little bit more and spend my time educating new themers instead of telling them stories about magic includes.

As for an example on the DIC, must see if I can digg up some time next week, so I can see what the status is, cause I tought it was already functional, yet very slow cause it is not materialized to disk in a cache (like symfony2 does when you turn your application into production mode) Personally I would rather work on a long term solution, instead of focusing on a short term thing. But i can imagine the presure that is upon us as community, December is coming along awefully fast, and if we don't make it before then this whole twig in core is a no-go I presume?

podarok’s picture

the file system must do the traversing again and again.

Not with twig and its template compillation cache

Rene Bakx’s picture

only if you are in production mode, in development mode a template is always considered fresh and therefor recompiled on every hit. A minor issue perhaps, but from a developers point of view quite irritating and possible slow. Compare it to the now common practice of clearing the theme registry on every request.

But besides that, my point for extending the loader system with the @themename notation is more about the readablity of template code then performance. I believe drupal could use a little less magic and more clearabillty on where what comes from :)

Fabianx’s picture

Assigned: Unassigned » Fabianx

Starting work on this

Fabianx’s picture

Status: Active » Needs work

Seems we need something like:

{% include '...' with {
  x: y,
  preprocess: TRUE / FALSE,
} %}

and maybe run the preprocess function - if needed with the default being that preprocess is called, but for item-lists or table cells the output can be pre-formatted.

But maybe this is making it more complex and we should just ensure to always call the respective preprocess function. Maybe even re-add theme("item_list", { x:y }).

podarok’s picture

#17 make template designers crazy
can we make it more simple?

Fabianx’s picture

Thats the official way:

http://twig.sensiolabs.org/doc/tags/include.html

I don't think we should divert from it.

We just need to make sure that the preprocess functions are called properly.

podarok’s picture

#19 I got it

steveoliver’s picture

re:

We just need to make sure that the preprocess functions are called properly.

Can we discuss this point, and how to know what is "proper", like when does an {% include %}'ed twig file need or not need preprocessing?

steveoliver’s picture

Am I right to think that currently when we include a twig template we are *not* running preprocess on that, as we are circumventing theme() at that, template, level?

Fabianx’s picture

@22: That is correct.

marfillaster’s picture

In Symfony2 if preprocessing is needed on an include tag, render is used instead which creates a sub request.

Fabianx’s picture

#24: Interesting ...

marfillaster’s picture

This is the actual code that gets executed via twig render function. This in turn calls Symfony2 Actions templating helper.

https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/Framew...

jenlampton’s picture

Will #1777532: Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme dependencies resolve the preprocessing issue?

What I've been doing in current templates is calling the preprocessor for the included template in the preprocessor for the parent template, like so:

function template_preprocess_user_admin_roles(&$variables) {
  $form = $variables['form'];

  $header = array(t('Name'), t('Weight'), array(
      'data' => t('Operations'),
      'colspan' => 2,
    ));
  foreach (element_children($form['roles']) as $rid) {
    $row = array();
    foreach (element_children($form['roles'][$rid]) as $column) {
      $row[] = drupal_render($form['roles'][$rid][$column]);
    }
    $rows[] = array(
      'data' => $row,
      'class' => array('draggable'),
    );
  }

  // Distribute the role add form into table columns.
  $form['role']['name']['#title_display'] = 'invisible';
  unset($form['role']['name']['#description']);
  unset($form['role']['rid']['#description']);

  $actions = $form['role']['actions'];
  unset($form['role']['actions']);
  unset($form['role']['weight']);
  $row = array();
  $row[] = drupal_render($form['role']);
  // Empty placeholder for the weight column.
  $row[] = '';
  $row[] = array(
    'data' => drupal_render($actions),
    'colspan' => 2,
  );
  $rows[] = array('data' => $row);

  drupal_add_tabledrag('user-roles', 'order', 'sibling', 'role-weight');

  $variables['header'] = $header;
  $variables['rows'] = $rows;
  $variables['attributes'] = new Attribute(array('id' => 'user-roles'));
  // @todo: remove this call to the preprocess for table?
  template_preprocess_table($variables);
  $variables['form'] = drupal_render_children($form);
}

we could just continue to do this for every template that includes another, if we had to.

Fabianx’s picture

Update from Hangout today:

* include will never call preprocess, but will be changed so namespaced templates can be loaded from the theme_registry.
* We will provide theme() for calling the full stack().

=> If you use "theme" you are in Drupal land
=> If you use "use", "extends", "include", "import", "embed" you are in twig land, but templates will be loaded via our special file loader.

---> Nice separation of concerns.

jenlampton’s picture

Title: Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme dependencies » Allow use of theme() from within twig templates. Expected behavior with {% include %} should remain unchanged
Component: Twig engine » Twig templates (front-end branch)

I created #1842160: Consider appropriate usage of theme() from within Twig templates but I think that might just be a dupe of this one. Updating title.

Fabianx’s picture

Title: Allow use of theme() from within twig templates. Expected behavior with {% include %} should remain unchanged » Extend Twig_Loader_Filesystem instance to get .twig templates paths with hierarchy for theme dependencies
Component: Twig templates (front-end branch) » Twig engine (twig_engine branch)

We still need to change the loader, so changing title back and re-opening and following the other one.

Rene Bakx’s picture

I guess the way templates are discovered changed inbetween D7 and D8? If not, I rewrote the D7 twig template discovery system yesterday to mimic a part of the way Symfony2 is handling includes.

Basically it comes down to a structure like this :

{% extend theme::path:to:template.tpl.twig %}

or without the path

{% extend theme::template.tpl.twig %}

or if you are in the same theme

{% extend template.tpl.twig %}

I addapted the Twig_Loader to use this system, with a fallback to the normal inclusion system. And the changes to the engine + loader will be commited into my D7-twig sandbox somewhere this evening (CET+1)

Fabianx’s picture

#31: Thanks a lot, I will pull the changes in :-).

That was exactly my plan as well, but doesn't Symfony use @namespace: instead of namespace::?

Just wondering ...

Edit: I am not yet seeing the changes. Have you pushed, yet?

Rene Bakx’s picture

Nope.. did not make it.. deadlines and work. And yes symfony uses the @ namespace to determine the difference, but i removed the @ because the theming structure in D7 is a lot less structured over various bundles.

I changed the system only to use the current theme and if available the base theme as possible source.

Planning to do the commit today, i actually blocked some time this afternoon to do this :)

Just to be sure, my implementation is for D7, and not a backport of the D8 system. I looked at that once, but it was a lot of work with in my humble opinion less to be gained for D7.

Fabianx’s picture

I have taken a look at your sandbox and am thinking of what I'll port to D8, so that is totally fine as I am aware of the differences.

Fabianx’s picture

Issue summary: View changes

Updated issue summary.

sun’s picture

Status: Needs work » Closed (duplicate)

Unless I'm terribly mistaken, this has been fixed in Drupal core very recently. We now support to load templates from a given provider namespace, denoted by @provider/desired-template.

Unfortunately I'm not able to find the core issue right now, but nevertheless, closing as duplicate.