Passed
Push — main ( 4a07e7...c747e2 )
by Oscar
05:42
created

DomIdent::generate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 35
ccs 18
cts 18
cp 1
rs 9.7333
cc 2
nc 2
nop 2
crap 2
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\Util;
13
14
use function BenTools\IterableFunctions\iterable_to_array;
15
use function Ocubom\Math\base_convert;
16
17
use Symfony\Component\OptionsResolver\OptionsResolver;
18
19
class DomIdent
20
{
21
    private static ?OptionsResolver $resolver = null;
22
23
    private const REPLACEMENTS = [
24
        '> <' => '><',
25
        '" >' => '">',
26
        '\' >' => '\'>',
27
    ];
28
29 6
    public static function generate(
30
        \DOMElement $element,
31
        iterable $options = null
32
    ): string {
33
        /* @psalm-suppress RedundantPropertyInitializationCheck */
34 6
        self::$resolver = self::$resolver ?? self::configureOptions();
35
36
        // Resolve options
37 6
        $options = self::$resolver->resolve(iterable_to_array($options ?? []));
38
39
        // Work on an optimized clone
40 6
        $node = DomHelper::cloneElement($element);
41
        // Remove identifier
42 6
        $node->removeAttribute('id');
43
44
        // Convert to text
45 6
        $text = DomHelper::toXml($node);
46 6
        $text = preg_replace('/\s\s+/', ' ', $text);
47 6
        $text = trim($text);
48 6
        foreach (self::REPLACEMENTS as $search => $replace) {
49 6
            $text = str_replace($search, $replace, $text);
50
        }
51
52
        // Generate a hash
53 6
        $hash = hash($options['algo'], $text);
54
55
        // Convert to base and pad to maximum length in new base
56 6
        $hash = str_pad(
57 6
            base_convert($hash, 16, $options['base']),
58 6
            (int) ceil(strlen($hash) * log(16, $options['base'])),
59 6
            '0',
60 6
            \STR_PAD_LEFT
61 6
        );
62
63 6
        return sprintf($options['format'], substr($hash, -$options['length']));
64
    }
65
66 6
    protected static function configureOptions(OptionsResolver $resolver = null): OptionsResolver
67
    {
68 1
        $resolver = $resolver ?? new OptionsResolver();
69
70 1
        $resolver->define('algo')
71 1
            ->default('sha512')
72 1
            ->allowedTypes('string')
73 1
            ->allowedValues(...hash_algos())
74 1
            ->info('Hash algorithm used to hash values');
75
76 1
        $resolver->define('format')
77 1
            ->default('%s')
78 1
            ->allowedTypes('string')
79 1
            ->allowedValues(function (string $value) {
80 6
                return str_contains($value, '%');
81 1
            })
82 1
            ->info('Native printf format string to generate final output. Must include %s');
83
84 1
        $resolver->define('base')
85 1
            ->default(62)
86 1
            ->allowedTypes('int')
87 1
            ->allowedValues(function (int $value) {
88 6
                return $value >= 2 && $value <= 62;
89 1
            })
90 1
            ->info('The base used to encode value to reduce its length or increase entropy');
91
92 1
        $resolver->define('length')
93 1
            ->default(7)
94 1
            ->allowedTypes('int')
95 1
            ->info('Length of the generated identifier (after base conversion)');
96
97 1
        return $resolver;
98
    }
99
}
100