Passed
Pull Request — master (#2045)
by Arnaud
05:46
created

Twig   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Test Coverage

Coverage 94.19%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 82
c 2
b 0
f 0
dl 0
loc 185
ccs 81
cts 86
cp 0.9419
rs 10
wmc 28

7 Methods

Rating   Name   Duplication   Size   Complexity  
A setLocale() 0 6 3
A addGlobal() 0 3 1
A addTransResource() 0 13 5
A render() 0 3 1
C __construct() 0 101 16
A getDebugProfile() 0 3 1
A getTwig() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
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\Intl\IntlExtension;
26
27
/**
28
 * Class Twig.
29
 */
30
class Twig implements RendererInterface
31
{
32
    /** @var Builder */
33
    private $builder;
34
35
    /** @var \Twig\Environment */
36
    private $twig;
37
38
    /** @var Translator */
39
    private $translator = null;
40
41
    /** @var \Twig\Profiler\Profile */
42
    private $profile = null;
43
44
    /**
45
     * {@inheritdoc}
46
     */
47 1
    public function __construct(Builder $builder, $templatesPath)
48
    {
49 1
        $this->builder = $builder;
50
        // load layouts
51 1
        $loader = new \Twig\Loader\FilesystemLoader($templatesPath);
52
        // default options
53 1
        $loaderOptions = [
54 1
            'debug'            => $this->builder->isDebug(),
55 1
            'strict_variables' => true,
56 1
            'autoescape'       => false,
57 1
            'auto_reload'      => true,
58 1
            'cache'            => false,
59 1
        ];
60
        // use Twig cache?
61 1
        if ((bool) $this->builder->getConfig()->get('cache.templates.enabled')) {
62 1
            $loaderOptions = array_replace($loaderOptions, ['cache' => $this->builder->getConfig()->getCacheTemplatesPath()]);
63
        }
64
        // create the Twig instance
65 1
        $this->twig = new \Twig\Environment($loader, $loaderOptions);
66
        // set date format
67 1
        $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
68 1
            ->setDateFormat((string) $this->builder->getConfig()->get('date.format'));
69
        // set timezone
70 1
        if ($this->builder->getConfig()->has('date.timezone')) {
71
            $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
72
                ->setTimezone((string) $this->builder->getConfig()->get('date.timezone') ?? date_default_timezone_get());
73
        }
74
        /*
75
         * adds extensions
76
         */
77
        // Cecil core extension
78 1
        $this->twig->addExtension(new CoreExtension($this->builder));
79
        // required by `template_from_string()`
80 1
        $this->twig->addExtension(new \Twig\Extension\StringLoaderExtension());
81
        // l10n
82 1
        $this->translator = new Translator(
83 1
            $this->builder->getConfig()->getLanguageProperty('locale'),
84 1
            new MessageFormatter(new IdentityTranslator()),
85 1
            (bool) $this->builder->getConfig()->get('cache.templates.enabled') ? $this->builder->getConfig()->getCacheTranslationsPath() : null,
86 1
            $this->builder->isDebug()
87 1
        );
88 1
        if (\count($this->builder->getConfig()->getLanguages()) > 0) {
89 1
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
90 1
                $loader = \sprintf('Symfony\Component\Translation\Loader\%sFileLoader', ucfirst($format));
91 1
                if (class_exists($loader)) {
92 1
                    $this->translator->addLoader($format, new $loader());
93 1
                    $this->builder->getLogger()->debug(\sprintf('Translation loader for format "%s" found', $format));
94
                }
95
            }
96 1
            foreach ($this->builder->getConfig()->getLanguages() as $lang) {
97
                // internal
98 1
                $this->addTransResource($this->builder->getConfig()->getTranslationsInternalPath(), $lang['locale']);
99
                // themes
100 1
                if ($themes = $this->builder->getConfig()->getTheme()) {
101 1
                    foreach ($themes as $theme) {
102 1
                        $this->addTransResource($this->builder->getConfig()->getThemeDirPath($theme, 'translations'), $lang['locale']);
103
                    }
104
                }
105
                // site
106 1
                $this->addTransResource($this->builder->getConfig()->getTranslationsPath(), $lang['locale']);
107
            }
108
        }
109 1
        $this->twig->addExtension(new TranslationExtension($this->translator));
110
        // intl
111 1
        $this->twig->addExtension(new IntlExtension());
112 1
        if (\extension_loaded('intl')) {
113 1
            $this->builder->getLogger()->debug('PHP Intl extension is loaded');
114
        }
115
        // filters fallback
116 1
        $this->twig->registerUndefinedFilterCallback(function ($name) {
117
            switch ($name) {
118 1
                case 'localizeddate':
119 1
                    return new \Twig\TwigFilter($name, function (?\DateTime $value = null) {
120 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

120
                        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

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