Test Setup Failed
Pull Request — latest (#3)
by Mark
32:07
created

RuntimeDataset::next()   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 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UnicornFail\Emoji\Dataset;
6
7
use League\Configuration\ConfigurationInterface;
8
use UnicornFail\Emoji\Emojibase\EmojibaseDatasetInterface;
9
use UnicornFail\Emoji\Emojibase\EmojibaseShortcodeInterface;
10
use UnicornFail\Emoji\Exception\FileNotFoundException;
11
use UnicornFail\Emoji\Exception\LocalePresetException;
12
use UnicornFail\Emoji\Exception\MalformedArchiveException;
13
use UnicornFail\Emoji\Exception\UnarchiveException;
14
use UnicornFail\Emoji\Parser\EmojiParser;
15
16
final class RuntimeDataset implements \ArrayAccess, \Countable, \SeekableIterator
17
{
18
    public const DEFAULT = 'en';
19
20
    /** @var ConfigurationInterface */
21
    private $config;
22
23
    /** @var ?Dataset */
24
    private $dataset;
25
26
    /** @var ?string */
27
    private $locale;
28
29
    /** @var ?bool */
30
    private $native;
31
32
    /** @var ?string[] */
33
    private $presets;
34
35
    public function __construct(ConfigurationInterface $configuration, ?Dataset $dataset = null)
36
    {
37
        $this->config  = $configuration;
38
        $this->dataset = $dataset;
39
    }
40
41
    /**
42
     * @param string[] $indices
43
     *
44
     * @return false|string
45
     */
46
    public static function archive(Dataset $dataset, array $indices = EmojiParser::INDICES)
47
    {
48
        foreach ($indices as $index) {
49
            $dataset->indexBy($index);
50
        }
51
52
        $serialize = \serialize($dataset);
53
54
        return \gzencode($serialize, 9);
55
    }
56
57
    public static function unarchive(string $filename): Dataset
58
    {
59
        if (! \file_exists($filename)) {
60
            throw new FileNotFoundException($filename);
61
        }
62
63
        if (
64
            ! ($contents = \file_get_contents($filename)) ||
65
            ! ($decoded = \gzdecode($contents))
66
        ) {
67
            throw new UnarchiveException($filename);
68
        }
69
70
        try {
71
            /** @var ?Dataset $dataset */
72
            $dataset = \unserialize((string) $decoded);
73
        } catch (\Throwable $throwable) {
74
            throw new MalformedArchiveException($filename, $throwable);
75
        }
76
77
        if (! $dataset instanceof Dataset) {
78
            throw new MalformedArchiveException($filename);
79
        }
80
81
        return $dataset;
82
    }
83
84
    public function count(): int
85
    {
86
        return $this->getDataset()->count();
87
    }
88
89
    public function current(): ?Emoji
90
    {
91
        /** @var ?Emoji $current */
92
        $current = $this->getDataset()->current();
93
94
        return $current;
95
    }
96
97
    /**
98
     * @param callable(Emoji):bool $callback
99
     */
100
    public function filter(callable $callback): RuntimeDataset
101
    {
102
        return new self($this->config, $this->getDataset()->filter($callback));
103
    }
104
105
    public function getDataset(): Dataset
106
    {
107
        if ($this->dataset === null) {
108
            /** @var \Throwable[] $throwables */
109
            $throwables = [];
110
            $locale     = $this->getLocale();
111
            $presets    = $this->getPresets();
112
113
            $remaining = $presets;
114
            while (\count($remaining) > 0) {
115
                $preset = \array_shift($remaining);
116
                try {
117
                    $this->dataset = self::unarchive(\sprintf('%s/%s/%s.gz', Dataset::DIRECTORY, $locale, $preset));
118
                    break;
119
                } catch (\Throwable $throwable) {
120
                    $throwables[$preset] = $throwable;
121
                }
122
            }
123
124
            if ($this->dataset === null) {
125
                if ($this->config->data()->has('locale')) {
126
                    $locale = (string) $this->config->data()->get('locale');
127
                }
128
129
                throw new LocalePresetException($locale, $throwables);
130
            }
131
        }
132
133
        return $this->dataset;
134
    }
135
136
    public function getLocale(): string
137
    {
138
        if ($this->locale === null) {
139
            $this->locale = (string) ($this->config->get('locale') ?? self::DEFAULT);
140
        }
141
142
        return $this->locale;
143
    }
144
145
    /**
146
     * @return string[]
147
     */
148
    public function getPresets(): array
149
    {
150
        if ($this->presets === null) {
151
            /** @var string[] $presets */
152
            $presets = (array) $this->config->get('preset');
153
154
            // Prepend the native preset if local is requires it and enabled.
155
            if ($this->isNative()) {
156
                \array_unshift($presets, EmojibaseShortcodeInterface::PRESET_CLDR_NATIVE);
157
            } else {
158
                /** @var int|false $key */
159
                $key = \array_search(EmojibaseShortcodeInterface::PRESET_CLDR_NATIVE, $presets, true);
160
161
                // Only remove the CLDR native preset if it's not the only one provided.
162
                if ($key !== false && \count($presets) !== 1) {
163
                    \array_splice($presets, $key, 1);
164
                }
165
            }
166
167
            $this->presets = \array_filter(\array_values(\array_unique(\array_filter($presets))));
168
        }
169
170
        return $this->presets;
171
    }
172
173
    public function indexBy(string $index = 'hexcode'): RuntimeDataset
174
    {
175
        return new self($this->config, $this->getDataset()->indexBy($index));
176
    }
177
178
    public function isNative(): bool
179
    {
180
        if ($this->native === null) {
181
            $locale  = $this->getLocale();
182
            $default = \in_array($locale, EmojibaseDatasetInterface::NON_LATIN_LOCALES, true);
183
184
            /** @var ?bool $native */
185
            $native = $this->config->get('native');
186
187
            $this->native = $native === null
188
                ? $default
189
                : $native && $default;
190
        }
191
192
        return $this->native;
193
    }
194
195
    public function key(): string
196
    {
197
        return (string) $this->getDataset()->key();
198
    }
199
200
    public function next(): void
201
    {
202
        $this->getDataset()->next();
203
    }
204
205
    /** @param string $offset */
206
    public function offsetExists($offset): bool // phpcs:ignore
207
    {
208
        return $this->getDataset()->offsetExists($offset);
209
    }
210
211
    /** @param string $offset */
212
    public function offsetGet($offset): ?Emoji // phpcs:ignore
213
    {
214
        return $this->getDataset()->offsetGet($offset);
215
    }
216
217
    /**
218
     * @param string $offset
219
     * @param mixed  $value
220
     */
221
    public function offsetSet($offset, $value): void // phpcs:ignore
222
    {
223
        throw new \BadMethodCallException('Unable to modify immutable object.');
224
    }
225
226
    /** @param string $offset */
227
    public function offsetUnset($offset): void // phpcs:ignore
228
    {
229
        throw new \BadMethodCallException('Unable to modify immutable object.');
230
    }
231
232
    public function rewind(): void
233
    {
234
        $this->getDataset()->rewind();
235
    }
236
237
    /** @param int $position */
238
    public function seek($position): void // phpcs:ignore
239
    {
240
        $this->getDataset()->seek($position);
241
    }
242
243
    public function valid(): bool
244
    {
245
        return $this->getDataset()->valid();
246
    }
247
}
248