Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
TwigServiceProvider
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
4 / 4
12
100.00% covered (success)
100.00%
1 / 1
 register
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 boot
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
7
 registerTwigEngine
100.00% covered (success)
100.00%
43 / 43
100.00% covered (success)
100.00%
1 / 1
2
 registerTwigExtensions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Renderer;
6
7use Engelsystem\Config\Config as EngelsystemConfig;
8use Engelsystem\Container\ServiceProvider;
9use Engelsystem\Renderer\Twig\Extensions\Assets;
10use Engelsystem\Renderer\Twig\Extensions\Authentication;
11use Engelsystem\Renderer\Twig\Extensions\Config;
12use Engelsystem\Renderer\Twig\Extensions\Csrf;
13use Engelsystem\Renderer\Twig\Extensions\Develop;
14use Engelsystem\Renderer\Twig\Extensions\Globals;
15use Engelsystem\Renderer\Twig\Extensions\Legacy;
16use Engelsystem\Renderer\Twig\Extensions\Markdown;
17use Engelsystem\Renderer\Twig\Extensions\Notification;
18use Engelsystem\Renderer\Twig\Extensions\Qr;
19use Engelsystem\Renderer\Twig\Extensions\Session;
20use Engelsystem\Renderer\Twig\Extensions\StringExtension;
21use Engelsystem\Renderer\Twig\Extensions\Translation;
22use Engelsystem\Renderer\Twig\Extensions\Url;
23use Engelsystem\Renderer\Twig\Extensions\Uuid;
24use Symfony\Component\VarDumper\VarDumper;
25use Twig\Environment as Twig;
26use Twig\Extension\CoreExtension as TwigCore;
27use Twig\Loader\FilesystemLoader;
28use Twig\Loader\LoaderInterface as TwigLoaderInterface;
29use TwigBridge\Extension\Laravel\Model as TwigModel;
30
31class TwigServiceProvider extends ServiceProvider
32{
33    /** @var array<string, class-string> */
34    protected array $extensions = [
35        'assets'         => Assets::class,
36        'authentication' => Authentication::class,
37        'config'         => Config::class,
38        'csrf'           => Csrf::class,
39        'develop'        => Develop::class,
40        'globals'        => Globals::class,
41        'notification'   => Notification::class,
42        'twigmodel'      => TwigModel::class,
43        'session'        => Session::class,
44        'string'         => StringExtension::class,
45        'legacy'         => Legacy::class,
46        'markdown'       => Markdown::class,
47        'qr'             => Qr::class,
48        'translation'    => Translation::class,
49        'url'            => Url::class,
50        'uuid'           => Uuid::class,
51    ];
52
53    public function register(): void
54    {
55        $this->registerTwigEngine();
56
57        foreach ($this->extensions as $alias => $class) {
58            $this->registerTwigExtensions($class, $alias);
59        }
60    }
61
62    public function boot(): void
63    {
64        /** @var Twig $renderer */
65        $renderer = $this->app->get('twig.environment');
66        /** @var Twig $textRenderer */
67        $textRenderer = $this->app->get('twig.textEnvironment');
68
69        foreach ($this->app->tagged('twig.extension') as $extension) {
70            $renderer->addExtension($extension);
71            $textRenderer->addExtension($extension);
72        }
73
74        if (class_exists(VarDumper::class)) {
75            /** @var Develop $dev */
76            $dev = $this->app->get('twig.extension.develop');
77            $dev->setDumper($this->app->make(VarDumper::class));
78        }
79
80        // Prepend views from plugins
81        foreach ($this->app->tagged('plugin.path') as $path) {
82            $viewsPath = $path . 'views/';
83            if (!is_dir($viewsPath)) {
84                continue;
85            }
86
87            foreach ($this->app->tagged('twig.loader') as $loader) {
88                if (!$loader instanceof FilesystemLoader) {
89                    continue;
90                }
91                $loader->prependPath($viewsPath);
92            }
93        }
94    }
95
96    protected function registerTwigEngine(): void
97    {
98        // Paths are added twice to allow "more specific" overwrites with "views/" prefix from plugin templates
99        $viewsPath = [$this->app->get('path.views'), $this->app->get('path.views') . '/..'];
100        /** @var EngelsystemConfig $config */
101        $config = $this->app->get('config');
102
103        $twigLoader = $this->app->make(TwigLoader::class, ['paths' => $viewsPath]);
104        $this->app->instance(TwigLoader::class, $twigLoader);
105        $this->app->instance(FilesystemLoader::class, $twigLoader);
106        $this->app->instance(TwigLoaderInterface::class, $twigLoader);
107        $this->app->instance('twig.loader', $twigLoader);
108        $this->app->tag('twig.loader', ['twig.loader']);
109
110        $twigTextLoader = $this->app->make(TwigTextLoader::class, ['paths' => $viewsPath]);
111        $this->app->instance('twig.textLoader', $twigTextLoader);
112        $this->app->tag('twig.textLoader', ['twig.loader']);
113
114        $cache = $this->app->get('path.cache.views');
115        $twigDebug = false;
116        if ($config->get('environment') == 'development') {
117            $cache = false;
118            $twigDebug = true;
119        }
120
121        $twigOptions =  [
122            'cache'            => $cache,
123            'auto_reload'      => true,
124            'debug'            => $twigDebug,
125            'strict_variables' => $twigDebug,
126        ];
127
128        /** @var Twig $twig */
129        $twig = $this->app->make(Twig::class, ['options' => $twigOptions]);
130        $this->app->instance(Twig::class, $twig);
131        $this->app->instance('twig.environment', $twig);
132
133        /** @var TwigCore $twigCore */
134        $twigCore = $twig->getExtension(TwigCore::class);
135        $twigCore->setTimezone($config->get('timezone'));
136
137        $twigEngine = $this->app->make(TwigEngine::class);
138        $this->app->instance('renderer.twigEngine', $twigEngine);
139
140        /** @var Twig $textTwig */
141        $textTwig = $this->app->make(
142            Twig::class,
143            ['loader' => $twigTextLoader, 'options' => [...$twigOptions, 'autoescape' => false]]
144        );
145        $this->app->instance('twig.textEnvironment', $textTwig);
146        $twigTextEngine = $this->app->make(TwigEngine::class, ['twig' => $textTwig]);
147        $this->app->instance('renderer.twigTextEngine', $twigTextEngine);
148
149        // Text is tagged first to catch .text.twig files
150        $this->app->tag('renderer.twigTextEngine', ['renderer.engine']);
151        $this->app->tag('renderer.twigEngine', ['renderer.engine']);
152
153        // Add token parsers
154        $extendsTokenParser = $this->app->make(
155            ExtendsTokenParser::class,
156            ['basePath' => $this->app->get('path.resources')],
157        );
158        $twig->addTokenParser($extendsTokenParser);
159    }
160
161    protected function registerTwigExtensions(string $class, string $alias): void
162    {
163        $alias = 'twig.extension.' . $alias;
164        $this->app->alias($class, $alias);
165
166        $this->app->tag($alias, ['twig.extension']);
167    }
168}