大脑/层次结构

无依赖的软件包,体现WordPress模板层次结构

3.2.0 2022-11-30 00:08 UTC

README

PHP Quality Assurance codecov.io license release

使用PHP对象表示WordPress模板层次结构

目录

是什么/为什么?

对于每个前端请求,WordPress都会运行一个查询,然后根据该查询加载一个模板文件。

查询 => 模板映射遵循在模板层次结构中定义的规则。

然而,给定一个查询对象,没有一种方法可以程序化地

  • 知道WordPress将加载哪个模板
  • 知道WordPress将搜索哪些模板
  • 将相同的"查询到模板解析"应用于一个查询

此库提供了一种执行上述3件事的方法。

模板层次结构表示

给定一个查询,此库以PHP数组的形式提供了一个模板层次结构表示

示例

// we will show template hierarchy for the main query
global $wp_query; 

$hierarchy = new Brain\Hierarchy\Hierarchy();

var_export($hierarchy->hierarchy($wp_query));

假设我们正在访问一个URL,例如 example.com/category/foo/page/2,并且术语"foo"的分类ID是123,上述代码的输出是

array(
  'category' => array('category-foo', 'category-123', 'category'),
  'archive' => array('archive'),
  'paged' => array('paged'),
  'index' => array('index'),
);

如果您将此数组与模板层次结构视觉概述进行比较,您可以验证上述内容是针对分类查询的模板层次结构的准确表示。

模板解析

如果您想回答的问题是

WordPress将为这个查询尝试找到哪些模板?

可以使用Hierarchy::templates()方法简单地回答

// we will target the main query
global $wp_query; 

$hierarchy = new Brain\Hierarchy\Hierarchy();

var_export($hierarchy->templates($wp_query));

假设与上述相同的查询,输出将是

array(
  'category-foo',
  'category-123',
  'category',
  'archive',
  'paged',
  'index',
);

这是WordPress将搜索的模板列表,按照WordPress将使用的相同顺序。

模板解析示例

对于此示例,我将假设一个主题将模板文件存储在/templates子文件夹中,并使用.phtml作为文件扩展名。

根据模板层次结构加载这些模板所需的所有代码如下

add_action('template_redirect', function() {

    $templates = (new Brain\Hierarchy\Hierarchy())->templates();
    
    foreach($templates as $template) {
      $path = get_theme_file_path("/templates/{$template}.phtml");
      if (file_exists($path)) {
         require $path;
         exit();
      }
    }
});

上述示例有效,只是展示了您可以使用此库做什么的示例。

但是,为了加载模板,此库提供了一个特定的类:QueryTemplate

介绍 QueryTemplate

QueryTemplate类使用Hierarchy类来获取要搜索的模板列表,然后查找这些模板并加载第一个找到的模板。

示例

add_action('template_redirect', function(): void {

    global $wp_query;
    
    $queryTemplate = new \Brain\Hierarchy\QueryTemplate();
    echo $queryTemplate->loadTemplate($wp_query);
    
    exit();
    
});

上述代码确实执行了WordPress所做的事情:在主题文件夹和在父主题文件夹中(如果当前主题是子主题)搜索合适的模板,然后加载第一个找到的模板并将其内容打印到页面上。

请注意,模板内容是通过QueryTemplate::loadTemplate() 返回的,所以需要使用echo来实际显示页面内容。

但是,这只是一种默认行为,并且可以进行自定义。

模板查找器

默认情况下,QueryTemplate 类在主题(以及如果有,父主题)文件夹中搜索模板,就像WordPress所做的那样。

然而,可以使用不同的“模板查找”类来实现不同的功能。

所有模板查找类都必须实现 Brain\Hierarchy\Finder\TemplateFinder 接口。

库中包含一些实现该接口的类,当然,也可以编写自定义的一个。

Finder\ByFolders

Brain\Hierarchy\Finder\ByFolders 可以用来在某个 任意 文件夹中搜索模板,而不是主题和父主题文件夹。

示例

add_action('template_redirect', function(): void {

    $finder = new \Brain\Hierarchy\Finder\ByFolders([
       __DIR__,
       get_stylesheet_directory(),
       get_template_directory(),
    ]);
    
    $queryTemplate = new \Brain\Hierarchy\QueryTemplate($finder);  
      
    echo $queryTemplate->loadTemplate();
    exit();
    
});

上面的代码片段将在 当前文件夹 中搜索模板,如果没有找到,它们将在主题和父主题文件夹中搜索。

自定义文件扩展名

Finder\ByFolders 类默认搜索具有 .php 扩展名的文件,但可以通过将它们作为第二个构造函数参数(字符串或字符串数组)传递来使用不同的文件扩展名。

// This will look for *.phtml files.
$phtml_finder = new \Brain\Hierarchy\Finder\ByFolders(
    [get_stylesheet_directory(), get_template_directory()],
    'phtml'
);

// This will look for Twig files first, and fall back to standard PHP files if
// no matching Twig file was found.
$twig_finder = new \Brain\Hierarchy\Finder\ByFolders(
    [get_stylesheet_directory(), get_template_directory()],
    'twig',
    'php'
);

Finder\BySubfolder

这个模板查找类与 Brain\Hierarchy\Finder\ByFolders 非常相似,但它在一个特定的主题(和父主题)子文件夹中查找模板,并使用主题(和父主题)文件夹作为后备。

add_action('template_redirect', function(): void {

    $finder = new \Brain\Hierarchy\Finder\BySubfolder('templates');

    $queryTemplate = new \Brain\Hierarchy\QueryTemplate($finder);

    echo $queryTemplate->loadTemplate();
    exit();
} );

使用上面的代码,模板按以下顺序搜索:

  • /path/to/wp-content/child-theme/templates/
  • /path/to/wp-content/parent-theme/templates/
  • /path/to/wp-content/child-theme/
  • /path/to/wp-content/parent-theme/

Finder\BySubfolder 类与 Finder\ByFolders 类一样,接受(可变数量的)自定义文件扩展名作为第二个构造函数参数。

Finder\Localized

这个查找类与另一个查找类结合使用,允许根据当前区域加载模板。

add_action('template_redirect', function(): void {

    $foldersFinder = new \Brain\Hierarchy\Finder\ByFolders();

    $finder = new \Brain\Hierarchy\Finder\Localized($foldersFinder);

    $queryTemplate = new \Brain\Hierarchy\QueryTemplate($finder);

    echo $queryTemplate->loadTemplate();
    exit();
} );

假设当前区域是 it_IT,使用上面的代码,模板按以下顺序搜索:

  • /path/to/wp-content/child-theme/it_IT/
  • /path/to/wp-content/parent-theme/it_IT/
  • /path/to/wp-content/child-theme/it/
  • /path/to/wp-content/parent-theme/it/
  • /path/to/wp-content/child-theme/
  • /path/to/wp-content/parent-theme/

Finder\SymfonyFinderAdapter

这个类允许使用 Symfony Finder Component 来查找模板。

add_action('template_redirect', function() {

    $symfonyFinder = new \Symfony\Component\Finder\Finder();
    $symfonyFinder = $symfonyFinder->files()->in(__DIR__);

    $finder = new \Brain\Hierarchy\Finder\SymfonyFinderAdapter($symfonyFinder);

    $queryTemplate = new \Brain\Hierarchy\QueryTemplate($finder);

    echo $queryTemplate->loadTemplate();
    exit();
} );

Finder\ByCallback

这个类可以用来轻松地将第三方不同加载器与 QueryTemplate 类集成。

实际上,需要提供一个任意回调,该回调将被调用以查找模板。

回调将接收到不带文件扩展名的模板名称,例如 index,并且如果找到模板,必须返回模板的完整路径,如果没有找到模板,则返回空字符串。

示例

add_action('template_redirect', function(): void {

    $callback = fn(string $tpl): string => realpath(__DIR__ . "{$template}.php") ?: '';

    $finder = new \Brain\Hierarchy\Finder\ByCallback($callback);

    $queryTemplate = new \Brain\Hierarchy\QueryTemplate($finder);

    echo $queryTemplate->loadTemplate();
    exit();
} );

模板加载的核心过滤器

当WordPress在 template-loader.php 中搜索模板时,它将触发不同形式的过滤器,形式为 {$type}_template;例如 'single_template''page_template' 等等。

此外,找到的模板将经过 'template_include' 过滤器。

默认情况下,QueryTemplate::loadTemplate() 应用相同的过滤器,以最大限度地与核心行为兼容。

这无论使用哪个模板查找类都会发生。

然而,通过将 false 作为方法的第二个参数传递,将停止应用这些核心过滤器。

介绍模板加载器

在找到任何查找类的模板之后,QueryTemplate 必须将其“加载”。

默认情况下,加载只是由 ob_start() / ob_get_clean() 包裹的 require,这样模板内容就可以原样返回。

然而,也可以以某种方式 处理 模板,例如,使用一个 模板引擎

自定义模板加载器必须实现 Brain\Hierarchy\Loader\Loader 接口,该接口只有一个方法:load(),该方法接收模板的完整路径并必须 返回 模板内容。

模板加载器可以作为第二个构造函数参数传递给 QueryTemplate

Loader\FileRequire

这是库中包含的加载器类,它提供了默认行为。

聚合加载器

聚合加载器使用不同的“内部”加载器来加载模板。

聚合加载器必须实现接口 Brain\Hierarchy\Loader\Aggregate,该接口有两个方法。

  • addLoader(Loader\TemplateLoader $loader, callable $predicate)
  • addLoaderFactory(callable $loaderFactory, callable $predicate)

第一个用于添加模板加载器实例。第二个用于添加一个工厂,一旦调用,将返回一个模板加载器实例。

这两个方法接受第二个参数一个“断言”:一个回调函数,将接收要加载的模板文件路径,并返回一个布尔值。

当断言返回true时,相关加载器用于加载模板。

Loader\Cascade

Loader\Cascade是一个简单的聚合加载器实现,其中断言的评估顺序与它们添加的顺序相同(先进先出)。

Loader\ExtensionMap

Loader\ExtensionMap是Hierarchy附带的其他聚合加载器实现。

它用于根据模板文件扩展名加载不同的加载器。

它需要一个传递给构造函数的“扩展名到加载器”的“映射”。

映射键是模板文件扩展名,值是使用的加载器。

加载器可以传递为

  • 模板加载器实例
  • 模板加载器完全限定类名
  • 工厂回调,一旦调用就返回模板加载器实例

可以使用由多个文件扩展名通过竖线|分隔的字符串作为映射键使用同一个加载器为多个文件扩展名服务。

示例

$loader = new Loader\ExtensionMap([
    'php|phtml' => new Loader\FileRequire(),
    'mustache'  => fn() => new MyMustacheAdapter(new Mustache_Engine),
    'md' => MyMarkdownRenderer::class
]);

QueryTemplate使用示例:加载和渲染Mustache模板

以下将展示所有必要的代码,以根据WordPress模板层次结构查找和渲染mustache模板。

namespace My\Theme;

use Brain\Hierarchy\{Finder, Loader, QueryTemplate};

class MustacheTemplateLoader implements Loader\Loader
{
   private $engine;

   public function __construct(\Mustache_Engine $engine)
   {
      $this->engine = $engine;
   }

   public function load(string $templatePath): string
   {
        // let's use a filter to build some context for the template
        $data = apply_filters('my_theme_data', ['query' => $GLOBALS['wp_query'], $templatePath);

        return $this->engine->render(file_get_contents($templatePath), $data);
   }
}

add_action('template_redirect', function() {
    if (!QueryTemplate::mainQueryTemplateAllowed()) {
        return;
    }

    $queryTemplate = new QueryTemplate(
        // will look for "*.mustache" templates in theme's "/templates" subfolder
        new Finder\BySubfolder('templates', 'mustache'),
        // the loader class defined above
        new MustacheTemplateLoader(new \Mustache_Engine())
    );

    // 3rd argument of loadTemplate() is passed by reference, and set to true if template is found
    $content = $queryTemplate->loadTemplate(null, true, $found);
    // if template was found, let's output it and exit, otherwise WordPress will continue its work
    $found and die($content);
});

要求

Hierarchy需要PHP 7.1.3+和Composer已安装。

安装

最佳通过Composer提供,可在Packagist上找到,名称为brain/hierarchy

从2.*版本迁移

版本3的逻辑没有变化,但现在所有类都使用类型声明,其中一些已经被重命名。

基于Hierarchy并实现其加载器/查找接口的库将需要对类进行重命名并添加类型声明。

FileExtensionPredicate的签名略有变化。

仅使用Hierarchy类的库应该无需任何更改即可工作,即使getHierarchy()getTemplates()方法现在分别被hierarchy()templates()替代,但旧方法在任何3.*版本中都不会被删除。

许可证

Hierarchy是在MIT许可下发布的。