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

heimrichhannot / contao-utils-bundle / 38531955264

14 Oct 2026 07:59AM UTC coverage: 83.985% (+4.2%) from 79.825%
38531955264

Pull #108

github

koertho
reject several search vector fields per table

A second registration for the same table silently replaced the first one.
Now loading the DCA throws an exception that names the conflicting fields.
Loading a DCA several times in one process, each time registering the field
once, is still allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pull Request #108: add search vector field

372 of 385 new or added lines in 16 files covered. (96.62%)

1568 of 1867 relevant lines covered (83.99%)

8.62 hits per line

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

90.32
/src/SearchVector/SearchVectorLanguageSwitcher.php
1
<?php
2

3
namespace HeimrichHannot\UtilsBundle\SearchVector;
4

5
use Composer\InstalledVersions;
6
use Contao\Controller;
7
use Contao\CoreBundle\Framework\ContaoFramework;
8
use Contao\CoreBundle\Util\LocaleUtil;
9
use Symfony\Component\Translation\LocaleSwitcher;
10
use Symfony\Contracts\Translation\LocaleAwareInterface;
11
use Symfony\Contracts\Translation\TranslatorInterface;
12

13
/**
14
 * Runs code in the language of a record, independent of the language of the current user.
15
 *
16
 * Since Contao 5.6.3 the locale is switched with the Symfony locale switcher, as Contao itself does. It also updates
17
 * the translator and the legacy $GLOBALS['TL_LANGUAGE']. Contao 5.3 to 5.6.2 set the translator and
18
 * $GLOBALS['TL_LANGUAGE'] directly, so the locale switcher does not know the language of the user there. In these
19
 * versions the translator and $GLOBALS['TL_LANGUAGE'] are switched directly as well.
20
 *
21
 * All loaded language files are reloaded in the language of the record and afterwards in the previous language, as
22
 * labels can come from the language files of any table. Language files loaded by the callback are restored as well,
23
 * so the current user never ends up with labels in the language of the record.
24
 *
25
 * @internal
26
 */
27
class SearchVectorLanguageSwitcher
28
{
29
    /**
30
     * Contao version that switches the locale of the user with the Symfony locale switcher.
31
     */
32
    private const LOCALE_SWITCHER_SINCE = '5.6.3';
33

34
    private readonly bool $contaoUsesLocaleSwitcher;
35

36
    public function __construct(
37
        private readonly ContaoFramework $framework,
38
        private readonly TranslatorInterface $translator,
39
        private readonly LocaleSwitcher $localeSwitcher,
40
    ) {
41
        // Null if the core bundle is replaced, e.g. by the contao/contao monorepo
42
        $version = InstalledVersions::getVersion('contao/core-bundle');
17 ✔
43

44
        $this->contaoUsesLocaleSwitcher = null === $version || \str_starts_with($version, 'dev-') || \version_compare($version, self::LOCALE_SWITCHER_SINCE, '>=');
17 ✔
45
    }
46

47
    /**
48
     * @template T
49
     *
50
     * @param callable(): T $callback
51
     *
52
     * @return T
53
     */
54
    public function run(string $locale, string $table, callable $callback): mixed
55
    {
56
        $locale = LocaleUtil::formatAsLocale($locale);
15 ✔
57
        $previousLocale = $this->getCurrentLocale();
15 ✔
58

59
        if ($locale === $previousLocale) {
15 ✔
60
            // The DCA loader does not load translations, so the labels of the table might be missing otherwise
61
            $this->loadLanguageFiles(['default', $table], $locale);
2 ✔
62

63
            return $callback();
2 ✔
64
        }
65

66
        try {
67
            return $this->switchLocale($locale, function () use ($locale, $table, $callback): mixed {
14 ✔
68
                $this->loadLanguageFiles([...LoadedLanguageFiles::getNames(), 'default', $table], $locale);
14 ✔
69

70
                return $callback();
14 ✔
71
            });
14 ✔
72
        } finally {
73
            $this->loadLanguageFiles([...LoadedLanguageFiles::getNames(), 'default', $table], $previousLocale);
14 ✔
74
        }
75
    }
76

77
    private function getCurrentLocale(): string
78
    {
79
        if ($this->contaoUsesLocaleSwitcher) {
15 ✔
NEW
80
            return LocaleUtil::formatAsLocale($this->localeSwitcher->getLocale());
×
81
        }
82

83
        // Contao before 5.6.3
84
        return LocaleUtil::formatAsLocale($GLOBALS['TL_LANGUAGE'] ?? ($this->translator instanceof LocaleAwareInterface ? $this->translator->getLocale() : 'en'));
15 ✔
85
    }
86

87
    /**
88
     * @param callable(): mixed $callback
89
     */
90
    private function switchLocale(string $locale, callable $callback): mixed
91
    {
92
        if ($this->contaoUsesLocaleSwitcher) {
14 ✔
NEW
93
            return $this->localeSwitcher->runWithLocale($locale, $callback);
×
94
        }
95

96
        // Contao before 5.6.3, which sets the translator and $GLOBALS['TL_LANGUAGE'] directly as well
97
        $previousLanguage = $GLOBALS['TL_LANGUAGE'] ?? null;
14 ✔
98
        $translator = $this->translator instanceof LocaleAwareInterface ? $this->translator : null;
14 ✔
99
        $previousTranslatorLocale = $translator?->getLocale();
14 ✔
100

101
        $GLOBALS['TL_LANGUAGE'] = LocaleUtil::formatAsLanguageTag($locale);
14 ✔
102
        $translator?->setLocale($locale);
14 ✔
103

104
        try {
105
            return $callback();
14 ✔
106
        } finally {
107
            if (null !== $previousTranslatorLocale) {
14 ✔
108
                $translator->setLocale($previousTranslatorLocale);
14 ✔
109
            }
110

111
            if (null === $previousLanguage) {
14 ✔
NEW
112
                unset($GLOBALS['TL_LANGUAGE']);
×
113
            } else {
114
                $GLOBALS['TL_LANGUAGE'] = $previousLanguage;
14 ✔
115
            }
116
        }
117
    }
118

119
    /**
120
     * @param list<string> $names
121
     */
122
    private function loadLanguageFiles(array $names, string $locale): void
123
    {
124
        $controller = $this->framework->getAdapter(Controller::class);
15 ✔
125

126
        foreach (\array_unique($names) as $name) {
15 ✔
127
            $controller->loadLanguageFile($name, $locale);
15 ✔
128
        }
129
    }
130
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc