Passed
Push — main ( 1a7f0e...7d39cf )
by Oscar
03:17
created

Icon   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 195
Duplicated Lines 0 %

Test Coverage

Coverage 92.66%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 18
eloc 103
c 1
b 0
f 0
dl 0
loc 195
ccs 101
cts 109
cp 0.9266
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getHtmlTag() 0 26 4
A getName() 0 3 1
A getProcessors() 0 15 1
A configureOptions() 0 58 3
A getStyleClass() 0 5 2
A __construct() 0 41 5
A getFaId() 0 6 1
A getStyle() 0 6 1
1
<?php
2
3
/*
4
 * This file is part of ocubom/twig-svg-extension
5
 *
6
 * © Oscar Cubo Medina <https://ocubom.github.io>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ocubom\Twig\Extension\Svg\Library\FontAwesome;
13
14
use function BenTools\IterableFunctions\iterable_to_array;
15
16
use Ocubom\Twig\Extension\Svg\Exception\ParseException;
17
use Ocubom\Twig\Extension\Svg\Library\FontAwesome;
18
use Ocubom\Twig\Extension\Svg\Processor\ClassProcessor;
19
use Ocubom\Twig\Extension\Svg\Processor\RemoveAttributeProcessor;
20
use Ocubom\Twig\Extension\Svg\Svg;
21
use Ocubom\Twig\Extension\Svg\Util\DomHelper;
22
use Symfony\Component\OptionsResolver\Options;
23
use Symfony\Component\OptionsResolver\OptionsResolver;
24
25
class Icon extends Svg
26
{
27
    /**
28
     * Full path to the icon.
29
     */
30
    protected \SplFileInfo $path;
31
32
    /**
33
     * @param mixed $data The FontAwesome Icon data
34
     */
35 2
    public function __construct($data, iterable $options = null)
36
    {
37
        try {
38
            switch (true) {
39 2
                case $data instanceof Icon: // "Copy" constructor
40
                    $this->path = $data->path;
41
                    break;
42
43 2
                case $data instanceof \SplFileInfo:
44 2
                    $this->path = $data;
45 2
                    break;
46
47
                case is_string($data):
0 ignored issues
show
Unused Code introduced by
is_string($data) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
48
                    $this->path = new \SplFileInfo($data);
49
                    break;
50
51
                default:
52
                    throw new ParseException(sprintf('Unable to create "%s" from "%s"', __CLASS__, get_debug_type($data)));
53
            }
54
55
            /** @var array $options */
56 2
            $options = iterable_to_array($options ?? []);
57
58
            // Construct from path
59 2
            parent::__construct($this->path, array_merge($options, [
60 2
                'class_default' => array_merge($options['class_default'] ?? [], [
61 2
                    FontAwesome::INLINE_CLASS, // Add inlined class
62 2
                    'fa-'.$this->getName(), // Add icon name class
63 2
                ]),
64 2
                'class_block' => array_merge($options['class_block'] ?? [], [
65 2
                    $this->getStyle(), // Block style
66 2
                    $this->getStyleClass(), // Block current classes
67 2
                    $this->getStyleClass('5.0'), // Block pre-6.0 classes
68 2
                ]),
69 2
            ]));
70
71
            // Add Font Awesome data-*
72 2
            $this->svg->setAttribute('data-prefix', $this->getStyleClass('5.0'));
73 2
            $this->svg->setAttribute('data-icon', $this->getName());
74
        } catch (ParseException $exc) {
75
            throw new ParseException(sprintf('Unable to create a FontAwesome Icon from "%s"', get_debug_type($data)), 0, $exc);
76
        }
77
    }
78
79
    /**
80
     * @codeCoverageIgnore
81
     */
82
    public function getFaId(): string
83
    {
84
        return sprintf(
85
            'fa-%s-%s',
86
            $this->getStyle(),
87
            $this->getName(),
88
        );
89
    }
90
91 2
    public function getName(): string
92
    {
93 2
        return $this->path->getBasename('.svg');
94
    }
95
96 2
    public function getStyle(): string
97
    {
98 2
        $path = $this->path->getPathInfo();
99
        assert($path instanceof \SplFileInfo);
100
101 2
        return $path->getBasename();
102
    }
103
104 2
    public function getStyleClass(string $version = '6.0'): string
105
    {
106 2
        return version_compare($version, '6.0', '<')
107 2
            ? 'fa'.$this->getStyle()[0]
108 2
            : 'fa-'.$this->getStyle();
109
    }
110
111 1
    public function getHtmlTag(iterable $options = null): \DOMElement
112
    {
113
        // Create the HTML Tag node
114 1
        $node = DomHelper::createElement(FontAwesome::HTML_TAG);
115
        // Copy options as attributes
116 1
        foreach ($options ?? [] as $key => $val) {
117 1
            if (!empty($val)) {
118 1
                $val = is_iterable($val) ? implode(' ', iterable_to_array($val)) : (string) $val;
119
120 1
                $node->setAttribute($key, $val);
121
            }
122
        }
123
124
        // Process classes
125 1
        $processor = new ClassProcessor();
126 1
        $processor($node, [
127 1
            'class' => [
128 1
                $this->getStyleClass(),
129 1
                'fa-'.$this->getName(),
130 1
            ],
131 1
            'class_banned' => [
132 1
                FontAwesome::INLINE_CLASS,
133 1
            ],
134 1
        ]);
135
136 1
        return $node;
137
    }
138
139
    /**
140
     * @return array<string, array<int, callable>|callable>
141
     *
142
     * @psalm-suppress InvalidScope
143
     */
144 1
    protected static function getProcessors(): array
145
    {
146 1
        return array_merge(parent::getProcessors(), [
147
            // Options will be ignored & removed
148 1
            'class_default' => new RemoveAttributeProcessor('class_default'),
149 1
            'class_block' => new RemoveAttributeProcessor('class_block'),
150 1
            'fill' => new RemoveAttributeProcessor('fill'),
151 1
            'opacity' => new RemoveAttributeProcessor('opacity'),
152 1
            'primary_fill' => new RemoveAttributeProcessor('primary_fill'),
153 1
            'primary_opacity' => new RemoveAttributeProcessor('primary_opacity'),
154 1
            'secondary_fill' => new RemoveAttributeProcessor('secondary_fill'),
155 1
            'secondary_opacity' => new RemoveAttributeProcessor('secondary_opacity'),
156
157
            // Remove special attributes
158 1
            'data-fa-title-id' => new RemoveAttributeProcessor('data-fa-title-id'),
159 1
        ]);
160
    }
161
162 2
    public static function configureOptions(OptionsResolver $resolver = null): OptionsResolver
163
    {
164 2
        $resolver = parent::configureOptions($resolver);
165
166 2
        $resolver->define('class_default')
167 2
            ->default([])
168 2
            ->allowedTypes('string[]')
169 2
            ->info('Default classes to add unless null');
170
171 2
        $resolver->define('fill')
172 2
            ->default('currentColor')
173 2
            ->allowedTypes('null', 'string')
174 2
            ->info('Default fill color for paths');
175
176 2
        $resolver->define('opacity')
177 2
            ->default(null)
178 2
            ->allowedTypes('null', 'float')
179 2
            ->info('Default opacity color for paths');
180
181 2
        $resolver->define('primary_fill')
182 2
            ->default(null)
183 2
            ->allowedTypes('null', 'string')
184 2
            ->info('Default fill color for primary paths (duotone)');
185
186 2
        $resolver->define('primary_opacity')
187 2
            ->default(null)
188 2
            ->allowedTypes('null', 'float')
189 2
            ->info('Default opacity color for primary paths (duotone)');
190
191 2
        $resolver->define('secondary_fill')
192 2
            ->default(null)
193 2
            ->allowedTypes('null', 'string')
194 2
            ->info('Default fill color for secondary paths (duotone)');
195
196 2
        $resolver->define('secondary_opacity')
197 2
            ->default(null)
198 2
            ->allowedTypes('null', 'float')
199 2
            ->info('Default opacity color for secondary paths (duotone)');
200
201 2
        $resolver->define('data-fa-title-id')
202 2
            ->default(null)
203 2
            ->allowedTypes('null', 'string')
204 2
            ->info('Set the icon title id instead of generate a new one');
205
206
        // Uses data-fa-title-id as aria-labelledby if not defined
207 2
        $resolver->addNormalizer(
208 2
            'aria-labelledby',
209 2
            function (Options $options, ?string $value): ?string {
210 2
                if (empty($value) && !empty($options['data-fa-title-id'])) {
211 1
                    return $options['data-fa-title-id'];
212
                }
213
214 2
                return $value;
215 2
            },
216 2
            true
217 2
        );
218
219 2
        return $resolver;
220
    }
221
}
222