Added Twig template renderer, closes #338
parent
a1bc763a16
commit
bb3d16d273
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Renderer;
|
||||||
|
|
||||||
|
use Twig_Environment as Twig;
|
||||||
|
use Twig_Error_Loader as LoaderError;
|
||||||
|
use Twig_Error_Runtime as RuntimeError;
|
||||||
|
use Twig_Error_Syntax as SyntaxError;
|
||||||
|
|
||||||
|
class TwigEngine implements EngineInterface
|
||||||
|
{
|
||||||
|
/** @var Twig */
|
||||||
|
protected $twig;
|
||||||
|
|
||||||
|
public function __construct(Twig $twig)
|
||||||
|
{
|
||||||
|
$this->twig = $twig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a twig template
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param array $data
|
||||||
|
* @return string
|
||||||
|
* @throws LoaderError|RuntimeError|SyntaxError
|
||||||
|
*/
|
||||||
|
public function get($path, $data = [])
|
||||||
|
{
|
||||||
|
return $this->twig->render($path, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function canRender($path)
|
||||||
|
{
|
||||||
|
return $this->twig->getLoader()->exists($path);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Renderer;
|
||||||
|
|
||||||
|
use Twig_Error_Loader;
|
||||||
|
use Twig_Loader_Filesystem as FilesystemLoader;
|
||||||
|
|
||||||
|
class TwigLoader extends FilesystemLoader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @param bool $throw
|
||||||
|
* @return false|string
|
||||||
|
* @throws Twig_Error_Loader
|
||||||
|
*/
|
||||||
|
public function findTemplate($name, $throw = true)
|
||||||
|
{
|
||||||
|
$extension = '.twig';
|
||||||
|
$extensionLength = strlen($extension);
|
||||||
|
if (substr($name, -$extensionLength, $extensionLength) !== $extension) {
|
||||||
|
$name .= $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::findTemplate($name, $throw);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Renderer;
|
||||||
|
|
||||||
|
use Engelsystem\Container\ServiceProvider;
|
||||||
|
use Twig_Environment as Twig;
|
||||||
|
use Twig_LoaderInterface as TwigLoaderInterface;
|
||||||
|
|
||||||
|
class TwigServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->registerTwigEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function registerTwigEngine()
|
||||||
|
{
|
||||||
|
$viewsPath = $this->app->get('path.views');
|
||||||
|
|
||||||
|
$twigLoader = $this->app->make(TwigLoader::class, ['paths' => $viewsPath]);
|
||||||
|
$this->app->instance(TwigLoader::class, $twigLoader);
|
||||||
|
$this->app->instance(TwigLoaderInterface::class, $twigLoader);
|
||||||
|
|
||||||
|
$twig = $this->app->make(Twig::class);
|
||||||
|
$this->app->instance(Twig::class, $twig);
|
||||||
|
|
||||||
|
$twigEngine = $this->app->make(TwigEngine::class);
|
||||||
|
$this->app->instance('renderer.twigEngine', $twigEngine);
|
||||||
|
$this->app->tag('renderer.twigEngine', ['renderer.engine']);
|
||||||
|
}
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>%title% - Engelsystem</title>
|
|
||||||
<meta charset="UTF-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/theme%theme%.css"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="vendor/icomoon/style.css"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="vendor/bootstrap-datepicker-1.7.1/css/bootstrap-datepicker3.min.css"/>
|
|
||||||
<script type="text/javascript" src="vendor/jquery-2.1.1.min.js"></script>
|
|
||||||
<script type="text/javascript" src="vendor/jquery-ui.min.js"></script>
|
|
||||||
%atom_link%
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="navbar navbar-default navbar-fixed-top">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<button type="button" class="navbar-toggle collapsed"
|
|
||||||
data-toggle="collapse" data-target="#navbar-collapse-1">
|
|
||||||
<span class="sr-only">Toggle navigation</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</button>
|
|
||||||
<a class="navbar-brand" href="%start_page_url%">
|
|
||||||
<span class="icon-icon_angel"></span> <strong class="visible-lg-inline">ENGELSYSTEM</strong>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="collapse navbar-collapse" id="navbar-collapse-1">%menu% %header_toolbar%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">%content%</div>
|
|
||||||
<div class="row" id="footer">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<hr/>
|
|
||||||
<div class="text-center footer" style="margin-bottom: 10px;">
|
|
||||||
%event_info%
|
|
||||||
<a href="%faq_url%">FAQ</a>
|
|
||||||
· <a href="%contact_email%"><span class="glyphicon glyphicon-envelope"></span> Contact</a>
|
|
||||||
· <a href="https://github.com/engelsystem/engelsystem/issues">Bugs / Features</a>
|
|
||||||
· <a href="https://github.com/engelsystem/engelsystem/">Development Platform</a>
|
|
||||||
· <a href="%credits_url%">Credits</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
|
|
||||||
<script type="text/javascript" src="vendor/bootstrap-datepicker-1.7.1/js/bootstrap-datepicker.min.js"></script>
|
|
||||||
<script type="text/javascript" src="vendor/bootstrap-datepicker-1.7.1/locales/bootstrap-datepicker.de.min.js"></script>
|
|
||||||
<script type="text/javascript" src="vendor/Chart.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/forms.js"></script>
|
|
||||||
<script type="text/javascript" src="vendor/moment-with-locales.min.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function () {
|
|
||||||
moment.locale("%locale%");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="js/moment-countdown.js"></script>
|
|
||||||
<script type="text/javascript" src="js/sticky-headers.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -0,0 +1,80 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
<title>{% block title %}{{ title }}{% endblock %} - Engelsystem</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/theme{{ theme }}.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="vendor/icomoon/style.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="vendor/bootstrap-datepicker-1.7.1/css/bootstrap-datepicker3.min.css"/>
|
||||||
|
<script type="text/javascript" src="vendor/jquery-2.1.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/jquery-ui.min.js"></script>
|
||||||
|
{{ atom_link|raw }}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="navbar navbar-default navbar-fixed-top">
|
||||||
|
{% block header %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed"
|
||||||
|
data-toggle="collapse" data-target="#navbar-collapse-1">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="{{ start_page_url }}">
|
||||||
|
<span class="icon-icon_angel"></span> <strong class="visible-lg-inline">ENGELSYSTEM</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
<div class="collapse navbar-collapse"
|
||||||
|
id="navbar-collapse-1">{{ menu|raw }} {{ header_toolbar|raw }}</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">{% block content %}{{ content|raw }}{% endblock %}</div>
|
||||||
|
<div class="row" id="footer">
|
||||||
|
{% block footer %}
|
||||||
|
<div class="col-md-12">
|
||||||
|
<hr/>
|
||||||
|
<div class="text-center footer" style="margin-bottom: 10px;">
|
||||||
|
{% block eventinfo %}
|
||||||
|
{{ event_info|raw }}
|
||||||
|
{% endblock %}
|
||||||
|
<a href="{{ faq_url }}">FAQ</a>
|
||||||
|
· <a href="{{ contact_email }}"><span class="glyphicon glyphicon-envelope"></span> Contact</a>
|
||||||
|
· <a href="https://github.com/engelsystem/engelsystem/issues">Bugs / Features</a>
|
||||||
|
· <a href="https://github.com/engelsystem/engelsystem/">Development Platform</a>
|
||||||
|
· <a href="{{ credits_url }}">Credits</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/bootstrap-datepicker-1.7.1/js/bootstrap-datepicker.min.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/bootstrap-datepicker-1.7.1/locales/bootstrap-datepicker.de.min.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/Chart.min.js"></script>
|
||||||
|
<script type="text/javascript" src="js/forms.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/moment-with-locales.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
moment.locale("{{ locale|escape('js') }}");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="js/moment-countdown.js"></script>
|
||||||
|
<script type="text/javascript" src="js/sticky-headers.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Renderer;
|
||||||
|
|
||||||
|
use Engelsystem\Renderer\TwigEngine;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Twig_Environment as Twig;
|
||||||
|
use Twig_LoaderInterface as LoaderInterface;
|
||||||
|
|
||||||
|
class TwigEngineTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\TwigEngine::__construct
|
||||||
|
* @covers \Engelsystem\Renderer\TwigEngine::get
|
||||||
|
*/
|
||||||
|
public function testGet()
|
||||||
|
{
|
||||||
|
/** @var Twig|MockObject $twig */
|
||||||
|
$twig = $this->createMock(Twig::class);
|
||||||
|
|
||||||
|
$path = 'foo.twig';
|
||||||
|
$data = ['lorem' => 'ipsum'];
|
||||||
|
|
||||||
|
$twig->expects($this->once())
|
||||||
|
->method('render')
|
||||||
|
->with($path, $data)
|
||||||
|
->willReturn('LoremIpsum!');
|
||||||
|
|
||||||
|
$engine = new TwigEngine($twig);
|
||||||
|
$return = $engine->get($path, $data);
|
||||||
|
$this->assertEquals('LoremIpsum!', $return);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\TwigEngine::canRender
|
||||||
|
*/
|
||||||
|
public function testCanRender()
|
||||||
|
{
|
||||||
|
/** @var Twig|MockObject $twig */
|
||||||
|
$twig = $this->createMock(Twig::class);
|
||||||
|
/** @var LoaderInterface|MockObject $loader */
|
||||||
|
$loader = $this->getMockForAbstractClass(LoaderInterface::class);
|
||||||
|
|
||||||
|
$path = 'foo.twig';
|
||||||
|
|
||||||
|
$twig->expects($this->once())
|
||||||
|
->method('getLoader')
|
||||||
|
->willReturn($loader);
|
||||||
|
$loader->expects($this->once())
|
||||||
|
->method('exists')
|
||||||
|
->with($path)
|
||||||
|
->willReturn(true);
|
||||||
|
|
||||||
|
$engine = new TwigEngine($twig);
|
||||||
|
$return = $engine->canRender($path);
|
||||||
|
$this->assertTrue($return);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Renderer;
|
||||||
|
|
||||||
|
use Engelsystem\Renderer\TwigLoader;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use ReflectionClass as Reflection;
|
||||||
|
|
||||||
|
class TwigLoaderTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\TwigLoader::findTemplate
|
||||||
|
*/
|
||||||
|
public function testFindTemplate()
|
||||||
|
{
|
||||||
|
$loader = new TwigLoader();
|
||||||
|
|
||||||
|
$reflection = new Reflection(get_class($loader));
|
||||||
|
$property = $reflection->getProperty('cache');
|
||||||
|
$property->setAccessible(true);
|
||||||
|
|
||||||
|
$realPath = __DIR__ . '/Stub/foo.twig';
|
||||||
|
$property->setValue($loader, ['Stub/foo.twig' => $realPath]);
|
||||||
|
|
||||||
|
$return = $loader->findTemplate('Stub/foo.twig');
|
||||||
|
$this->assertEquals($realPath, $return);
|
||||||
|
|
||||||
|
$return = $loader->findTemplate('Stub/foo');
|
||||||
|
$this->assertEquals($realPath, $return);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Renderer;
|
||||||
|
|
||||||
|
use Engelsystem\Renderer\TwigEngine;
|
||||||
|
use Engelsystem\Renderer\TwigLoader;
|
||||||
|
use Engelsystem\Renderer\TwigServiceProvider;
|
||||||
|
use Engelsystem\Test\Unit\ServiceProviderTest;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use Twig_Environment as Twig;
|
||||||
|
use Twig_LoaderInterface as TwigLoaderInterface;
|
||||||
|
|
||||||
|
class TwigServiceProviderTest extends ServiceProviderTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Renderer\TwigServiceProvider::register
|
||||||
|
* @covers \Engelsystem\Renderer\TwigServiceProvider::registerTwigEngine
|
||||||
|
*/
|
||||||
|
public function testRegister()
|
||||||
|
{
|
||||||
|
/** @var TwigEngine|MockObject $htmlEngine */
|
||||||
|
$twigEngine = $this->createMock(TwigEngine::class);
|
||||||
|
/** @var TwigLoader|MockObject $twigLoader */
|
||||||
|
$twigLoader = $this->createMock(TwigLoader::class);
|
||||||
|
/** @var Twig|MockObject $twig */
|
||||||
|
$twig = $this->createMock(Twig::class);
|
||||||
|
|
||||||
|
$app = $this->getApp(['make', 'instance', 'tag', 'get']);
|
||||||
|
|
||||||
|
$viewsPath = __DIR__ . '/Stub';
|
||||||
|
|
||||||
|
$app->expects($this->exactly(3))
|
||||||
|
->method('make')
|
||||||
|
->withConsecutive(
|
||||||
|
[TwigLoader::class, ['paths' => $viewsPath]],
|
||||||
|
[Twig::class],
|
||||||
|
[TwigEngine::class]
|
||||||
|
)->willReturnOnConsecutiveCalls(
|
||||||
|
$twigLoader,
|
||||||
|
$twig,
|
||||||
|
$twigEngine
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->expects($this->exactly(4))
|
||||||
|
->method('instance')
|
||||||
|
->withConsecutive(
|
||||||
|
[TwigLoader::class, $twigLoader],
|
||||||
|
[TwigLoaderInterface::class, $twigLoader],
|
||||||
|
[Twig::class, $twig],
|
||||||
|
['renderer.twigEngine', $twigEngine]
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with('path.views')
|
||||||
|
->willReturn($viewsPath);
|
||||||
|
|
||||||
|
$this->setExpects($app, 'tag', ['renderer.twigEngine', ['renderer.engine']]);
|
||||||
|
|
||||||
|
$serviceProvider = new TwigServiceProvider($app);
|
||||||
|
$serviceProvider->register();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue