Brain-WP / Hierarchy

No-dependencies package that embodies WordPress template hierarchy
MIT License
84 stars 16 forks source link

Error Loading Partials #22

Closed sayontan closed 2 years ago

sayontan commented 3 years ago

To be honest this is probably more of a request for help rather than a bug report.

I reached this package from a StackOverflow thread (https://wordpress.stackexchange.com/questions/184235/best-way-of-passing-php-variable-between-partials). In there you have described a way to pass variables to templates without putting them in global scope. I was using that approach as the foundation for my theme, but I ran into issues when I tried combining it with a plugin that adds WooCommerce support to my theme (basically it was hell trying to get WooCommerce to read from a file in my plugin's folders). That brought me to the "Hierarchy" project that you have, since it helps define additional paths to traverse.

However, once I put in the code, I started running into a different issue. Let's say I have a file, index.php. This gets picked up by the approach you have in the ReadMe.

However, in index.php there if I have $this->partial('header'); as per the SO post (header.php is a file in the theme), I get an error saying that partial is not a method defined in File_Require_Loader (my files are renamed with underscores)

If I switch out the call to get_header() instead, I get an error from header.php saying Uncaught Error: Using $this when not in object context. That is because header.php also makes use of $this->partial().

So, regardless of what I do, I cannot get past the first step. Obviously there is no method called partial in File_Require_Loader. But I am unable to figure out what the equivalent method is. I tried using load, but that tells me that there is no file called header (since the code is doing a require $templatePath).

It is likely that I am doing something incorrectly or that I am missing something, but I cannot figure out what.

This is my basic flow:



Note that I don't have Composer, but I have made sure to `require_once` each file that gets picked up by the Hierarchy.
gmazzap commented 2 years ago

This is not a problem with Hierarchy.

Hierarchy default "loader" is FileRequireLoader (https://github.com/Brain-WP/Hierarchy/blob/master/src/Loader/FileRequireLoader.php) which does a a simple require of the template file, and so you can use $this inside templates required like that.

If you want to use both Hierarchy's QueryTemplate and a "renderer" object, you have to create your own loader, kile it is examplained at the end of the README, in the "QueryTemplate Usage Example: Loading and Rendering Mustache Templates" section. Instead of using a mustache engine, you would use your custom engine.

Something like this:

<?php
namespace MyTheme;

class Template
{
    private $context;
    private $baseDir = '';

    public function __construct(array $context = [])
    {
        $this->context = $context;
    }

    public function __get(string $name)
    {
        return $this->context[$name] ?? '';
    }

    public function render(string $template): string
    {
        $this->baseDir = trailingslashit(dirname($template));

        ob_start();
        require $template;
        return ob_get_clean() ?: '';
    }

    public function partial(string $template, array $data = []): string
    {
        $newObj = new self(array_merge($this->context, $data));
        if (!is_file($template) && is_file($this->baseDir . $template)) {
            $template = $this->baseDir . $template;
        }

        return $newObj->render($template);
    }
}

class MyLoader implements \Brain\Hierarchy\Loader\Loader
{
    public function load(string $templatePath): string
    {
        global $wp_query, $wp;
        $baseData = ['query' => $wp_query, 'wp' => $wp];
        // You need a way to build data to pass to templates...
        $data = (array)apply_filters('my_theme_template_data', $baseData, $templatePath, $wp_query, $wp);

        return (new Template($data))->render($templatePath);
    }
}

(Please note the code above uses Hierarchy version 3.0 released today. In version 2 the interface had a different name.)

At that point you could do:

add_action('template_redirect', function(): void
{
    if (!\Brain\Hierarchy\QueryTemplate::mainQueryTemplateAllowed()) {
         return;
    }
    global $wp_query;
    $template = new \Brain\Hierarchy\QueryTemplate();
    echo $template->loadTemplate($wp_query);
    exit;
}, 99);

An then you can write templates using the standard WP hierarchy, but inside templates you can use $this->varName to access variables passed in the main template "data", and you can use $this->partial('templateName') to load partials using relative path to templates instead of full paths.

And inside partials you'll have access to both the "main" data, plus and data that you could pass explicitly via $this->partial() method.

WP functions like get_header() or get_footer() will continue to work, but in the templates they'll load (header.php/footer.php) you'll not have access to the main data passed to your templates, nor you'll be able to use $this inside header.php and/or footer.php.

However, nothing stops you to use $this->partial('header') / $this->partial('footer') if you want to keep the same approach even in header/footer files, but then you'd probably want to trigger manually WP hooks. For example:

// this is index.php, for example

do_action('get_header', '', $this->context);
$this->partial('header');

/* ...rest of template here.. */

do_action('get_footer', '', $this->context);
$this->partial('footer');