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

Environment::createDefaultConfiguration()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 128
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 72
c 0
b 0
f 0
dl 0
loc 128
rs 7.6775
cc 7
nc 1
nop 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A Environment::defaultExtensions() 0 3 1
A Environment::normalizeLocale() 0 29 5
A Environment::normalizeConvert() 0 7 2
A Environment::__construct() 0 4 1
A Environment::create() 0 9 2
A Environment::normalizePresets() 0 10 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file was originally part of the league/commonmark package.
7
 *
8
 * (c) Colin O'Dell <[email protected]>
9
 *
10
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
11
 *  - (c) John MacFarlane
12
 *
13
 * For the full copyright and license information, please view the LICENSE
14
 * file that was distributed with this source code.
15
 */
16
17
namespace UnicornFail\Emoji\Environment;
18
19
use League\Configuration\Configuration;
20
use League\Configuration\ConfigurationAwareInterface;
21
use League\Configuration\ConfigurationInterface;
22
use Nette\Schema\Expect;
23
use Nette\Schema\Schema;
24
use Psr\EventDispatcher\EventDispatcherInterface;
25
use Psr\EventDispatcher\StoppableEventInterface;
26
use UnicornFail\Emoji\Dataset\RuntimeDataset;
27
use UnicornFail\Emoji\EmojiConverterInterface;
28
use UnicornFail\Emoji\Emojibase\EmojibaseDatasetInterface;
29
use UnicornFail\Emoji\Emojibase\EmojibaseShortcodeInterface;
30
use UnicornFail\Emoji\Event\ListenerData;
31
use UnicornFail\Emoji\Extension\ConfigurableExtensionInterface;
32
use UnicornFail\Emoji\Extension\ConfigureConversionTypesInterface;
33
use UnicornFail\Emoji\Extension\EmojiCoreExtension;
34
use UnicornFail\Emoji\Extension\ExtensionInterface;
35
use UnicornFail\Emoji\Renderer\NodeRendererInterface;
36
use UnicornFail\Emoji\Util\PrioritizedList;
37
38
final class Environment implements EnvironmentBuilderInterface
39
{
40
    /** @var Configuration */
41
    private $config;
42
43
    /** @var ?RuntimeDataset */
44
    private $dataset;
45
46
    /** @var ?EventDispatcherInterface */
47
    private $eventDispatcher;
48
49
    /**
50
     * @var ExtensionInterface[]
51
     *
52
     * @psalm-readonly-allow-private-mutation
53
     */
54
    private $extensions = [];
55
56
    /**
57
     * @var bool
58
     *
59
     * @psalm-readonly-allow-private-mutation
60
     */
61
    private $initialized = false;
62
63
    /**
64
     * @var ?PrioritizedList<ListenerData>
65
     *
66
     * @psalm-readonly-allow-private-mutation
67
     */
68
    private $listenerData;
69
70
    /**
71
     * @var array<string, PrioritizedList<NodeRendererInterface>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, Prioritize...NodeRendererInterface>> at position 4 could not be parsed: Expected '>' at position 4, but found 'PrioritizedList'.
Loading history...
72
     *
73
     * @psalm-readonly-allow-private-mutation
74
     */
75
    private $renderersByClass = [];
76
77
    /**
78
     * @var ExtensionInterface[]
79
     *
80
     * @psalm-readonly-allow-private-mutation
81
     */
82
    private $uninitializedExtensions = [];
83
84
    /**
85
     * @param array<string, mixed> $config
86
     */
87
    public function __construct(array $config = [])
88
    {
89
        $this->config = new Configuration();
90
        $this->config->merge($config);
91
    }
92
93
    /**
94
     * @param array<string, mixed> $configuration
95
     */
96
    public static function create(array $configuration = []): self
97
    {
98
        $environment = new self($configuration);
99
100
        foreach (self::defaultExtensions() as $extension) {
101
            $environment->addExtension($extension);
102
        }
103
104
        return $environment;
105
    }
106
107
    /**
108
     * @param string|string[] $value
109
     *
110
     * @return string[]
111
     */
112
    public static function normalizeConvert($value): array
113
    {
114
        if (\is_array($value)) {
115
            return $value;
116
        }
117
118
        return \array_fill_keys(EmojiConverterInterface::TYPES, $value);
119
    }
120
121
    /**
122
     * @return ExtensionInterface[]
123
     */
124
    protected static function defaultExtensions(): iterable
125
    {
126
        return [new EmojiCoreExtension()];
127
    }
128
129
    public static function normalizeLocale(string $locale): string
130
    {
131
        /** @var string[] $normalized */
132
        static $normalized = [];
133
134
        // Immediately return if locale is an exact match.
135
        if (\in_array($locale, EmojibaseDatasetInterface::SUPPORTED_LOCALES, true)) {
136
            $normalized[$locale] = $locale;
137
        }
138
139
        // Immediately return if this local has already been normalized.
140
        if (isset($normalized[$locale])) {
141
            return $normalized[$locale];
142
        }
143
144
        $original              = $locale;
145
        $normalized[$original] = 'en';
146
147
        // Otherwise, see if it just needs some TLC.
148
        $locale = \strtolower($locale);
149
        $locale = \preg_replace('/[^a-z]/', '-', $locale) ?? $locale;
150
        foreach ([$locale, \current(\explode('-', $locale, 2))] as $locale) {
151
            if (\in_array($locale, EmojibaseDatasetInterface::SUPPORTED_LOCALES, true)) {
152
                $normalized[$original] = $locale;
153
                break;
154
            }
155
        }
156
157
        return $normalized[$original];
158
    }
159
160
    /**
161
     * @param string|string[] $presets
162
     *
163
     * @return string[]
164
     */
165
    public static function normalizePresets($presets): ?array
166
    {
167
        // Map preset aliases to their correct value.
168
        return \array_unique(\array_filter(\array_map(static function (string $preset): string {
169
            if (isset(EmojibaseShortcodeInterface::PRESET_ALIASES[$preset])) {
170
                return EmojibaseShortcodeInterface::PRESET_ALIASES[$preset];
171
            }
172
173
            return $preset;
174
        }, \array_values((array) $presets))));
175
    }
176
177
    public function addEventListener(string $eventClass, callable $listener, int $priority = 0): EnvironmentBuilderInterface
178
    {
179
        $this->assertUninitialized('Failed to add event listener.');
180
181
        if ($this->listenerData === null) {
182
            /** @var PrioritizedList<ListenerData> $listenerData */
183
            $listenerData       = new PrioritizedList();
184
            $this->listenerData = $listenerData;
185
        }
186
187
        $this->listenerData->add(new ListenerData($eventClass, $listener), $priority);
188
189
        $object = \is_array($listener)
190
            ? $listener[0]
191
            : $listener;
192
193
        if ($object instanceof EnvironmentAwareInterface) {
194
            $object->setEnvironment($this);
195
        }
196
197
        if ($object instanceof ConfigurationAwareInterface) {
198
            $object->setConfiguration($this->getConfiguration());
199
        }
200
201
        return $this;
202
    }
203
204
    public function addExtension(ExtensionInterface $extension): EnvironmentBuilderInterface
205
    {
206
        $this->assertUninitialized('Failed to add extension.');
207
208
        $this->extensions[]              = $extension;
209
        $this->uninitializedExtensions[] = $extension;
210
211
        if ($extension instanceof ConfigurableExtensionInterface) {
212
            $extension->configureSchema($this->config, $this->config->data());
213
        }
214
215
        return $this;
216
    }
217
218
    public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0): EnvironmentBuilderInterface
219
    {
220
        $this->assertUninitialized('Failed to add renderer.');
221
222
        if (! isset($this->renderersByClass[$nodeClass])) {
223
            /** @var PrioritizedList<NodeRendererInterface> $renderers */
224
            $renderers = new PrioritizedList();
225
226
            $this->renderersByClass[$nodeClass] = $renderers;
227
        }
228
229
        $this->renderersByClass[$nodeClass]->add($renderer, $priority);
230
231
        if ($renderer instanceof ConfigurationAwareInterface) {
232
            $renderer->setConfiguration($this->getConfiguration());
233
        }
234
235
        return $this;
236
    }
237
238
    /**
239
     * @throws \RuntimeException
240
     */
241
    protected function assertUninitialized(string $message): void
242
    {
243
        if ($this->initialized) {
244
            throw new \RuntimeException(\sprintf('%s The Environment has already been initialized.', $message));
245
        }
246
    }
247
248
    /**
249
     * {@inheritDoc}
250
     */
251
    public function dispatch(object $event)
252
    {
253
        $this->initialize();
254
255
        if ($this->eventDispatcher !== null) {
256
            return $this->eventDispatcher->dispatch($event);
257
        }
258
259
        foreach ($this->getListenersForEvent($event) as $listener) {
260
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
261
                return $event;
262
            }
263
264
            $listener($event);
265
        }
266
267
        return $event;
268
    }
269
270
    public function getConfiguration(): ConfigurationInterface
271
    {
272
        $this->initializeConfiguration();
273
274
        return $this->config->reader();
275
    }
276
277
    public function getRuntimeDataset(string $index = 'hexcode'): RuntimeDataset
278
    {
279
        $this->initialize();
280
281
        if ($this->dataset === null) {
282
            $this->dataset = new RuntimeDataset($this->getConfiguration());
283
        }
284
285
        return $this->dataset->indexBy($index);
286
    }
287
288
    /**
289
     * {@inheritDoc}
290
     *
291
     * @return ExtensionInterface[]
292
     */
293
    public function getExtensions(): iterable
294
    {
295
        return $this->extensions;
296
    }
297
298
    /**
299
     * {@inheritDoc}
300
     *
301
     * @return iterable<callable>
302
     */
303
    public function getListenersForEvent(object $event): iterable
304
    {
305
        if ($this->listenerData === null) {
306
            /** @var PrioritizedList<ListenerData> $listenerData */
307
            $listenerData       = new PrioritizedList();
308
            $this->listenerData = $listenerData;
309
        }
310
311
        /** @var ListenerData $listenerData */
312
        foreach ($this->listenerData as $listenerData) {
313
            if (! \is_a($event, $listenerData->getEvent())) {
314
                continue;
315
            }
316
317
            yield function (object $event) use ($listenerData): void {
318
                $this->initialize();
319
320
                \call_user_func($listenerData->getListener(), $event);
321
            };
322
        }
323
    }
324
325
    /**
326
     * {@inheritDoc}
327
     */
328
    public function getRenderersForClass(string $nodeClass): iterable
329
    {
330
        $this->initialize();
331
332
        // If renderers are defined for this specific class, return them immediately
333
        if (isset($this->renderersByClass[$nodeClass])) {
334
            return $this->renderersByClass[$nodeClass];
335
        }
336
337
        while (\class_exists($parent = (string) ($parent ?? $nodeClass)) && ($parent = \get_parent_class($parent))) {
338
            if (! isset($this->renderersByClass[$parent])) {
339
                continue;
340
            }
341
342
            // "Cache" this result to avoid future loops
343
            return $this->renderersByClass[$nodeClass] = $this->renderersByClass[$parent];
344
        }
345
346
        return [];
347
    }
348
349
    protected function initialize(): void
350
    {
351
        if ($this->initialized) {
352
            return;
353
        }
354
355
        $this->initializeConfiguration();
356
357
        $this->initializeExtensions();
358
359
        $this->initialized = true;
360
    }
361
362
    protected function initializeConfiguration(): void
363
    {
364
        $this->config->addSchema('allow_unsafe_links', Expect::bool(true));
365
366
        $default = EmojiConverterInterface::UNICODE;
367
368
        /** @var string[] $conversionTypes */
369
        $conversionTypes = (array) EmojiConverterInterface::TYPES;
370
371
        foreach ($this->extensions as $extension) {
372
            if ($extension instanceof ConfigureConversionTypesInterface) {
373
                $extension->configureConversionTypes($default, $conversionTypes, $this->config->data());
374
            }
375
        }
376
377
        $conversionTypes = \array_unique($conversionTypes);
378
379
        $structuredConversionTypes = Expect::structure(\array_combine(
380
            EmojiConverterInterface::TYPES,
381
            \array_map(static function (string $conversionType) use ($conversionTypes, $default): Schema {
0 ignored issues
show
Unused Code introduced by
The parameter $conversionType is not used and could be removed. ( Ignorable by Annotation )

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

381
            \array_map(static function (/** @scrutinizer ignore-unused */ string $conversionType) use ($conversionTypes, $default): Schema {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
382
                return Expect::anyOf(false, ...$conversionTypes)->default($default)->nullable();
383
            }, EmojiConverterInterface::TYPES)
384
        ))->castTo('array');
385
386
        $this->config->addSchema('convert', Expect::anyOf($structuredConversionTypes, ...$conversionTypes)
387
            ->default(\array_fill_keys(EmojiConverterInterface::TYPES, $default))
388
            ->before('\UnicornFail\Emoji\Environment\Environment::normalizeConvert'));
389
390
        $this->config->addSchema('exclude', Expect::structure([
391
            'shortcodes' => Expect::arrayOf('string')
392
                ->default([])
393
                ->before('\UnicornFail\Emoji\Util\Normalize::shortcodes'),
394
        ])->castTo('array'));
395
396
        $this->config->addSchema('locale', Expect::anyOf(...EmojibaseDatasetInterface::SUPPORTED_LOCALES)
397
            ->default('en')
398
            ->before('\UnicornFail\Emoji\Environment\Environment::normalizeLocale'));
399
400
        $this->config->addSchema('native', Expect::bool()->nullable());
401
402
        $this->config->addSchema('presentation', Expect::anyOf(...EmojibaseDatasetInterface::SUPPORTED_PRESENTATIONS)
403
            ->default(EmojibaseDatasetInterface::EMOJI));
404
405
        $this->config->addSchema('preset', Expect::anyOf(Expect::listOf(Expect::anyOf(...EmojibaseShortcodeInterface::SUPPORTED_PRESETS)), ...EmojibaseShortcodeInterface::SUPPORTED_PRESETS)
406
            ->default(EmojibaseShortcodeInterface::DEFAULT_PRESETS)
407
            ->before('\UnicornFail\Emoji\Environment\Environment::normalizePresets'));
408
    }
409
410
    protected function initializeExtensions(): void
411
    {
412
        // Ask all extensions to register their components.
413
        while (\count($this->uninitializedExtensions) > 0) {
414
            foreach ($this->uninitializedExtensions as $i => $extension) {
415
                $extension->register($this);
416
                unset($this->uninitializedExtensions[$i]);
417
            }
418
        }
419
    }
420
421
    public function setEventDispatcher(EventDispatcherInterface $dispatcher): void
422
    {
423
        $this->eventDispatcher = $dispatcher;
424
    }
425
}
426