Skip to content

Commit 6e9b819

Browse files
authored
Merge pull request #419 from dotkernel/issue-414
Issue #414: Replaced `Twig` with custom templating solution
2 parents 46ccda7 + daa2fcb commit 6e9b819

34 files changed

+647
-159
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@
7272
"mezzio/mezzio-cors": "^1.13.0",
7373
"mezzio/mezzio-fastroute": "^3.12.0",
7474
"mezzio/mezzio-hal": "^2.10.1",
75+
"mezzio/mezzio-helpers": "^5.18",
7576
"mezzio/mezzio-problem-details": "^1.15.0",
76-
"mezzio/mezzio-twigrenderer": "^2.17.0",
7777
"ramsey/uuid-doctrine": "^2.1.0",
7878
"roave/psr-container-doctrine": "^5.2.2",
7979
"symfony/filesystem": "^7.2.0",

config/autoload/mezzio.global.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@
55
use Laminas\ConfigAggregator\ConfigAggregator;
66

77
return [
8-
// Toggle the configuration cache. Set this to boolean false, or remove the
9-
// directive, to disable configuration caching. Toggling development mode
10-
// will also disable it by default; clear the configuration cache using
11-
// `composer clear-config-cache`.
8+
// Toggle the configuration cache.
9+
// Set this to boolean false or remove the directive to disable configuration caching.
10+
// Toggling development mode will also disable it by default.
11+
// Clear the configuration cache using `composer clear-config-cache`.
1212
ConfigAggregator::ENABLE_CACHE => true,
1313

1414
// Enable debugging; typically used to provide debugging information within templates.
1515
'debug' => false,
1616
'mezzio' => [
17-
// Provide templates for the error handling middleware to use when
18-
// generating responses.
17+
// Provide templates for the error handling middleware to use when generating responses.
1918
'error_handler' => [
2019
'template_404' => 'error::404',
2120
'template_error' => 'error::error',

config/autoload/templates.global.php

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,11 @@
22

33
declare(strict_types=1);
44

5+
use Api\App\Template\RendererInterface;
6+
57
return [
6-
'debug' => false,
7-
'templates' => [
8-
'extension' => 'html.twig',
9-
],
10-
'twig' => [
11-
'assets_url' => '/',
12-
'assets_version' => null,
13-
'autoescape' => 'html',
14-
'auto_reload' => true,
15-
'cache_dir' => 'data/cache/twig',
16-
'extensions' => [],
17-
'globals' => [],
18-
'optimizations' => -1,
19-
'runtime_loaders' => [],
20-
'timezone' => 'UTC',
8+
RendererInterface::class => [
9+
'globals' => [],
10+
'extension' => 'phtml',
2111
],
2212
];

config/config.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
Mezzio\Hal\ConfigProvider::class,
2929
Mezzio\ProblemDetails\ConfigProvider::class,
3030
Mezzio\Router\FastRouteRouter\ConfigProvider::class,
31-
Mezzio\Twig\ConfigProvider::class,
3231
Mezzio\Helper\ConfigProvider::class,
3332
Mezzio\ConfigProvider::class,
3433
Mezzio\Router\ConfigProvider::class,

src/App/src/ConfigProvider.php

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
use Api\App\Middleware\ResourceProviderMiddleware;
1818
use Api\App\Service\ErrorReportService;
1919
use Api\App\Service\ErrorReportServiceInterface;
20+
use Api\App\Template\Parser;
21+
use Api\App\Template\ParserInterface;
22+
use Api\App\Template\Renderer;
23+
use Api\App\Template\RendererInterface;
2024
use Dot\DependencyInjection\Factory\AttributedServiceFactory;
2125
use Laminas\Hydrator\ArraySerializableHydrator;
2226
use Mezzio\Application;
@@ -25,13 +29,6 @@
2529
use Mezzio\Hal\Metadata\RouteBasedCollectionMetadata;
2630
use Mezzio\Hal\Metadata\RouteBasedResourceMetadata;
2731
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
28-
use Mezzio\Template\TemplateRendererInterface;
29-
use Mezzio\Twig\TwigEnvironmentFactory;
30-
use Mezzio\Twig\TwigExtension;
31-
use Mezzio\Twig\TwigExtensionFactory;
32-
use Mezzio\Twig\TwigRenderer;
33-
use Mezzio\Twig\TwigRendererFactory;
34-
use Twig\Environment;
3532

3633
class ConfigProvider
3734
{
@@ -62,14 +59,14 @@ private function getDependencies(): array
6259
PostErrorReportResourceHandler::class => AttributedServiceFactory::class,
6360
ErrorReportService::class => AttributedServiceFactory::class,
6461
TokenGenerateCommand::class => AttributedServiceFactory::class,
65-
Environment::class => TwigEnvironmentFactory::class,
66-
TwigExtension::class => TwigExtensionFactory::class,
67-
TwigRenderer::class => TwigRendererFactory::class,
62+
Parser::class => AttributedServiceFactory::class,
63+
Renderer::class => AttributedServiceFactory::class,
6864
],
6965
'aliases' => [
7066
AuthenticationInterface::class => OAuth2Adapter::class,
7167
ErrorReportServiceInterface::class => ErrorReportService::class,
72-
TemplateRendererInterface::class => TwigRenderer::class,
68+
RendererInterface::class => Renderer::class,
69+
ParserInterface::class => Parser::class,
7370
],
7471
];
7572
}

src/App/src/Template/Parser.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Template;
6+
7+
use Dot\DependencyInjection\Attribute\Inject;
8+
use Mezzio\Helper\UrlHelperInterface;
9+
10+
use function array_key_exists;
11+
use function sprintf;
12+
13+
class Parser implements ParserInterface
14+
{
15+
protected array $globals = [];
16+
17+
#[Inject(
18+
UrlHelperInterface::class,
19+
'config',
20+
)]
21+
public function __construct(
22+
protected UrlHelperInterface $urlHelper,
23+
array $config = [],
24+
) {
25+
$this->globals = $this->prepareGlobals($config);
26+
}
27+
28+
public function __invoke(string $path, array $params = []): void
29+
{
30+
foreach ($params as $key => $value) {
31+
${$key} = $value;
32+
}
33+
foreach ($this->globals as $key => $value) {
34+
${$key} = $value;
35+
}
36+
37+
include $path;
38+
}
39+
40+
public function absoluteUrl(string $path, ?string $baseUrl = null): string
41+
{
42+
if ($baseUrl === null) {
43+
$baseUrl = $this->globals['application']['url'] ?? '';
44+
}
45+
46+
return sprintf('%s%s', $baseUrl, $path);
47+
}
48+
49+
public function url(
50+
?string $routeName = null,
51+
array $routeParams = [],
52+
array $queryParams = [],
53+
?string $fragmentIdentifier = null,
54+
array $options = []
55+
): string {
56+
return $this->urlHelper->generate($routeName, $routeParams, $queryParams, $fragmentIdentifier, $options);
57+
}
58+
59+
public function getGlobals(): array
60+
{
61+
return $this->globals;
62+
}
63+
64+
public function prepareGlobals(array $config = []): array
65+
{
66+
$globals = $config[RendererInterface::class]['globals'] ?? [];
67+
68+
if (array_key_exists('application', $config)) {
69+
$globals['application'] = $config['application'] ?? [];
70+
}
71+
72+
return $globals;
73+
}
74+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Template;
6+
7+
interface ParserInterface
8+
{
9+
public function absoluteUrl(string $path, ?string $baseUrl = null): string;
10+
11+
public function url(
12+
?string $routeName = null,
13+
array $routeParams = [],
14+
array $queryParams = [],
15+
?string $fragmentIdentifier = null,
16+
array $options = []
17+
): string;
18+
}

src/App/src/Template/Renderer.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Template;
6+
7+
use Api\App\Exception\RuntimeException;
8+
use Core\App\Message;
9+
use Dot\DependencyInjection\Attribute\Inject;
10+
11+
use function array_key_exists;
12+
use function explode;
13+
use function is_readable;
14+
use function is_string;
15+
use function ob_get_clean;
16+
use function ob_start;
17+
use function realpath;
18+
use function sprintf;
19+
20+
class Renderer implements RendererInterface
21+
{
22+
protected array $templates = [];
23+
protected string $extension;
24+
25+
#[Inject(
26+
ParserInterface::class,
27+
'config',
28+
)]
29+
public function __construct(
30+
protected ParserInterface $parser,
31+
array $config = [],
32+
) {
33+
$this->templates = $config['templates'] ?? [];
34+
$this->extension = $config[RendererInterface::class]['extension'] ?? 'phtml';
35+
}
36+
37+
public function render(string $template, array $params = []): false|string
38+
{
39+
ob_start();
40+
($this->parser)($this->getPath($template), $params);
41+
return ob_get_clean();
42+
}
43+
44+
public function getExtension(): string
45+
{
46+
return $this->extension;
47+
}
48+
49+
/**
50+
* @throws RuntimeException
51+
*/
52+
public function getPath(string $template): string
53+
{
54+
[$namespace, $templateName] = explode('::', $template, 2);
55+
if (! array_key_exists($namespace, $this->templates)) {
56+
throw RuntimeException::create(Message::templateNotFound($template));
57+
}
58+
59+
$path = realpath(sprintf('%s/%s.%s', $this->templates[$namespace], $templateName, $this->extension));
60+
if (! is_string($path) || ! is_readable($path)) {
61+
throw RuntimeException::create(Message::templateNotFound($template));
62+
}
63+
64+
return $path;
65+
}
66+
67+
public function getTemplates(): array
68+
{
69+
return $this->templates;
70+
}
71+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Template;
6+
7+
interface RendererInterface
8+
{
9+
public function render(string $template, array $params = []): false|string;
10+
}

src/Core/src/App/src/Message.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Message
5050
public const ROLE_NOT_FOUND = 'Role not found.';
5151
public const SERVICE_NOT_FOUND = 'Service %s not found in the container.';
5252
public const SETTING_NOT_FOUND = 'Setting "%s" not found.';
53+
public const TEMPLATE_NOT_FOUND = 'Template "%s" not found.';
5354
public const UNSUPPORTED_MEDIA_TYPE = 'Unsupported Media Type.';
5455
public const USER_ACTIVATED = 'User account has been activated.';
5556
public const USER_ALREADY_ACTIVATED = 'User account is already active.';
@@ -137,6 +138,11 @@ public static function settingNotFound(string $identifier): string
137138
return sprintf(self::SETTING_NOT_FOUND, $identifier);
138139
}
139140

141+
public static function templateNotFound(string $template): string
142+
{
143+
return sprintf(self::TEMPLATE_NOT_FOUND, $template);
144+
}
145+
140146
public static function unsupportedMediaType(array $types = []): string
141147
{
142148
if (count($types) === 0) {

0 commit comments

Comments
 (0)