From bb3d16d273bb3e4552e4869dd22cb2c2d81f5387 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sun, 26 Aug 2018 02:54:52 +0200 Subject: [PATCH] Added Twig template renderer, closes #338 --- composer.json | 1 + config/app.php | 1 + includes/engelsystem.php | 2 +- includes/pages/guest_credits.php | 2 +- includes/pages/user_shifts.php | 2 +- src/Application.php | 1 + src/Middleware/LegacyMiddleware.php | 2 +- src/Renderer/TwigEngine.php | 41 ++++++++++ src/Renderer/TwigLoader.php | 26 ++++++ src/Renderer/TwigServiceProvider.php | 31 +++++++ src/helpers.php | 4 +- templates/layout.html | 62 -------------- templates/layouts/app.twig | 80 +++++++++++++++++++ templates/{ => layouts}/maintenance.html | 0 .../credits.html} | 18 ++--- .../user-shifts.html} | 0 tests/Unit/ApplicationTest.php | 1 + tests/Unit/Renderer/TwigEngineTest.php | 60 ++++++++++++++ tests/Unit/Renderer/TwigLoaderTest.php | 31 +++++++ .../Unit/Renderer/TwigServiceProviderTest.php | 63 +++++++++++++++ 20 files changed, 351 insertions(+), 77 deletions(-) create mode 100644 src/Renderer/TwigEngine.php create mode 100644 src/Renderer/TwigLoader.php create mode 100644 src/Renderer/TwigServiceProvider.php delete mode 100644 templates/layout.html create mode 100644 templates/layouts/app.twig rename templates/{ => layouts}/maintenance.html (100%) rename templates/{guest_credits.html => pages/credits.html} (64%) rename templates/{user_shifts.html => pages/user-shifts.html} (100%) create mode 100644 tests/Unit/Renderer/TwigEngineTest.php create mode 100644 tests/Unit/Renderer/TwigLoaderTest.php create mode 100644 tests/Unit/Renderer/TwigServiceProviderTest.php diff --git a/composer.json b/composer.json index f38bb972..0e6ee17d 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "symfony/http-foundation": "^3.3", "symfony/psr-http-message-bridge": "^1.0", "twbs/bootstrap": "^3.3", + "twig/twig": "^2.5", "zendframework/zend-diactoros": "^1.7" }, "require-dev": { diff --git a/config/app.php b/config/app.php index 9af35eb4..13d4d22b 100644 --- a/config/app.php +++ b/config/app.php @@ -10,6 +10,7 @@ return [ \Engelsystem\Config\ConfigServiceProvider::class, \Engelsystem\Http\UrlGeneratorServiceProvider::class, \Engelsystem\Renderer\RendererServiceProvider::class, + \Engelsystem\Renderer\TwigServiceProvider::class, \Engelsystem\Database\DatabaseServiceProvider::class, \Engelsystem\Http\RequestServiceProvider::class, \Engelsystem\Http\SessionServiceProvider::class, diff --git a/includes/engelsystem.php b/includes/engelsystem.php index f7d813c5..eb5b220a 100644 --- a/includes/engelsystem.php +++ b/includes/engelsystem.php @@ -16,7 +16,7 @@ require __DIR__ . '/includes.php'; * Check for maintenance */ if ($app->get('config')->get('maintenance')) { - echo file_get_contents(__DIR__ . '/../templates/maintenance.html'); + echo file_get_contents(__DIR__ . '/../templates/layouts/maintenance.html'); die(); } diff --git a/includes/pages/guest_credits.php b/includes/pages/guest_credits.php index db86132d..ecfa8f7c 100644 --- a/includes/pages/guest_credits.php +++ b/includes/pages/guest_credits.php @@ -13,5 +13,5 @@ function credits_title() */ function guest_credits() { - return view(__DIR__ . '/../../templates/guest_credits.html'); + return view(__DIR__ . '/../../templates/pages/credits.html'); } diff --git a/includes/pages/user_shifts.php b/includes/pages/user_shifts.php index a620d081..67f6785f 100644 --- a/includes/pages/user_shifts.php +++ b/includes/pages/user_shifts.php @@ -224,7 +224,7 @@ function view_user_shifts() return page([ div('col-md-12', [ msg(), - view(__DIR__ . '/../../templates/user_shifts.html', [ + view(__DIR__ . '/../../templates/pages/user-shifts.html', [ 'title' => shifts_title(), 'room_select' => make_select($rooms, $shiftsFilter->getRooms(), 'rooms', _('Rooms')), 'start_select' => html_select_key( diff --git a/src/Application.php b/src/Application.php index 6644a6cf..86397a2c 100644 --- a/src/Application.php +++ b/src/Application.php @@ -107,6 +107,7 @@ class Application extends Container $this->instance('path', $appPath); $this->instance('path.config', $appPath . DIRECTORY_SEPARATOR . 'config'); $this->instance('path.lang', $appPath . DIRECTORY_SEPARATOR . 'locale'); + $this->instance('path.views', $appPath . DIRECTORY_SEPARATOR . 'templates'); } /** diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php index 276fb3ee..37ae9331 100644 --- a/src/Middleware/LegacyMiddleware.php +++ b/src/Middleware/LegacyMiddleware.php @@ -283,7 +283,7 @@ class LegacyMiddleware implements MiddlewareInterface $content = info($content, true); } - return response(view(__DIR__ . '/../../templates/layout.html', [ + return response(view('layouts/app', [ 'theme' => isset($user) ? $user['color'] : config('theme'), 'title' => $title, 'atom_link' => ($page == 'news' || $page == 'user_meetings') diff --git a/src/Renderer/TwigEngine.php b/src/Renderer/TwigEngine.php new file mode 100644 index 00000000..55a2e299 --- /dev/null +++ b/src/Renderer/TwigEngine.php @@ -0,0 +1,41 @@ +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); + } +} diff --git a/src/Renderer/TwigLoader.php b/src/Renderer/TwigLoader.php new file mode 100644 index 00000000..154e6dbb --- /dev/null +++ b/src/Renderer/TwigLoader.php @@ -0,0 +1,26 @@ +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']); + } +} diff --git a/src/helpers.php b/src/helpers.php index a90b2462..336f81fe 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -5,8 +5,8 @@ use Engelsystem\Application; use Engelsystem\Config\Config; use Engelsystem\Http\Request; use Engelsystem\Http\Response; -use Engelsystem\Renderer\Renderer; use Engelsystem\Http\UrlGenerator; +use Engelsystem\Renderer\Renderer; use Symfony\Component\HttpFoundation\Session\SessionInterface; /** @@ -139,7 +139,7 @@ function url($path = null, $parameters = []) * @param mixed[] $data * @return Renderer|string */ -function view($template = null, $data = null) +function view($template = null, $data = []) { $renderer = app('renderer'); diff --git a/templates/layout.html b/templates/layout.html deleted file mode 100644 index 12a91086..00000000 --- a/templates/layout.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - %title% - Engelsystem - - - - - - - - %atom_link% - - - -
-
%content%
- -
- - - - - - - - - - - diff --git a/templates/layouts/app.twig b/templates/layouts/app.twig new file mode 100644 index 00000000..6b6bd16f --- /dev/null +++ b/templates/layouts/app.twig @@ -0,0 +1,80 @@ + + + + {% block head %} + {% block title %}{{ title }}{% endblock %} - Engelsystem + + + + + + + + {{ atom_link|raw }} + {% endblock %} + + + +{% block body %} + + +
+
{% block content %}{{ content|raw }}{% endblock %}
+ +
+ + + + + + + + + + +{% endblock %} + + + diff --git a/templates/maintenance.html b/templates/layouts/maintenance.html similarity index 100% rename from templates/maintenance.html rename to templates/layouts/maintenance.html diff --git a/templates/guest_credits.html b/templates/pages/credits.html similarity index 64% rename from templates/guest_credits.html rename to templates/pages/credits.html index db7fac57..4e247113 100644 --- a/templates/guest_credits.html +++ b/templates/pages/credits.html @@ -6,15 +6,15 @@

The original system was written by cookie. It was then completely rewritten and enhanced by - msquare (maintainer), - MyIgel, - mortzu, - jplitza and - gnomus. + msquare (maintainer), + MyIgel, + mortzu, + jplitza and + gnomus.

- Please look at the contributor - list on github for a more complete version. + Please look at the + contributor list on github for a more complete version.

@@ -22,8 +22,8 @@

Webspace, development platform and domain on engelsystem.de is currently provided by would you buy this? (ichdasich) - and adminstrated by mortzu, - derf and ichdasich. + and adminstrated by mortzu, + derf and ichdasich.

diff --git a/templates/user_shifts.html b/templates/pages/user-shifts.html similarity index 100% rename from templates/user_shifts.html rename to templates/pages/user-shifts.html diff --git a/tests/Unit/ApplicationTest.php b/tests/Unit/ApplicationTest.php index 866eb957..012226b2 100644 --- a/tests/Unit/ApplicationTest.php +++ b/tests/Unit/ApplicationTest.php @@ -48,6 +48,7 @@ class ApplicationTest extends TestCase $this->assertTrue($app->has('path')); $this->assertTrue($app->has('path.config')); $this->assertTrue($app->has('path.lang')); + $this->assertTrue($app->has('path.views')); $this->assertEquals(realpath('.'), $app->path()); $this->assertEquals(realpath('.') . '/config', $app->get('path.config')); diff --git a/tests/Unit/Renderer/TwigEngineTest.php b/tests/Unit/Renderer/TwigEngineTest.php new file mode 100644 index 00000000..9d0618f1 --- /dev/null +++ b/tests/Unit/Renderer/TwigEngineTest.php @@ -0,0 +1,60 @@ +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); + } +} diff --git a/tests/Unit/Renderer/TwigLoaderTest.php b/tests/Unit/Renderer/TwigLoaderTest.php new file mode 100644 index 00000000..e6867643 --- /dev/null +++ b/tests/Unit/Renderer/TwigLoaderTest.php @@ -0,0 +1,31 @@ +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); + } +} diff --git a/tests/Unit/Renderer/TwigServiceProviderTest.php b/tests/Unit/Renderer/TwigServiceProviderTest.php new file mode 100644 index 00000000..ede6fae4 --- /dev/null +++ b/tests/Unit/Renderer/TwigServiceProviderTest.php @@ -0,0 +1,63 @@ +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(); + } +}