• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cecilapp / Cecil / 14620241211

23 Apr 2025 02:06PM UTC coverage: 83.787%. First build
14620241211

Pull #2148

github

web-flow
Merge 12fc09dec into 6d7ba8f0a
Pull Request #2148: refactor: configuration and cache

361 of 423 new or added lines in 26 files covered. (85.34%)

3049 of 3639 relevant lines covered (83.79%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.06
/src/Renderer/Twig.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <arnaud@ligny.fr>
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
use Twig\Extra\Cache\CacheExtension;
27

28
/**
29
 * Class Twig.
30
 */
31
class Twig implements RendererInterface
32
{
33
    /** @var Builder */
34
    private $builder;
35

36
    /** @var \Twig\Environment */
37
    private $twig;
38

39
    /** @var Translator */
40
    private $translator = null;
41

42
    /** @var \Twig\Profiler\Profile */
43
    private $profile = null;
44

45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function __construct(Builder $builder, $templatesPath)
49
    {
50
        $this->builder = $builder;
1✔
51
        // load layouts
52
        $loader = new \Twig\Loader\FilesystemLoader($templatesPath);
1✔
53
        // default options
54
        $loaderOptions = [
1✔
55
            'debug'            => $this->builder->isDebug(),
1✔
56
            'strict_variables' => true,
1✔
57
            'autoescape'       => false,
1✔
58
            'auto_reload'      => true,
1✔
59
            'cache'            => false,
1✔
60
        ];
1✔
61
        // use Twig cache?
62
        if ($this->builder->getConfig()->isEnabled('cache.templates')) {
1✔
63
            $loaderOptions = array_replace($loaderOptions, ['cache' => $this->builder->getConfig()->getCacheTemplatesPath()]);
1✔
64
        }
65
        // create the Twig instance
66
        $this->twig = new \Twig\Environment($loader, $loaderOptions);
1✔
67
        // set date format
68
        $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
1✔
69
            ->setDateFormat((string) $this->builder->getConfig()->get('date.format'));
1✔
70
        // set timezone
71
        if ($this->builder->getConfig()->has('date.timezone')) {
1✔
72
            $this->twig->getExtension(\Twig\Extension\CoreExtension::class)
×
NEW
73
                ->setTimezone($this->builder->getConfig()->get('date.timezone') ?? date_default_timezone_get());
×
74
        }
75
        /*
76
         * adds extensions
77
         */
78
        // Cecil core extension
79
        $this->twig->addExtension(new CoreExtension($this->builder));
1✔
80
        // required by `template_from_string()`
81
        $this->twig->addExtension(new \Twig\Extension\StringLoaderExtension());
1✔
82
        // l10n
83
        $this->translator = new Translator(
1✔
84
            $this->builder->getConfig()->getLanguageProperty('locale'),
1✔
85
            new MessageFormatter(new IdentityTranslator()),
1✔
86
            $this->builder->getConfig()->isEnabled('cache.templates') ? $this->builder->getConfig()->getCacheTranslationsPath() : null,
1✔
87
            $this->builder->isDebug()
1✔
88
        );
1✔
89
        if (\count($this->builder->getConfig()->getLanguages()) > 0) {
1✔
90
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
1✔
91
                $loader = \sprintf('Symfony\Component\Translation\Loader\%sFileLoader', ucfirst($format));
1✔
92
                if (class_exists($loader)) {
1✔
93
                    $this->translator->addLoader($format, new $loader());
1✔
94
                    $this->builder->getLogger()->debug(\sprintf('Translation loader for format "%s" found', $format));
1✔
95
                }
96
            }
97
            foreach ($this->builder->getConfig()->getLanguages() as $lang) {
1✔
98
                // internal
99
                $this->addTransResource($this->builder->getConfig()->getTranslationsInternalPath(), $lang['locale']);
1✔
100
                // themes
101
                if ($themes = $this->builder->getConfig()->getTheme()) {
1✔
102
                    foreach ($themes as $theme) {
1✔
103
                        $this->addTransResource($this->builder->getConfig()->getThemeDirPath($theme, 'translations'), $lang['locale']);
1✔
104
                    }
105
                }
106
                // site
107
                $this->addTransResource($this->builder->getConfig()->getTranslationsPath(), $lang['locale']);
1✔
108
            }
109
        }
110
        $this->twig->addExtension(new TranslationExtension($this->translator));
1✔
111
        // intl
112
        $this->twig->addExtension(new IntlExtension());
1✔
113
        if (\extension_loaded('intl')) {
1✔
114
            $this->builder->getLogger()->debug('PHP Intl extension is loaded');
1✔
115
        }
116
        // filters fallback
117
        $this->twig->registerUndefinedFilterCallback(function ($name) {
1✔
118
            switch ($name) {
119
                case 'localizeddate':
1✔
120
                    return new \Twig\TwigFilter($name, function (?\DateTime $value = null) {
1✔
121
                        return date($this->builder->getConfig()->get('date.format'), $value->getTimestamp());
1✔
122
                    });
1✔
123
            }
124

125
            return false;
×
126
        });
1✔
127
        // components
128
        Configuration::make($this->twig)
1✔
129
            ->setTemplatesPath($this->builder->getConfig()->get('layouts.components.dir') ?? 'components')
1✔
130
            ->setTemplatesExtension($this->builder->getConfig()->get('layouts.components.ext') ?? 'twig')
1✔
131
            ->useCustomTags()
1✔
132
            ->setup();
1✔
133
        // cache
134
        $this->twig->addExtension(new CacheExtension());
1✔
135
        $this->twig->addRuntimeLoader(new TwigCacheRuntimeLoader($this->builder->getConfig()->getCacheTemplatesPath()));
1✔
136
        // debug
137
        if ($this->builder->isDebug()) {
1✔
138
            // dump()
139
            $this->twig->addExtension(new \Twig\Extension\DebugExtension());
1✔
140
            // profiler
141
            $this->profile = new \Twig\Profiler\Profile();
1✔
142
            $this->twig->addExtension(new \Twig\Extension\ProfilerExtension($this->profile));
1✔
143
        }
144
        // loads custom extensions
145
        if ($this->builder->getConfig()->has('layouts.extensions')) {
1✔
146
            foreach ((array) $this->builder->getConfig()->get('layouts.extensions') as $name => $class) {
1✔
147
                try {
148
                    $this->twig->addExtension(new $class($this->builder));
1✔
149
                    $this->builder->getLogger()->debug(\sprintf('Twig extension "%s" added', $name));
1✔
150
                } catch (RuntimeException | \Error $e) {
1✔
151
                    $this->builder->getLogger()->error(\sprintf('Unable to add Twig extension "%s": %s', $name, $e->getMessage()));
1✔
152
                }
153
            }
154
        }
155
    }
156

157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function addGlobal(string $name, $value): void
161
    {
162
        $this->twig->addGlobal($name, $value);
1✔
163
    }
164

165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function render(string $template, array $variables): string
169
    {
170
        return $this->twig->render($template, $variables);
1✔
171
    }
172

173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function setLocale(string $locale): void
177
    {
178
        if (\extension_loaded('intl')) {
1✔
179
            \Locale::setDefault($locale);
1✔
180
        }
181
        $this->translator === null ?: $this->translator->setLocale($locale);
1✔
182
    }
183

184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function addTransResource(string $translationsDir, string $locale): void
188
    {
189
        $locales = [$locale];
1✔
190
        // if locale is 'fr_FR', trying to load ['fr', 'fr_FR']
191
        if (\strlen($locale) > 2) {
1✔
192
            array_unshift($locales, substr($locale, 0, 2));
1✔
193
        }
194
        foreach ($locales as $locale) {
1✔
195
            foreach ((array) $this->builder->getConfig()->get('layouts.translations.formats') as $format) {
1✔
196
                $translationFile = Util::joinPath($translationsDir, \sprintf('messages.%s.%s', $locale, $format));
1✔
197
                if (Util\File::getFS()->exists($translationFile)) {
1✔
198
                    $this->translator->addResource($format, $translationFile, $locale);
1✔
199
                    $this->builder->getLogger()->debug(\sprintf('Translation file "%s" added', $translationFile));
1✔
200
                }
201
            }
202
        }
203
    }
204

205
    /**
206
     * {@inheritdoc}
207
     */
208
    public function getDebugProfile(): ?\Twig\Profiler\Profile
209
    {
210
        return $this->profile;
1✔
211
    }
212

213
    /**
214
     * Returns the Twig instance.
215
     */
216
    public function getTwig(): \Twig\Environment
217
    {
218
        return $this->twig;
×
219
    }
220
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc