bluedigit/wp-league-plates-integration

WordPress integration for League Plates.

Maintainers

Package info

github.com/blueDigit/wp-league-plates-integration

pkg:composer/bluedigit/wp-league-plates-integration

Transparency log

Statistics

Installs: 65

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.0 2026-08-23 04:12 UTC

This package is auto-updated.

Last update: 2026-08-23 04:15:51 UTC


README

WordPress integration for League Plates.

This library provides a small, framework-independent integration layer between WordPress and League Plates. It allows League Plates templates to be resolved from a WordPress theme or from a plugin, with theme templates taking precedence over plugin templates.

The integration is designed for WordPress plugins and Composer-based projects that want to use League Plates as their template engine while still following the conventions of the WordPress template hierarchy.

Requirements

Installation

Install the package with Composer:

composer require bluedigit/wp-league-plates-integration

The package uses PSR-4 autoloading:

{
    "autoload": {
        "psr-4": {
            "BlueDigit\\WordPress\\LeaguePlatesIntegration\\": "src/"
        }
    }
}

After installing the package, Composer's autoloader provides all required classes automatically.

Basic Usage

The integration is built around an existing League Plates Engine instance.

Create a configuration and pass it together with the engine to EngineConfigurator::configure():

use BlueDigit\WordPress\LeaguePlatesIntegration\Configuration\Configuration;
use BlueDigit\WordPress\LeaguePlatesIntegration\EngineConfigurator;
use League\Plates\Engine;

$configuration = new Configuration(
    pluginPath: __DIR__,
);

$engine = new Engine();

EngineConfigurator::configure(
    $engine,
    $configuration,
);

Templates can then be rendered through League Plates as usual:

echo $engine->render('calendar/month', [
    'month' => $month,
]);

With the default configuration, the integration looks for the template at:

<plugin>/templates/calendar/month.php

Template Resolution

The central purpose of this package is to integrate WordPress template locations with the League Plates template resolver.

The default resolution order is:

  1. Active WordPress theme
  2. Plugin template directory

This means that a theme can override a template supplied by a plugin without modifying the plugin itself.

For example, suppose a plugin provides:

my-plugin/
├── src/
└── templates/
    └── calendar/
        └── month.php

The template can be rendered as:

$engine->render('calendar/month');

The integration first checks the configured theme directory.

If the theme does not provide the template, the plugin's template directory is checked:

my-plugin/templates/calendar/month.php

If neither location contains the requested template, League Plates receives a TemplateNotFound exception.

Theme Overrides

Theme overrides are the primary WordPress-specific feature of the integration.

A plugin can ship its default templates while allowing the active theme to customize their presentation.

For example, the plugin may contain:

templates/
└── calendar/
    └── month.php

The plugin can configure the integration to use a theme subdirectory:

$configuration = new Configuration(
    pluginPath: __DIR__,
    themeDirectory: 'my-plugin',
);

The resolver then checks:

wp-content/themes/<active-theme>/my-plugin/calendar/month.php

before falling back to:

<plugin>/templates/calendar/month.php

This keeps plugin templates independent from the active theme while still providing a clean customization mechanism.

Why a Theme Subdirectory?

Using a dedicated directory prevents plugin templates from colliding with unrelated theme files.

For example:

my-plugin/
└── calendar/
    └── month.php

would be ambiguous in a large theme.

A dedicated directory such as:

my-plugin/
└── calendar/
    └── month.php

makes the ownership and purpose of the templates explicit.

The exact directory name is configurable through Configuration.

Child Theme Support

Theme resolution uses WordPress's locate_template() function.

This means that the normal WordPress theme lookup behavior is retained, including child-theme overrides.

For example:

wp-content/
└── themes/
    ├── parent-theme/
    │   └── my-plugin/
    │       └── calendar/
    │           └── month.php
    └── child-theme/
        └── my-plugin/
            └── calendar/
                └── month.php

If child-theme is active, WordPress can resolve the child theme's template before the corresponding parent-theme template.

This allows the integration to participate naturally in WordPress's existing theme override mechanism.

Plugin Templates

The plugin template directory defaults to:

templates/

It can be changed through Configuration:

$configuration = new Configuration(
    pluginPath: __DIR__,
    templateDirectory: 'views',
);

The plugin template:

views/calendar/month.php

can then be rendered as:

$engine->render('calendar/month');

The .php extension is added automatically during template resolution.

Configuration

The Configuration class contains the paths used by the resolvers.

use BlueDigit\WordPress\LeaguePlatesIntegration\Configuration\Configuration;

$configuration = new Configuration(
    pluginPath: __DIR__,
    templateDirectory: 'templates',
    themeDirectory: 'my-plugin',
);

pluginPath

The absolute path to the plugin.

new Configuration(
    pluginPath: __DIR__,
);

This value is used as the base path for plugin templates.

The path is normalized by removing trailing / and \ characters.

templateDirectory

The directory containing the plugin's templates.

Default:

templates

Example:

new Configuration(
    pluginPath: __DIR__,
    templateDirectory: 'views',
);

The resulting plugin template path is:

<plugin>/views/

Leading and trailing path separators are removed automatically.

themeDirectory

The directory inside the active theme in which plugin overrides are expected.

Default:

''

An empty value disables theme template resolution.

Example:

new Configuration(
    pluginPath: __DIR__,
    themeDirectory: 'my-plugin',
);

The resolver will use WordPress's locate_template() to look for:

my-plugin/<template>.php

inside the active theme and its parent theme.

Using Only Plugin Templates

If theme overrides are not required, simply omit themeDirectory:

$configuration = new Configuration(
    pluginPath: __DIR__,
);

In this case the resolver effectively behaves as:

Plugin template
    ↓
TemplateNotFound

No theme lookup is performed.

Using Theme Overrides

To enable theme overrides:

$configuration = new Configuration(
    pluginPath: __DIR__,
    themeDirectory: 'my-plugin',
);

The lookup order becomes:

Theme
  ↓
Plugin
  ↓
TemplateNotFound

This is useful for WordPress plugins that provide a default presentation but want the active theme to have complete control over the markup.

Resolver Architecture

Template resolution is intentionally separated from the League Plates engine.

The package defines a small Resolver interface:

interface Resolver
{
    public function resolve(string $template): ?string;
}

A resolver receives a League Plates template name and either returns the corresponding file path or null if the template cannot be resolved.

This makes the resolution mechanism extensible without coupling the entire integration to WordPress.

PluginResolver

PluginResolver resolves templates relative to the configured plugin template directory.

For:

$engine->render('calendar/month');

it constructs a path equivalent to:

<plugin>/templates/calendar/month.php

The resolver returns the path only when the file exists.

ThemeResolver

ThemeResolver resolves templates through WordPress:

locate_template()

This is important because it delegates theme and child-theme handling to WordPress instead of implementing a separate theme lookup mechanism.

If no themeDirectory is configured, the resolver returns null immediately.

CompositeResolver

CompositeResolver combines multiple resolvers into a single resolver.

Resolvers are checked in the order in which they are supplied:

$resolver = new CompositeResolver(
    $firstResolver,
    $secondResolver,
);

The first resolver returning a non-null path wins.

This provides a simple and flexible fallback mechanism.

The default EngineConfigurator creates the following chain:

ThemeResolver
      ↓
PluginResolver
      ↓
null

League Plates Integration

League Plates allows applications to customize how template names are resolved.

This library implements League Plates' ResolveTemplatePath interface through IntegrationResolveTemplatePath.

The integration is installed on the engine with:

$engine->setResolveTemplatePath(
    new IntegrationResolveTemplatePath($resolver),
);

The resulting flow is:

$engine->render('calendar/month')
              │
              ▼
   League Plates template name
              │
              ▼
 IntegrationResolveTemplatePath
              │
              ▼
       CompositeResolver
          │         │
          ▼         ▼
        Theme     Plugin
        Resolver  Resolver
          │         │
          └────┬────┘
               ▼
          template.php

If a resolver cannot find the template, it returns null.

If none of the configured resolvers can resolve the template, IntegrationResolveTemplatePath throws League Plates' TemplateNotFound exception.

Custom Resolvers

The Resolver abstraction also allows applications to add their own template locations.

For example:

final readonly class CustomResolver implements Resolver
{
    public function resolve(string $template): ?string
    {
        $path = '/some/custom/path/' . $template . '.php';

        return is_file($path) ? $path : null;
    }
}

It can then be combined with the existing resolvers:

$resolver = new CompositeResolver(
    new CustomResolver(),
    new ThemeResolver($configuration),
    new PluginResolver($configuration),
);

This makes it possible to add additional template sources without changing the integration itself.

Custom Engine Configuration

EngineConfigurator provides the standard WordPress configuration, but it is not required if a project needs a custom resolver chain.

For example:

use BlueDigit\WordPress\LeaguePlatesIntegration\Resolver\CompositeResolver;
use BlueDigit\WordPress\LeaguePlatesIntegration\Resolver\IntegrationResolveTemplatePath;
use BlueDigit\WordPress\LeaguePlatesIntegration\Resolver\PluginResolver;
use BlueDigit\WordPress\LeaguePlatesIntegration\Resolver\ThemeResolver;

$resolver = new CompositeResolver(
    new ThemeResolver($configuration),
    new PluginResolver($configuration),
);

$engine->setResolveTemplatePath(
    new IntegrationResolveTemplatePath($resolver),
);

EngineConfigurator is therefore a convenience for the standard configuration rather than a requirement of the underlying integration.

Recommended Plugin Structure

A typical Composer-based WordPress plugin can use the following structure:

my-plugin/
├── composer.json
├── my-plugin.php
├── src/
│   └── ...
├── templates/
│   ├── calendar/
│   │   ├── month.php
│   │   └── day.php
│   └── components/
│       └── event.php
└── vendor/

The plugin bootstrap can configure Plates:

use BlueDigit\WordPress\LeaguePlatesIntegration\Configuration\Configuration;
use BlueDigit\WordPress\LeaguePlatesIntegration\EngineConfigurator;
use League\Plates\Engine;

$engine = new Engine();

EngineConfigurator::configure(
    $engine,
    new Configuration(
        pluginPath: __DIR__,
        themeDirectory: 'my-plugin',
    ),
);

The theme can then override individual templates:

theme/
└── my-plugin/
    └── calendar/
        └── month.php

Templates that are not overridden automatically fall back to the plugin:

plugin/
└── templates/
    └── calendar/
        └── month.php

Template Naming

Template names use the same logical structure as the underlying file system.

For example:

templates/
├── calendar/
│   ├── month.php
│   └── week.php
└── components/
    └── event.php

can be referenced as:

$engine->render('calendar/month');
$engine->render('calendar/week');
$engine->render('components/event');

The .php extension should not be included in the template name.

WordPress Context

This package intentionally does not provide a WordPress plugin framework, service container, hooks, or rendering abstraction beyond template resolution.

It focuses on one responsibility:

Make League Plates understand WordPress theme and plugin template locations.

This keeps the package small and allows it to be used with different WordPress plugin architectures.

For example, it can be used with:

  • a custom plugin architecture
  • dependency injection containers
  • Symfony components
  • League Container
  • custom service providers
  • traditional WordPress plugin bootstrapping

Error Handling

If a template cannot be resolved, the integration throws League Plates' TemplateNotFound exception.

For example:

try {
    echo $engine->render('calendar/missing');
} catch (\League\Plates\Exception\TemplateNotFound $exception) {
    // Handle missing template.
}

The exception contains the requested template name and an explanatory message:

Unable to resolve template "calendar/missing".

Design Goals

The library intentionally follows a few principles.

Small API

The public API consists primarily of:

  • Configuration
  • EngineConfigurator
  • Resolver
  • CompositeResolver
  • PluginResolver
  • ThemeResolver
  • IntegrationResolveTemplatePath

There is no framework-specific application layer.

WordPress-Native Theme Resolution

Theme templates are resolved using WordPress's own locate_template() mechanism.

This means child themes and WordPress's normal template precedence are handled by WordPress itself.

Explicit Fallback Order

The default behavior is predictable:

Theme → Plugin

A theme can override a plugin template, while the plugin always provides a fallback implementation.

Extensibility

The Resolver interface allows applications to introduce additional template sources without modifying the package.

Development

Clone the repository and install dependencies:

git clone https://github.com/blueDigit/wp-league-plates-integration.git
cd wp-league-plates-integration
composer install

Static Analysis

Run PHPStan with:

composer analyse

Coding Standards

Run PHP_CodeSniffer with:

composer lint

The project uses PHP_CodeSniffer together with Slevomat Coding Standard.

License

This project is licensed under the MIT License.

See LICENSE for the complete license text.

Author

blueDigit LP

Email: info@bluedigit.ca

Links

Changelog

See CHANGELOG.md for release history.