Drupal 8 https://design.briarmoon.ca/ en Getting the parent node of a Drupal 8 node https://design.briarmoon.ca/tutorials/drupal-8/getting-the-parent-node-of-a-drupal-8-node <span class="clearfix field field--name-title field--type-string field--label-hidden">Getting the parent node of a Drupal 8 node</span> <span class="clearfix field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">NickWilde</span></span> <span class="clearfix field field--name-created field--type-created field--label-hidden">Sat, 12/16/2017 - 09:16</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>There are some circumstances that you want to get data from the node one level up a menu from your current node. However, doing that is not self-explanatory since there isn't actually any direct relationship between the nodes.I'll be referring to the nodes as "parent" and "child" for simplicity despite this. For example, consider this snippet of a menu from /admin/structure/menu/manage/main <img alt="Screenshot of a menu admin page in Drupal 8" data-entity-type="file" data-entity-uuid="279535a4-c12e-46ed-b3ec-3b69181dfe0d" src="/sites/default/files/inline-images/Screenshot-2017-12-16%20Edit%20menu%20Main%20navigation%20BriarMoon%20Farm.png" /></p> <p>"Goats" would link to the "parent" node, while "Our Policies" would link to the "child" node.</p> <p>My personal use case was getting a hero image from the parent node if the child node didn't have one. But it could be applied, to get any data from the parent. You could also, reverse the process and get data from the child although then you would have to filter it to <em>what</em> child node/menu item whereas menu items only have one parent.</p> <p>This basic technique can be done from either a module or a theme although depending on the hook you need it in, you may need to use a module - but most hooks you'd want to use it in work in themes. You could also use it in OOP based code in a module although then you'd want to inject some of the services. For the purpose of this tutorial, we'll presume you've created a new theme - if you don't know how to do that, see <a href="https://www.drupal.org/docs/8/theming-drupal-8/creating-a-drupal-8-sub-theme-or-sub-theme-of-sub-theme">Creating Drupal a 8 sub-theme</a>.</p> <h2 id="pre-requisites">Pre-requisites</h2> <ul> <li>Functioning Drupal 8 install</li> <li>Some Drupal 8 knowledge</li> <li>Moderate PHP comfort.</li> <li>Custom module or theme to add it to.</li> </ul> <h2 id="setup">Setup</h2> <p>Since Drupal 8 uses namespaces and autoloading (because they make so much easier), you'll have to add a use statement to the top of the file:</p> <pre> <code>use Drupal\node\Entity\Node; </code></pre> <p>Then you have to decide what hook you need to use it in. This is mostly based on what you are doing with this data. In my case, I used <a href="https://api.drupal.org/api/drupal/core%21includes%21theme.inc/function/template_preprocess_region/">template_preprocess_region()</a> however, you can use any hook - or class. A frequently ideal choice, would probably be <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21node.module/function/template_preprocess_node/8.4.x">template_preprocess_node()</a> - it also means the node is guaranteed to be available.</p> <p>With whatever hook we're using, the first thing we need to do is get the current node's id. If using <code>template_preprocess_node</code>, that'd look like:</p> <pre> <code>function MY_THEME_preprocess_node($var) { $node_id = $var['node']-&gt;id(); ...</code></pre> <p>For most other hooks, you'll have to get the node via the ::request() service. There is one important hangup here that although easy to avoid you have to know: there are cases when the request includes a node ID instead of a loaded node object. So if you then try to do operations with this "object" you'll get a fatal error. So in that case, your code would look like:</p> <pre> <code>function MY_THEME_preprocess_region($var) { // This will only be true if it is a node route. if ($node = \Drupal::request()-&gt;attributes-&gt;get('node')) { if (is_string($node)) { $node_id = $node; } else { $node_id = $node-&gt;id(); } ...</code></pre> <p>Now we get to the part that is least straight forward. Nodes don't actually have a direct relationship with Menu Links. However, the menu link manager service will get links that match various filters. So next, we get the menu link manager service, get the relevant link, then get the parent menu item. Finally from the parent menu item, if it refers to a node, we get that node. Continuing that function, next we add:</p> <pre> <code> /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); $links = $menu_link_manager-&gt;loadLinksByRoute('entity.node.canonical', ['node' =&gt; $node_id]); // Because loadLinksByRoute() returns an array keyed by a complex id // it is simplest to just get the first result by using array_pop(). /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = array_pop($links); // Now check if the menu has a parent menu item and if so load it. /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */ if ($link-&gt;getParent() &amp;&amp; $parent = $menu_link_manager-&gt;createInstance($link-&gt;getParent())) { // Finally, we figure out if the parent menu item refers to another node // and if so, load it. $route = $parent-&gt;getUrlObject()-&gt;getRouteParameters(); if (isset($route['node']) &amp;&amp; $parent_node = Node::load($route['node'])) { // We now have a fully loaded node in the $parent_node variable and can // get whatever data we need from it. } }</code></pre> <h2 id="straight-forward-code">Straight forward code</h2> <p>That code above is extra commented to explain what is going on and split into chunks that are hard to copy and past. So here is the completed function if you're using <code>template_preprocess_region</code>.</p> <pre> <code>/** * Implements template_preprocess_region(). */ function MY_THEME_preprocess_region($var) { if ($node = \Drupal::request()-&gt;attributes-&gt;get('node')) { if (is_string($node)) { $node_id = $node; } else { $node_id = $node-&gt;id(); } /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); $links = $menu_link_manager-&gt;loadLinksByRoute('entity.node.canonical', ['node' =&gt; $node_id]); /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = array_pop($links); /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */ if ($link-&gt;getParent() &amp;&amp; $parent = $menu_link_manager-&gt;createInstance($link-&gt;getParent())) { $route = $parent-&gt;getUrlObject()-&gt;getRouteParameters(); if (isset($route['node']) &amp;&amp; $parent_node = Node::load($route['node'])) { // Do something with teh $parent_node here. } } } }</code></pre> <p>Any questions? Something unclear? Something you disagree with? Leave a comment below.</p> </div> <div class="clearfix field field--name-field-audience field--type-entity-reference field--label-inline"> <div class="field__label">Audience</div> <div class="field__item">Back-End Development</div> </div> <div class="clearfix field field--name-field-difficulty field--type-entity-reference field--label-inline"> <div class="field__label">Difficulty</div> <div class="field__item">Moderate</div> </div> <div class="clearfix field field--name-field-recommended-skills field--type-entity-reference field--label-inline"> <div class="field__label">Recommended Skills</div> <div class="field__items"> <div class="field__item">PHP</div> </div> </div> <div class="clearfix field field--name-field-tutorial-subject field--type-entity-reference field--label-above"> <div class="field__label">Tutorial Subjects:</div> <div class="field__items"> <div class="field__item"><a href="/tutorials/drupal/drupal-8" hreflang="en">Drupal 8</a></div> </div> </div> <section class="clearfix field field--name-comment field--type-comment field--label-above comment-wrapper"> <h2 class="title comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=18&amp;2=comment&amp;3=comment" token="nAj2keyfEt4_0fyhSgew93ONC0o92AbyNwt55od7XmU"></drupal-render-placeholder> </section> Sat, 16 Dec 2017 17:16:13 +0000 NickWilde 18 at https://design.briarmoon.ca https://design.briarmoon.ca/tutorials/drupal-8/getting-the-parent-node-of-a-drupal-8-node#comments