Test Setup Failed
Pull Request — latest (#3)
by Mark
34:22
created

RuntimeDataset::offsetSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UnicornFail\Emoji\Dataset;
6
7
use League\Configuration\ConfigurationInterface;
8
use League\Configuration\Exception\InvalidConfigurationException;
9
use UnicornFail\Emoji\Emojibase\EmojibaseDatasetInterface;
10
use UnicornFail\Emoji\Emojibase\EmojibaseShortcodeInterface;
11
use UnicornFail\Emoji\Exception\FileNotFoundException;
12
use UnicornFail\Emoji\Exception\LocalePresetException;
13
use UnicornFail\Emoji\Exception\MalformedArchiveException;
14
use UnicornFail\Emoji\Exception\UnarchiveException;
15
use UnicornFail\Emoji\Parser\EmojiParser;
16
17
final class RuntimeDataset implements \ArrayAccess, \Countable, \SeekableIterator
18
{
19
    public const DEFAULT = 'en';
20
21
    /** @var ConfigurationInterface */
22
    private $config;
23
24
    /** @var ?Dataset */
25
    private $dataset;
26
27
    /** @var ?string */
28
    private $locale;
29
30
    /** @var ?bool */
31
    private $native;
32
33
    /** @var ?string[] */
34
    private $presets;
35
36
    public function __construct(ConfigurationInterface $configuration, ?Dataset $dataset = null)
37
    {
38
        $this->config  = $configuration;
39
        $this->dataset = $dataset;
40
    }
41
42
    /**
43
     * @param string[] $indices
44
     *
45
     * @return false|string
46
     */
47
    public static function archive(Dataset $dataset, array $indices = EmojiParser::INDICES)
48
    {
49
        foreach ($indices as $index) {
50
            $dataset->indexBy($index);
51
        }
52
53
        $serialize = \serialize($dataset);
54
55
        return \gzencode($serialize, 9);
56
    }
57
58
    public static function unarchive(string $filename): Dataset
59
    {
60
        if (! \file_exists($filename)) {
61
            throw new FileNotFoundException($filename);
62
        }
63
64
        if (
65
            ! ($contents = \file_get_contents($filename)) ||
66
            ! ($decoded = \gzdecode($contents))
67
        ) {
68
            throw new UnarchiveException($filename);
69
        }
70
71
        try {
72
            /** @var ?Dataset $dataset */
73
            $dataset = \unserialize((string) $decoded);
74
        } catch (\Throwable $throwable) {
75
            throw new MalformedArchiveException($filename, $throwable);
76
        }
77
78
        if (! $dataset instanceof Dataset) {
79
            throw new MalformedArchiveException($filename);
80
        }
81
82
        return $dataset;
83
    }
84
85
    public static function normalizeLocale(string $locale): string
86
    {
87
        // Immediately return if locale is an exact match.
88
        if (\in_array($locale, EmojibaseDatasetInterface::SUPPORTED_LOCALES, true)) {
89
            return $locale;
90
        }
91
92
        // Immediately return if this local has already been normalized.
93
        /** @var string[] $normalized */
94
        static $normalized = [];
95
        if (isset($normalized[$locale])) {
96
            return $normalized[$locale];
97
        }
98
99
        $original              = $locale;
100
        $normalized[$original] = '';
101
102
        // Otherwise, see if it just needs some TLC.
103
        $locale = \strtolower($locale);
104
        $locale = \preg_replace('/[^a-z]/', '-', $locale) ?? $locale;
105
        foreach ([$locale, \current(\explode('-', $locale, 2))] as $locale) {
106
            if (\in_array($locale, EmojibaseDatasetInterface::SUPPORTED_LOCALES, true)) {
107
                $normalized[$original] = $locale;
108
                break;
109
            }
110
        }
111
112
        return $normalized[$original];
113
    }
114
115
    /**
116
     * @param string|string[] $value
117
     *
118
     * @return string[]
119
     */
120
    public static function normalizePreset($value): array
121
    {
122
        // Presets.
123
        $presets = [];
124
        foreach ((array) $value as $preset) {
125
            if (isset(EmojibaseShortcodeInterface::PRESET_ALIASES[$preset])) {
126
                $presets[] = EmojibaseShortcodeInterface::PRESET_ALIASES[$preset];
127
            } elseif (isset(EmojibaseShortcodeInterface::PRESETS[$preset])) {
128
                $presets[] = EmojibaseShortcodeInterface::PRESETS[$preset];
129
            } else {
130
                throw InvalidConfigurationException::forConfigOption('preset', $preset, \sprintf(
131
                    'Accepted values are: %s.',
132
                    \implode(', ', \array_map(
133
                        static function ($s) {
134
                            return \sprintf('"%s"', $s);
135
                        },
136
                        EmojibaseShortcodeInterface::SUPPORTED_PRESETS
137
                    ))
138
                ));
139
            }
140
        }
141
142
        return \array_values(\array_unique($presets));
143
    }
144
145
    public function count(): int
146
    {
147
        return $this->getDataset()->count();
148
    }
149
150
    public function current(): Emoji
151
    {
152
        return $this->getDataset()->current();
153
    }
154
155
    /**
156
     * @param callable(Emoji):bool $callback
157
     */
158
    public function filter(callable $callback): RuntimeDataset
159
    {
160
        return new self($this->config, $this->getDataset()->filter($callback));
161
    }
162
163
    public function getDataset(): Dataset
164
    {
165
        if ($this->dataset === null) {
166
            $locale     = $this->getLocale();
167
            $presets    = $this->getPresets();
168
            $throwables = [];
169
            $presets    = \array_filter($presets);
170
            $remaining  = $presets;
171
            while (\count($remaining) > 0) {
172
                $preset = \array_shift($remaining);
173
                try {
174
                    $this->dataset = self::unarchive(\sprintf('%s/%s/%s.gz', Dataset::DIRECTORY, $locale, $preset));
175
                    break;
176
                } catch (\Throwable $throwable) {
177
                    $throwables[$preset] = $throwable;
178
                }
179
            }
180
181
            if ($this->dataset === null) {
182
                throw new LocalePresetException($locale, $throwables);
183
            }
184
        }
185
186
        return $this->dataset;
187
    }
188
189
    public function getLocale(): string
190
    {
191
        if ($this->locale === null) {
192
            $this->locale = (string) ($this->config->get('local') ?? self::DEFAULT);
193
        }
194
195
        return $this->locale;
196
    }
197
198
    /**
199
     * @return string[]
200
     */
201
    public function getPresets(): array
202
    {
203
        if ($this->presets === null) {
204
            /** @var string[] $presets */
205
            $presets = (array) $this->config->get('preset');
206
207
            // Prepend the native preset if local is requires it and enabled.
208
            if ($this->isNative()) {
209
                \array_unshift($presets, EmojibaseShortcodeInterface::PRESET_CLDR_NATIVE);
210
            }
211
212
            $this->presets = $presets;
213
        }
214
215
        return $this->presets;
216
    }
217
218
    public function indexBy(string $index = 'hexcode'): RuntimeDataset
219
    {
220
        return new self($this->config, $this->getDataset()->indexBy($index));
221
    }
222
223
    public function isNative(): bool
224
    {
225
        if ($this->native === null) {
226
            $locale  = $this->getLocale();
227
            $default = \in_array($locale, EmojibaseDatasetInterface::NON_LATIN_LOCALES, true);
228
229
            /** @var ?bool $native */
230
            $native = $this->config->get('native');
231
232
            $this->native = $native === null
233
                ? $default
234
                : $native && $default;
235
        }
236
237
        return $this->native;
238
    }
239
240
    public function key(): string
241
    {
242
        return (string) $this->getDataset()->key();
243
    }
244
245
    public function next(): void
246
    {
247
        $this->getDataset()->next();
248
    }
249
250
    /** @param string $offset */
251
    public function offsetExists($offset): bool // phpcs:ignore
252
    {
253
        return $this->getDataset()->offsetExists($offset);
254
    }
255
256
    /** @param string $offset */
257
    public function offsetGet($offset): ?Emoji // phpcs:ignore
258
    {
259
        return $this->getDataset()->offsetGet($offset);
260
    }
261
262
    /**
263
     * @param string $offset
264
     * @param mixed  $value
265
     */
266
    public function offsetSet($offset, $value): void // phpcs:ignore
267
    {
268
        $this->getDataset()->offsetSet($offset, $value);
269
    }
270
271
    /** @param string $offset */
272
    public function offsetUnset($offset): void // phpcs:ignore
273
    {
274
        $this->getDataset()->offsetUnset($offset);
275
    }
276
277
    public function rewind(): void
278
    {
279
        $this->getDataset()->rewind();
280
    }
281
282
    /** @param int $position */
283
    public function seek($position): void // phpcs:ignore
284
    {
285
        $this->getDataset()->seek($position);
286
    }
287
288
    public function valid(): bool
289
    {
290
        return $this->getDataset()->valid();
291
    }
292
}
293