Passed
Pull Request — latest (#3)
by Mark
35:03
created

Configuration::definePresetNormalize()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 19
rs 9.6111
cc 5
nc 8
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UnicornFail\Emoji\Configuration;
6
7
use Dflydev\DotAccessData\Data;
8
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
9
use Symfony\Component\OptionsResolver\Options;
10
use Symfony\Component\OptionsResolver\OptionsResolver;
11
use UnicornFail\Emoji\Emojibase\DatasetInterface;
12
use UnicornFail\Emoji\Emojibase\ShortcodeInterface;
13
use UnicornFail\Emoji\Exception\InvalidConfigurationException;
14
use UnicornFail\Emoji\Parser\Lexer;
15
use UnicornFail\Emoji\Util\Normalize;
16
17
class Configuration extends Data implements ConfigurationInterface
18
{
19
    /**
20
     * @param mixed[]|\Traversable $configuration
21
     */
22
    public function __construct(?iterable $configuration = null)
23
    {
24
        parent::__construct([]);
25
26
        /** @var string[] $data */
27
        $data = $configuration !== null ? (new \ArrayObject($configuration))->getArrayCopy() : [];
0 ignored issues
show
Bug introduced by
$configuration of type iterable is incompatible with the type array|object expected by parameter $input of ArrayObject::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

27
        $data = $configuration !== null ? (new \ArrayObject(/** @scrutinizer ignore-type */ $configuration))->getArrayCopy() : [];
Loading history...
28
        foreach ($data as $key => $value) {
29
            $this->set((string) $key, $value);
30
        }
31
32
        try {
33
            $resolver = new OptionsResolver();
34
            $this->configureOptions($resolver);
35
            $this->data = $resolver->resolve($this->data);
36
        } catch (\Throwable $throwable) {
37
            throw new InvalidConfigurationException($throwable->getMessage(), (int) $throwable->getCode(), $throwable->getPrevious());
38
        }
39
    }
40
41
    /**
42
     * @param mixed[]|\Traversable $configuration
43
     */
44
    public static function create(?iterable $configuration = null): ConfigurationInterface
45
    {
46
        if ($configuration instanceof ConfigurationInterface) {
47
            return $configuration;
48
        }
49
50
        return new self($configuration);
51
    }
52
53
    public function configureOptions(OptionsResolver $resolver): void
54
    {
55
        $this->defineConvertEmoticons($resolver);
56
        $this->defineExclude($resolver);
57
        $this->defineLocale($resolver);
58
        $this->defineNative($resolver);
59
        $this->definePresentation($resolver);
60
        $this->definePreset($resolver);
61
        $this->defineStringableType($resolver);
62
    }
63
64
    protected function defineConvertEmoticons(OptionsResolver $resolver): void
65
    {
66
        $resolver->define('convertEmoticons')
67
            ->allowedTypes('bool')
68
            ->default(true);
69
    }
70
71
    protected function defineExclude(OptionsResolver $resolver): void
72
    {
73
        $resolver->setDefault('exclude', static function (OptionsResolver $resolver): void {
74
            $resolver->define('shortcodes')
75
                ->allowedTypes('string', 'string[]')
76
                ->default([])
77
                ->normalize(
78
                /**
79
                 * @param string|string[] $value
80
                 *
81
                 * @return string[]
82
                 */
83
                    static function (Options $options, $value): array {
84
                        return Normalize::shortcodes($value);
85
                    }
86
                );
87
        });
88
    }
89
90
    protected function defineLocale(OptionsResolver $resolver): void
91
    {
92
        $resolver->define('locale')
93
            ->allowedTypes('string')
94
            ->allowedValues(static function (string $value): bool {
95
                return ! ! Normalize::locale($value);
96
            })
97
            ->default('en')
98
            ->normalize(static function (Options $options, string $value): string {
99
                return Normalize::locale($value);
100
            });
101
    }
102
103
    protected function defineNative(OptionsResolver $resolver): void
104
    {
105
        $resolver->define('native')
106
            ->allowedTypes('bool')
107
            ->default(static function (Options $options): bool {
108
                return \in_array($options['locale'], DatasetInterface::NON_LATIN_LOCALES, true);
109
            })
110
            ->normalize(static function (Options $options, bool $value) {
111
                return $value && \in_array($options['locale'], DatasetInterface::NON_LATIN_LOCALES, true);
112
            });
113
    }
114
115
    protected function definePresentation(OptionsResolver $resolver): void
116
    {
117
        $resolver->define('presentation')
118
            ->allowedTypes('int', 'null')
119
            ->allowedValues(...DatasetInterface::SUPPORTED_PRESENTATIONS)
120
            ->default(DatasetInterface::EMOJI);
121
    }
122
123
    protected function definePreset(OptionsResolver $resolver): void
124
    {
125
        $resolver->define('preset')
126
            ->allowedTypes('string', 'string[]')
127
            ->allowedValues(\Closure::fromCallable([$this, 'definePresetAllowedValues']))
128
            ->default(ShortcodeInterface::DEFAULT_PRESETS)
129
            ->normalize(\Closure::fromCallable([$this, 'definePresetNormalize']));
130
    }
131
132
    /**
133
     * @param mixed $values
134
     */
135
    protected function definePresetAllowedValues($values): bool
136
    {
137
        foreach ((array) $values as $value) {
138
            \assert(\is_string($value));
139
            if (! \in_array($value, ShortcodeInterface::SUPPORTED_PRESETS, true)) {
140
                throw new InvalidOptionsException(\sprintf(
141
                    'The option "preset" with value "%s" is invalid. Accepted values are: %s.',
142
                    $value,
143
                    \implode(', ', \array_map(static function ($s) {
144
                        return \sprintf('"%s"', $s);
145
                    }, ShortcodeInterface::SUPPORTED_PRESETS))
146
                ));
147
            }
148
        }
149
150
        return true;
151
    }
152
153
    /**
154
     * @param mixed $value
155
     *
156
     * @return string[]
157
     */
158
    protected function definePresetNormalize(Options $options, $value): array
159
    {
160
        // Presets.
161
        $presets = [];
162
        foreach ((array) $value as $preset) {
163
            \assert(\is_string($preset));
164
            if (isset(ShortcodeInterface::PRESET_ALIASES[$preset])) {
165
                $presets[] = ShortcodeInterface::PRESET_ALIASES[$preset];
166
            } elseif (isset(ShortcodeInterface::PRESETS[$preset])) {
167
                $presets[] = ShortcodeInterface::PRESETS[$preset];
168
            }
169
        }
170
171
        // Prepend the native preset if local is requires it and enabled.
172
        if ($options['native']) {
173
            \array_unshift($presets, ShortcodeInterface::PRESET_CLDR_NATIVE);
174
        }
175
176
        return \array_values(\array_unique($presets));
177
    }
178
179
    protected function defineStringableType(OptionsResolver $resolver): void
180
    {
181
        $resolver->define('stringableType')
182
            ->allowedTypes('string')
183
            ->allowedValues(Lexer::EMOTICON, Lexer::HTML_ENTITY, Lexer::SHORTCODE, Lexer::UNICODE)
184
            ->default(Lexer::UNICODE);
185
    }
186
187
    public function getIterator(): \ArrayObject
188
    {
189
        return new \ArrayObject($this->export());
190
    }
191
192
    /**
193
     * {@inheritDoc}
194
     */
195
    public function import(array $data, $clobber = true): void
196
    {
197
        $data = static::create(new \ArrayObject($data))->export();
198
        parent::import($data, $clobber);
199
    }
200
}
201