Test Setup Failed
Pull Request — latest (#3)
by Mark
33:50
created

RuntimeDataset::unarchive()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 13
c 1
b 0
f 1
dl 0
loc 25
rs 9.2222
cc 6
nc 5
nop 1
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
                throw new LocalePresetException($locale, $throwables);
126
            }
127
        }
128
129
        return $this->dataset;
130
    }
131
132
    public function getLocale(): string
133
    {
134
        if ($this->locale === null) {
135
            $this->locale = (string) ($this->config->get('locale') ?? self::DEFAULT);
136
        }
137
138
        return $this->locale;
139
    }
140
141
    /**
142
     * @return string[]
143
     */
144
    public function getPresets(): array
145
    {
146
        if ($this->presets === null) {
147
            /** @var string[] $presets */
148
            $presets = (array) $this->config->get('preset');
149
150
            // Prepend the native preset if local is requires it and enabled.
151
            if ($this->isNative()) {
152
                \array_unshift($presets, EmojibaseShortcodeInterface::PRESET_CLDR_NATIVE);
153
            } else {
154
                /** @var int|false $key */
155
                $key = \array_search(EmojibaseShortcodeInterface::PRESET_CLDR_NATIVE, $presets, true);
156
157
                // Only remove the CLDR native preset if it's not the only one provided.
158
                if ($key !== false && \count($presets) !== 1) {
159
                    \array_splice($presets, $key, 1);
160
                }
161
            }
162
163
            $this->presets = \array_filter(\array_values(\array_unique(\array_filter($presets))));
164
        }
165
166
        return $this->presets;
167
    }
168
169
    public function indexBy(string $index = 'hexcode'): RuntimeDataset
170
    {
171
        return new self($this->config, $this->getDataset()->indexBy($index));
172
    }
173
174
    public function isNative(): bool
175
    {
176
        if ($this->native === null) {
177
            $locale  = $this->getLocale();
178
            $default = \in_array($locale, EmojibaseDatasetInterface::NON_LATIN_LOCALES, true);
179
180
            /** @var ?bool $native */
181
            $native = $this->config->get('native');
182
183
            $this->native = $native === null
184
                ? $default
185
                : $native && $default;
186
        }
187
188
        return $this->native;
189
    }
190
191
    public function key(): string
192
    {
193
        return (string) $this->getDataset()->key();
194
    }
195
196
    public function next(): void
197
    {
198
        $this->getDataset()->next();
199
    }
200
201
    /** @param string $offset */
202
    public function offsetExists($offset): bool // phpcs:ignore
203
    {
204
        return $this->getDataset()->offsetExists($offset);
205
    }
206
207
    /** @param string $offset */
208
    public function offsetGet($offset): ?Emoji // phpcs:ignore
209
    {
210
        return $this->getDataset()->offsetGet($offset);
211
    }
212
213
    /**
214
     * @param string $offset
215
     * @param mixed  $value
216
     */
217
    public function offsetSet($offset, $value): void // phpcs:ignore
218
    {
219
        throw new \BadMethodCallException('Unable to modify immutable object.');
220
    }
221
222
    /** @param string $offset */
223
    public function offsetUnset($offset): void // phpcs:ignore
224
    {
225
        throw new \BadMethodCallException('Unable to modify immutable object.');
226
    }
227
228
    public function rewind(): void
229
    {
230
        $this->getDataset()->rewind();
231
    }
232
233
    /** @param int $position */
234
    public function seek($position): void // phpcs:ignore
235
    {
236
        $this->getDataset()->seek($position);
237
    }
238
239
    public function valid(): bool
240
    {
241
        return $this->getDataset()->valid();
242
    }
243
}
244