bluedigit / wp-league-plates-integration
WordPress integration for League Plates.
Package info
github.com/blueDigit/wp-league-plates-integration
pkg:composer/bluedigit/wp-league-plates-integration
Requires
- php: ^8.3
- league/plates: ^3.6
Requires (Dev)
- php-stubs/wordpress-stubs: ^7.0
- phpstan/phpstan: ^2.2
- slevomat/coding-standard: ^8.31
- squizlabs/php_codesniffer: ^4.0
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
- PHP 8.3 or newer
- WordPress
- League Plates 3.6 or newer
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:
- Active WordPress theme
- 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:
ConfigurationEngineConfiguratorResolverCompositeResolverPluginResolverThemeResolverIntegrationResolveTemplatePath
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.