Skip to content

Commit

Permalink
Themes Support #234
Browse files Browse the repository at this point in the history
- Added support for themes
- Added support for ResolveTemplatePath
  as a way for more complex path resolutions
  for templates

Signed-off-by: RJ Garcia <ragboyjr@icloud.com>
  • Loading branch information
ragboyjr committed Jan 9, 2021
1 parent 6d3ee31 commit 6714830
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -42,7 +42,7 @@ Full documentation can be found at [platesphp.com](https://platesphp.com/).
## Testing

```bash
phpunit
composer test
```

## Contributing
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -46,7 +46,7 @@
}
},
"scripts": {
"test": "phpunit --testdox",
"test": "phpunit --testdox --colors=always",
"docs": "hugo -s doc server"
}
}
68 changes: 68 additions & 0 deletions doc/content/engine/themes.md
@@ -0,0 +1,68 @@
+++
title = "Themes"
linkTitle = "Engine Themes"
[menu.main]
parent = "engine"
weight = 6
[menu.main.params]
badge = "v3.5"
+++

Themes provide an alternative to template path resolution that allow for a holistic approach to template overrides and fallbacks.

## Usage

Given an engine configuration like:

```php
use League\Plates\{Engine, Template\Theme};

$plates = Engine::fromTheme(Theme::hierarchy([
Theme::new('/templates/main', 'Main'), // parent
Theme::new('/templates/user', 'User'), // child
Theme::new('/templates/seasonal', 'Seasonal'), // child2
]));
```

And a file structure like:

```
templates/
main/
layout.php
home.php
header.php
user/
layout.php
header.php
seasonal/
header.php
```

The following looks ups, *regardless of where they are called from*, would resolve to the following files:

```php
$templates->render('home'); // templates/main/home.php
$templates->render('layout'); // templates/user/layout.php
$templates->render('header'); // templates/seasonal/header.php
```

All paths are resolved from the last child to the first parent allowing a hierarchy of overrides.

## Differences from Directory and Folders

This logic is used **instead of** the directories and folders feature since they are distinct in nature, and combining the logic isn't obvious on how the features should stack.

Creating an engine with one theme is functionally equivalent to using just a directory with no folders.

The fallback functionality is a bit different however since with folders, it's *opt in*, you need to prefix the template name with the folder name. With themes, all template names implicitly will be resolved and fallback according to the hierarchy setup.

## Additional Customization

This functionality is powered by the `League\Plates\Template\ResolveTemplatePath` interface. If you'd prefer a more complex or specific path resolution, you can just implement your own and assign it to the engine instance with:

```php
$plates = Engine::withResolveTemplatePath(new MyCustomResolveTemplatePath());
```

The resolve template path should always resolve a string that represents a verified path on the filesystem or throw a TemplateNotFound exception.
7 changes: 6 additions & 1 deletion doc/layouts/_default/baseof.html
Expand Up @@ -71,7 +71,12 @@ <h2>{{ .Name }}</h2>
<ul>
{{ range .Children }}
<li class="{{ if $currentPage.IsMenuCurrent "main" . }}selected{{ end }}">
<a href="{{ .URL }}">{{ .Page.Title }}</a>
<a href="{{ .URL }}">
{{ .Page.Title }}
{{ with .Params.badge }}
<sup class="menu-badge">{{ . }}</sup>
{{ end }}
</a>
</li>
{{ end }}
</ul>
Expand Down
4 changes: 4 additions & 0 deletions doc/static/css/custom.css
Expand Up @@ -21,3 +21,7 @@
.version-select {
margin: 8px 25px 0px 45px;
}

.menu-badge {
color: #ff4143;
}
24 changes: 24 additions & 0 deletions src/Engine.php
Expand Up @@ -10,7 +10,9 @@
use League\Plates\Template\Func;
use League\Plates\Template\Functions;
use League\Plates\Template\Name;
use League\Plates\Template\ResolveTemplatePath;
use League\Plates\Template\Template;
use League\Plates\Template\Theme;

/**
* Template API and environment settings storage.
Expand Down Expand Up @@ -47,6 +49,9 @@ class Engine
*/
protected $data;

/** @var ResolveTemplatePath */
private $resolveTemplatePath;

/**
* Create new Engine instance.
* @param string $directory
Expand All @@ -59,6 +64,25 @@ public function __construct($directory = null, $fileExtension = 'php')
$this->folders = new Folders();
$this->functions = new Functions();
$this->data = new Data();
$this->resolveTemplatePath = new ResolveTemplatePath\NameAndFolderResolveTemplatePath();
}

public static function fromTheme(Theme $theme, string $fileExtension = 'php'): self {
$engine = new self(null, $fileExtension);
$engine->setResolveTemplatePath(new ResolveTemplatePath\ThemeResolveTemplatePath($theme));
return $engine;
}

public function setResolveTemplatePath(ResolveTemplatePath $resolveTemplatePath)
{
$this->resolveTemplatePath = $resolveTemplatePath;

return $this;
}

public function getResolveTemplatePath(): ResolveTemplatePath
{
return $this->resolveTemplatePath;
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/Exception/TemplateNotFound.php
@@ -0,0 +1,23 @@
<?php

namespace League\Plates\Exception;

final class TemplateNotFound extends \LogicException
{
private $template;
private $paths;

public function __construct(string $template, array $paths, string $message) {
$this->template = $template;
$this->paths = $paths;
parent::__construct($message);
}

public function template(): string {
return $this->template;
}

public function paths(): array {
return $this->paths;
}
}
13 changes: 13 additions & 0 deletions src/Template/ResolveTemplatePath.php
@@ -0,0 +1,13 @@
<?php

namespace League\Plates\Template;

use League\Plates\Exception\TemplateNotFound;

interface ResolveTemplatePath
{
/**
* @throws TemplateNotFound if the template could not be properly resolved to a file path
*/
public function __invoke(Name $name): string;
}
@@ -0,0 +1,20 @@
<?php

namespace League\Plates\Template\ResolveTemplatePath;

use League\Plates\Exception\TemplateNotFound;
use League\Plates\Template\Name;
use League\Plates\Template\ResolveTemplatePath;

/** Resolves the path from the logic in the Name class which resolves via folder lookup, and then the default directory */
final class NameAndFolderResolveTemplatePath implements ResolveTemplatePath
{
public function __invoke(Name $name): string {
$path = $name->getPath();
if (is_file($path)) {
return $path;
}

throw new TemplateNotFound($name->getName(), [$name->getPath()], 'The template "' . $name->getName() . '" could not be found at "' . $name->getPath() . '".');
}
}
41 changes: 41 additions & 0 deletions src/Template/ResolveTemplatePath/ThemeResolveTemplatePath.php
@@ -0,0 +1,41 @@
<?php

namespace League\Plates\Template\ResolveTemplatePath;

use League\Plates\Exception\TemplateNotFound;
use League\Plates\Template\Name;
use League\Plates\Template\ResolveTemplatePath;
use League\Plates\Template\Theme;

final class ThemeResolveTemplatePath implements ResolveTemplatePath
{
private $theme;

public function __construct(Theme $theme) {
$this->theme = $theme;
}

public function __invoke(Name $name): string {
$searchedPaths = [];
foreach ($this->theme->listThemeHierarchy() as $theme) {
$path = $theme->dir() . '/' . $name->getName() . '.' . $name->getEngine()->getFileExtension();
if (is_file($path)) {
return $path;
}
$searchedPaths[] = [$theme->name(), $path];
}

throw new TemplateNotFound(
$name->getName(),
array_map(function(array $tup) {
return $tup[1];
}, $searchedPaths),
sprintf('The template "%s" was not found in the following themes: %s',
$name->getName(),
implode(', ', array_map(function(array $tup) {
return implode(':', $tup);
}, $searchedPaths))
)
);
}
}
28 changes: 14 additions & 14 deletions src/Template/Template.php
Expand Up @@ -4,6 +4,7 @@

use Exception;
use League\Plates\Engine;
use League\Plates\Exception\TemplateNotFound;
use LogicException;
use Throwable;

Expand Down Expand Up @@ -126,7 +127,12 @@ public function data(array $data = null)
*/
public function exists()
{
return $this->name->doesPathExist();
try {
($this->engine->getResolveTemplatePath())($this->name);
return true;
} catch (TemplateNotFound $e) {
return false;
}
}

/**
Expand All @@ -135,7 +141,11 @@ public function exists()
*/
public function path()
{
return $this->name->getPath();
try {
return ($this->engine->getResolveTemplatePath())($this->name);
} catch (TemplateNotFound $e) {
return $e->paths()[0];
}
}

/**
Expand All @@ -151,17 +161,13 @@ public function render(array $data = array())
unset($data);
extract($this->data);

if (!$this->exists()) {
throw new LogicException(
'The template "' . $this->name->getName() . '" could not be found at "' . $this->path() . '".'
);
}
$path = ($this->engine->getResolveTemplatePath())($this->name);

try {
$level = ob_get_level();
ob_start();

include $this->path();
include $path;

$content = ob_get_clean();

Expand All @@ -177,12 +183,6 @@ public function render(array $data = array())
ob_end_clean();
}

throw $e;
} catch (Exception $e) {
while (ob_get_level() > $level) {
ob_end_clean();
}

throw $e;
}
}
Expand Down

0 comments on commit 6714830

Please sign in to comment.