Twig::getTwig()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
/**
4
 * This file is part of Cecil.
5
 *
6
 * (c) Arnaud Ligny <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Cecil\Renderer;
15
16
use Cecil\Builder;
17
use Cecil\Exception\RuntimeException;
18
use Cecil\Renderer\Extension\Core as CoreExtension;
19
use Cecil\Util;
20
use Performing\TwigComponents\Configuration;
21
use Symfony\Bridge\Twig\Extension\TranslationExtension;
22
use Symfony\Component\Translation\Formatter\MessageFormatter;
23
use Symfony\Component\Translation\IdentityTranslator;
24
use Symfony\Component\Translation\Translator;
25
use Twig\Extra\Cache\CacheExtension;
26
use Twig\Extra\Intl\IntlExtension;
27
use Twig\Extra\String\StringExtension;
28
29
/**
30
 * Twig renderer.
31
 *
32
 * This class is responsible for rendering templates using the Twig templating engine.
33
 * It initializes Twig with the necessary configurations, loads extensions, and provides methods
34
 * to render templates, add global variables, and manage translations.
35
 * It also supports debugging and profiling when in debug mode.
36
 */
37
class Twig implements RendererInterface
38
{
39
    /**
40
     * Builder object.
41
     * @var Builder
42
     */
43
    protected $builder;
44
    /**
45
     * Twig environment instance.
46
     * @var \Twig\Environment
47
     */
48
    private $twig;
49
    /**
50
     * Translator instance for translations.
51
     * @var Translator
52
     */
53
    private $translator = null;
54
    /**
55
     * Profile for debugging and profiling.
56
     * @var \Twig\Profiler\Profile
57
     */
58
    private $profile = null;
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 1
    public function __construct(Builder $builder, $templatesPath)
64
    {
65 1
        $this->builder = $builder;
66
        // load layouts
67 1
        $loader = new \Twig\Loader\FilesystemLoader($templatesPath);
68
        // default options
69 1
        $loaderOptions = [
70 1
            'debug'            => $this->builder->isDebug(),
71 1
            'strict_variables' => true,
72 1
            'autoescape'       => false,
73 1
            'auto_reload'      => true,
74 1
            'cache'            => false,
75 1
        ];
76
        // use Twig cache?
77 1
        if ($this->builder->getConfig()->isEnabled('cache.templates')) {
78 1
            $loaderOptions = array_replace($loaderOptions, ['cache' => $this->builder->getConfig()->getCacheTemplatesPath()]);
79
        }
80
        // create the Twig instance
81 1
        $this->twig = new \Twig\Environment($loader, $loaderOptions);
82
        // set date format
83 1
        $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
84 1
            ->setDateFormat((string) $this->builder->getConfig()->get('date.format'));
85
        // set timezone
86 1
        if ($this->builder->getConfig()->has('date.timezone')) {
87
            $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
88
                ->setTimezone($this->builder->getConfig()->get('date.timezone') ?? date_default_timezone_get());
89
        }
90
        /*
91
         * adds extensions
92
         */
93
        // Cecil core extension
94 1
        $this->twig->addExtension(new CoreExtension($this->builder));
95
        // required by `template_from_string()`
96 1
        $this->twig->addExtension(new \Twig\Extension\StringLoaderExtension());
97
        // the `u` filter (https://github.com/twigphp/string-extra)
98 1
        $this->twig->addExtension(new StringExtension());
99
        // l10n
100 1
        $this->translator = new Translator(
101 1
            $this->builder->getConfig()->getLanguageProperty('locale'),
102 1
            new MessageFormatter(new IdentityTranslator()),
103 1
            $this->builder->getConfig()->isEnabled('cache.translations') ? $this->builder->getConfig()->getCacheTranslationsPath() : null,
104 1
            $this->builder->isDebug()
105 1
        );
106 1
        if (\count($this->builder->getConfig()->getLanguages()) > 0) {
107 1
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
108 1
                $loader = \sprintf('Symfony\Component\Translation\Loader\%sFileLoader', ucfirst($format));
109 1
                if (class_exists($loader)) {
110 1
                    $this->translator->addLoader($format, new $loader());
111 1
                    $this->builder->getLogger()->debug(\sprintf('Translation loader for format "%s" found', $format));
112
                }
113
            }
114 1
            foreach ($this->builder->getConfig()->getLanguages() as $lang) {
115
                // internal
116 1
                $this->addTransResource($this->builder->getConfig()->getTranslationsInternalPath(), $lang['locale']);
117
                // themes
118 1
                if ($themes = $this->builder->getConfig()->getTheme()) {
119 1
                    foreach ($themes as $theme) {
120 1
                        $this->addTransResource($this->builder->getConfig()->getThemeDirPath($theme, 'translations'), $lang['locale']);
121
                    }
122
                }
123
                // site
124 1
                $this->addTransResource($this->builder->getConfig()->getTranslationsPath(), $lang['locale']);
125
            }
126
        }
127 1
        $this->twig->addExtension(new TranslationExtension($this->translator));
128
        // intl
129 1
        $this->twig->addExtension(new IntlExtension());
130 1
        if (\extension_loaded('intl')) {
131 1
            $this->builder->getLogger()->debug('PHP Intl extension is loaded');
132
        }
133
        // filters fallback
134 1
        $this->twig->registerUndefinedFilterCallback(function ($name) {
135
            switch ($name) {
136 1
                case 'localizeddate':
137 1
                    return new \Twig\TwigFilter($name, function (?\DateTime $value = null) {
138 1
                        return date($this->builder->getConfig()->get('date.format'), $value->getTimestamp());
0 ignored issues
show
Bug introduced by
The method getTimestamp() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

138
                        return date($this->builder->getConfig()->get('date.format'), $value->/** @scrutinizer ignore-call */ getTimestamp());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
It seems like $this->builder->getConfig()->get('date.format') can also be of type null; however, parameter $format of date() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

138
                        return date(/** @scrutinizer ignore-type */ $this->builder->getConfig()->get('date.format'), $value->getTimestamp());
Loading history...
139 1
                    });
140
            }
141
142
            return false;
143 1
        });
144
        // components
145 1
        Configuration::make($this->twig)
146 1
            ->setTemplatesPath($this->builder->getConfig()->get('layouts.components.dir') ?? 'components')
147 1
            ->setTemplatesExtension($this->builder->getConfig()->get('layouts.components.ext') ?? 'twig')
148 1
            ->useCustomTags()
149 1
            ->setup();
150
        // cache
151 1
        $this->twig->addExtension(new CacheExtension());
152 1
        $this->twig->addRuntimeLoader(new TwigCacheRuntimeLoader($this->builder->getConfig()->getCacheTemplatesPath()));
153
        // debug
154 1
        if ($this->builder->isDebug()) {
155
            // dump()
156 1
            $this->twig->addExtension(new \Twig\Extension\DebugExtension());
157
            // profiler
158 1
            $this->profile = new \Twig\Profiler\Profile();
159 1
            $this->twig->addExtension(new \Twig\Extension\ProfilerExtension($this->profile));
160
        }
161
        // loads custom extensions
162 1
        if ($this->builder->getConfig()->has('layouts.extensions')) {
163 1
            foreach ((array) $this->builder->getConfig()->get('layouts.extensions') as $name => $class) {
164
                try {
165 1
                    $this->twig->addExtension(new $class($this->builder));
166 1
                    $this->builder->getLogger()->debug(\sprintf('Twig extension "%s" added', $name));
167 1
                } catch (RuntimeException | \Error $e) {
168 1
                    $this->builder->getLogger()->error(\sprintf('Unable to add Twig extension "%s": %s', $name, $e->getMessage()));
169
                }
170
            }
171
        }
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 1
    public function addGlobal(string $name, $value): void
178
    {
179 1
        $this->twig->addGlobal($name, $value);
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 1
    public function render(string $template, array $variables): string
186
    {
187 1
        return $this->twig->render($template, $variables);
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193 1
    public function setLocale(string $locale): void
194
    {
195 1
        if (\extension_loaded('intl')) {
196 1
            \Locale::setDefault($locale);
197
        }
198 1
        $this->translator === null ?: $this->translator->setLocale($locale);
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204 1
    public function addTransResource(string $translationsDir, string $locale): void
205
    {
206 1
        $locales = [$locale];
207
        // if locale is 'fr_FR', trying to load ['fr', 'fr_FR']
208 1
        if (\strlen($locale) > 2) {
209 1
            array_unshift($locales, substr($locale, 0, 2));
210
        }
211 1
        foreach ($locales as $locale) {
212 1
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
213 1
                $translationFile = Util::joinPath($translationsDir, \sprintf('messages.%s.%s', $locale, $format));
214 1
                if (Util\File::getFS()->exists($translationFile)) {
215 1
                    $this->translator->addResource($format, $translationFile, $locale);
216 1
                    $this->builder->getLogger()->debug(\sprintf('Translation file "%s" added', $translationFile));
217
                }
218
            }
219
        }
220
    }
221
222
    /**
223
     * Returns the Twig instance.
224
     */
225
    public function getTwig(): \Twig\Environment
226
    {
227
        return $this->twig;
228
    }
229
230
    /**
231
     * Returns debug profile.
232
     */
233 1
    public function getDebugProfile(): ?\Twig\Profiler\Profile
234
    {
235 1
        return $this->profile;
236
    }
237
}
238