Passed
Push — master ( d12f46...58955b )
by Ben
01:03
created

Config::existsAsLocale()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 6
nop 2
dl 0
loc 20
ccs 11
cts 11
cp 1
crap 5
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace Thinktomorrow\Locale\Values;
4
5
use Thinktomorrow\Locale\Exceptions\InvalidConfig;
6
7
class Config implements \ArrayAccess
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
8
{
9
    /**
10
     * @var array
11
     */
12
    private $config;
13
14 113
    private function __construct(array $config)
15
    {
16 113
        $this->validate($config);
17
18 101
        $this->config = $this->sanitize($config);
19 101
    }
20
21 113
    public static function from(array $config)
22
    {
23 113
        return new static($config);
24
    }
25
26 94
    public function get($key, $default = null)
27
    {
28 94
        if (!isset($this->config[$key])) {
29 2
            return $default;
30
        }
31
32 92
        return $this->config[$key];
33
    }
34
35 1
    public function all(): array
36
    {
37 1
        return $this->config;
38
    }
39
40 101
    private function sanitize(array $config): array
41
    {
42
        // Sanitize locales
43 101
        $locales = $config['locales'];
44 101
        $locales = $this->convertSingleEntryToDefault($locales);
45 101
        $locales = $this->removeSlashes($locales);
46 101
        $locales = $this->removeTrailingDomainSlashes($locales);
47 101
        $config['locales'] = $locales;
48
49
        // Compute canonicals for all locales
50 101
        $config['canonicals'] = $this->computeCanonicals($config);
51
52 101
        return $config;
53
    }
54
55 101
    private function computeCanonicals(array $config): array
56
    {
57 101
        $canonicals = $config['canonicals'] ?? [];
58
59 101
        foreach ($config['locales'] as $rootKey => $locales) {
60
61
            // We currently do not accept wildcard domains as canonicals as we cannot know to which root this should resolve to.
62 101
            if (false !== strpos($rootKey, '*')) {
63 101
                continue;
64
            }
65
66 92
            foreach ($locales as $locale) {
67 92
                if (!isset($canonicals[$locale])) {
68 92
                    $canonicals[$locale] = $rootKey;
69
                }
70
            }
71
        }
72
73 101
        return $canonicals;
74
    }
75
76
    /**
77
     * @param array $locales
78
     *
79
     * @return array
80
     */
81 101
    private function removeSlashes(array $locales): array
82
    {
83 101
        foreach ($locales as $group => $segments) {
84 101
            foreach ($segments as $segment => $locale) {
85
                // remove slashes if any e.g. '/nl' will be sanitized to 'nl'
86 101
                if ($segment != '/' && false !== strpos($segment, '/')) {
87 1
                    $_segment = str_replace('/', '', $segment);
88
89 1
                    unset($locales[$group][$segment]);
90 101
                    $locales[$group][$_segment] = $locale;
91
                }
92
            }
93
        }
94
95 101
        return $locales;
96
    }
97
98
    /**
99
     * e.g. example.com/ will be sanitized to example.com.
100
     *
101
     * @param array $locales
102
     *
103
     * @return array
104
     */
105 101
    private function removeTrailingDomainSlashes(array $locales)
106
    {
107 101
        foreach ($locales as $scopeKey => $segments) {
108 101
            unset($locales[$scopeKey]);
109 101
            $locales[rtrim($scopeKey, '/')] = $segments;
110
        }
111
112 101
        return $locales;
113
    }
114
115
    /**
116
     * @param array $locales
117
     *
118
     * @return array
119
     */
120 101
    private function convertSingleEntryToDefault(array $locales): array
121
    {
122 101
        foreach ($locales as $group => $segments) {
123
            // If single locale is passed, it's considered the default for this group
124 101
            if (!is_array($segments)) {
125 101
                $locales[$group] = $segments = ['/' => $segments];
126
            }
127
        }
128
129 101
        return $locales;
130
    }
131
132
    /**
133
     * @param array $config
134
     */
135 113
    private function validate(array $config)
136
    {
137 113
        if (!isset($config['locales'])) {
138 2
            throw new InvalidConfig('Value [Locales] is missing for config structure.');
139
        }
140
141 111
        $locales = $config['locales'];
142
143 111
        if (!isset($locales['*'])) {
144 8
            throw new InvalidConfig('Default group [*] is missing for locales structure.');
145
        }
146
147 103
        if (is_array($locales['*']) && !isset($locales['*']['/'])) {
148 1
            throw new InvalidConfig('Group [default] is missing the default locale. e.g. ["/" => "en"]');
149
        }
150
151 102
        foreach ($locales as $group => $segments) {
152 102
            if (!is_string($group)) {
153 102
                throw new InvalidConfig('Invalid config structure for locales group ['.$group.']');
154
            }
155
        }
156
157 102
        $this->validateEachCanonicalLocaleExists($config);
158 101
    }
159
160
    /**
161
     * Each custom canonical entry should point to an existing locale.
162
     *
163
     * @param array $config
164
     */
165 102
    private function validateEachCanonicalLocaleExists(array $config)
166
    {
167 102
        $canonicals = $config['canonicals'] ?? [];
168 102
        foreach ($canonicals as $locale => $canonical) {
169 81
            if (!$this->existsAsLocale($config['locales'], $locale)) {
170 81
                throw new InvalidConfig('Canonical key '.$locale.' is not present as locale.');
171
            }
172
        }
173 101
    }
174
175 81
    private function existsAsLocale($existing_locales, $locale): bool
176
    {
177 81
        $flag = false;
178
179 81
        foreach ($existing_locales as $existing_locale) {
180 81
            if (is_array($existing_locale)) {
181 78
                if (true === $this->existsAsLocale($existing_locale, $locale)) {
182 78
                    $flag = true;
183 78
                    break;
184
                }
185
            }
186
187 81
            if ($existing_locale === $locale) {
188 80
                $flag = true;
189 81
                break;
190
            }
191
        }
192
193 81
        return $flag;
194
    }
195
196 69
    public function offsetExists($offset)
197
    {
198 69
        if (!is_string($offset) && !is_int($offset)) {
199
            return false;
200
        }
201
202 69
        return array_key_exists($offset, $this->config);
203
    }
204
205 68
    public function offsetGet($offset)
206
    {
207 68
        return $this->config[$offset];
208
    }
209
210 1
    public function offsetSet($offset, $value)
211
    {
212 1
        if (is_null($offset)) {
213
            $this->config[] = $value;
214
        } else {
215 1
            $this->config[$offset] = $value;
216
        }
217 1
    }
218
219 1
    public function offsetUnset($offset)
220
    {
221 1
        unset($this->config[$offset]);
222 1
    }
223
224 78
    public function toArray(): array
225
    {
226 78
        return $this->config;
227
    }
228
}
229