Score:0

How can I pass existing variables to a custom module template?

ua flag

I'm having trouble working out how to pull existing variables into my custom module TWIG template.

I can create my own variables and pass them, but do not know how to expose variables that already exist from the theme or other entities and then pass them to the template in my custom module.

I'm working in Drupal 10, and my general goal is to create a custom module that will build a custom landing page for me. The code I'm trying right now is just test code to work out how to use a custom module to inject such a page into my site.

My custom module is called main_sections. To start I have a TWIG template called pphtml--landing.html.twig. I've named it this weird way so I can distinguish custom syntax vs any potential default naming formats Drupal might be pulling from elsewhere. My primary theme is a Drupal Bootsrap sub-theme, that has no customization yet. For testing I'm starting by trying to pull in the site_slogan variable and the page.primary_menu variable which is normally available as part of the existing theme page.html.twig template.

In my module I have my template: main_sections/templates/pphtml--landing.html.twig

<div class="layout-container">
  <header role="banner">
    <div>
      <p>TESTING HEAD</p>
    </div>
    {{ page.header }}
  </header>
      <div>
        <p>TESTING BODY</p>
        <p>test_var: {{ test_var }}</p>
        <div>{{ site_slogan }}</div>
        <div>{{ page.primary_menu }}</div>
        <div>{{ kint(existing) }}</div>
        <div>{{ kint(theme) }}</div>
      </div>
    <footer role="contentinfo">
        <div>
          <p>FOOTER TESTING</p>
        </div>
      </footer>
    </div>

my controller main_sections/src/Controller/SectionsController.php is:

  /**
   * Returns landing page.
   *
   * @return array
   *   for landing page template pphtml--landing
   */
  public function landingPage() {
    return [
      '#theme' => 'pphtml__landing',
    ];
  }

In my module's routing file main_sections/main_sections.routing.yml I have:

main_sections.landing:
  path: '/landing'
  defaults:
    _controller: '\Drupal\main_sections\Controller\SectionsController::landingPage'
    _title: 'Spoon Soup Landing Page'
  requirements:
    _permission: 'access content'

and then in my module's main_sections/main_sections.module I have:

function main_sections_theme($existing, $type, $theme, $path) {
  return [
    'pphtml__landing' => [
      'variables' => [
        'test_var' => 'my var',
        'theme' => $theme,
        'existing' => $existing,
      ],
    ],
  ];
}

This is the result of the code above, that also highlights what the site slogan and main navigation menu is that I'm trying to also pull into the landing page template. Though as one can see they are not displaying in the custom template. landing page result

Here is a screenshot of the $existing array expanded: enter image description here

I'm guessing that I may have to use some kind of pre-processing function to grab variables that are natively pre-processed for templates that are inside the theme folder, but I can't seem to work out how to do that.

Kevin avatar
in flag
Did you inspect the contents of $existing?
micbay avatar
ua flag
Yes, I looked through the data in the $existing array and nothing there seemed to apply. I'll edit the post to add an image of it expanded in case there's something I missed.
Score:0
ua flag

Using the custom template pre-process function or trying to mine the theme hook $existing parameter, or pre-process $variables parameter, to get data from existing Drupal entities to use in the custom module template, was the wrong approach.

It turns out I needed to use general Drupal methods to pull the desired Drupal entities from within the custom template's theme hook function. Then pass them to the template by adding them to custom template variables array.

So inside the main_sections_theme function we can do the following:

Getting Config Parameter to Pass to Custom Template

With the Drupal::configFactory() interface, we can use the listAll() method to get a list of the available configuration objects.

$site_config = \Drupal::configFactory()->listAll();

Once we have figured out the name of the config entity we want we can use Drupal::config($name) to get a read only config object. In this case I wanted the site information configuration which I figured out is contained in system.site.

Thus, we can get the full set of system.site configuration parameters using:

$site_info = \Drupal::config('system.site')->get();

Wanting only the site slogan parameter, this can be further refined to pull just the site slogan by using:

$site_slogan = \Drupal::config('system.site')->get('slogan');

Getting Existing Menu Object to Pass to Custom Template

Reviewing the Drupal Menu System API Docs page, Rendering Menus section, we see that we can use the Drupal function menuTree(), to get a MenuLinkTreeInterface that we can use to load an existing menu by name.

// get the menu tree interface object
$menu_tree_interface = \Drupal::menuTree();
// create menu tree parameters object needed to load requested menu
$menu_parameters = new \Drupal\Core\Menu\MenuTreeParameters();
// get menu tree object for the 'main' navigation menu
$main_menu_tree = $menu_tree_interface->load('main', $menu_parameters);

In this case the $main_menu_tree, at this point, can be built into a renderable array and passed as a template variable, however, from the documentation it seems most proper to also add standard manipulators to ensure access and menu order properties are maintained. Using the same MenuLinkTreeInterface we can use the transform() method to add a couple standard manipulators. Then we can finally prepare our pulled menu data back into a renderable array that can be passed as the template variable, using the MenuLinkTreeInterface function, build($tree):

// create array of desired manipulators to apply to menu tree
$manipulators = array(
  // Only show links that are accessible for the current user.
  array(
    'callable' => 'menu.default_tree_manipulators:checkAccess',
  ),
  // Use the default sorting of menu links.
  array(
    'callable' => 'menu.default_tree_manipulators:generateIndexAndSort',
  ),
);
// apply manipulators to the menu tree using \Drupal::menuTree()->transform()
$tree = $menu_tree_interface->transform($main_menu_tree, $manipulators);
// convert main menu tree into renderable object for TWIG
$main_menu = $menu_tree_interface->build($tree);

Passing Pulled Existing Site Parameters as Variables to the Custom Template

Finally, we can now pass the discovered Drupal entity parameters, we pulled into the theme hook function above, as variables for our custom TWIG template. We do this by assigning a hook variable name to each variable we want to pass and add them to the template variables array in the theme_HOOK return object.

return [
  'pphtml__landing' => [
    'variables' => [
      'test_var' => 'test text variable',
      'theme' => $theme,
      'main_menu' => $main_menu,
      'main_menu_tree' => $main_menu_tree,
      'site_slogan' => $site_slogan,
      'site_information' => $site_info,
      'site_config' => $site_config,
    ],
  ],
];

The final function main_sections_them(), found in the main_sections.module file, in full:

/**
 * Implements hook_theme()
 * Primary module hooks for test_module module.
 */

function main_sections_theme($existing, $type, $theme, $path) {

  // get the menu tree interface object
  $menu_tree_interface = \Drupal::menuTree();
  // create menu tree parameters object
  $menu_parameters = new \Drupal\Core\Menu\MenuTreeParameters();
  // get menu tree object for the 'main' navigation menu
  $main_menu_tree = $menu_tree_interface->load('main', $menu_parameters);
  // create array of manipulators to add to menu tree object
  $manipulators = array(
    // Only show links that are accessible for the current user.
    array(
      'callable' => 'menu.default_tree_manipulators:checkAccess',
    ),
    // Use the default sorting of menu links.
    array(
      'callable' => 'menu.default_tree_manipulators:generateIndexAndSort',
    ),
  );
  // Transform the menu tree using the manipulators you want.
  $tree = $menu_tree_interface->transform($main_menu_tree, $manipulators);
  // build transformed menu tree into renderable array object for TWIG
  $main_menu = $menu_tree_interface->build($tree);

  // get list of site configuration entities
  $site_config = \Drupal::configFactory()->listAll();
  // get array of all system site information configuration parameters
  $site_info = \Drupal::config('system.site')->get();
  // get the site slogan the Drupal system site information configuration
  $site_slogan = \Drupal::config('system.site')->get('slogan');

  return [
    'pphtml__landing' => [
      'variables' => [
        'test_var' => 'test text variable',
        'theme' => $theme,
        'main_menu' => $main_menu,
        'main_menu_tree' => $main_menu_tree,
        'site_config' => $site_config,
        'site_information' => $site_info,
        'site_slogan' => $site_slogan,
      ],
    ],
  ];
}

The final custom TWIG template pphtml--landing.html.twig using our new variables:

<div class="layout-container">
  <header role="banner">
    <div>
      <p>TESTING HEAD</p>
    </div>
  </header>
  
  <div>
    <p>TESTING BODY</p>
    <div>{{ kint(theme) }}</div>
    <p>Simple text variable: {{ test_var }}</p>
    {{ kint(site_config) }}
    {{ kint(site_information) }}
    {{ kint(site_slogan) }}
    {{ kint(main_menu_tree) }}
    {% if site_slogan %}
      <div class="d-inline-block align-top site-name-slogan">
        {{ site_slogan }}
      </div>
    {% endif %}
    <nav class="navbar navbar-dark bg-primary navbar-expand-lg">
      <div class="container contextual-region block block-menu navigation menu--main">
        {{ main_menu }}
      </div>
    </nav>
  </div>

  <footer role="contentinfo">
    <div>
      <p>FOOTER TESTING</p>
    </div>
  </footer>
</div>

Final resulting page showing devel kint variables we used to figure things out and the properly rendered site slogan and main menu inside our custom template. Final Rendered Result


Expanded kint arrays: expanded kint arrays

I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.