YamlSortService   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 25
eloc 54
c 2
b 0
f 0
dl 0
loc 151
ccs 57
cts 57
cp 1
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A sortArray() 0 16 6
A sortArrayKeyWithUnderscoresAsFirst() 0 11 1
A changeElementPositionInArray() 0 5 1
B sortArrayAlphabetical() 0 35 8
A recursiveKsort() 0 13 5
A sortArrayElementsByPrioritizedKeys() 0 22 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace YamlStandards\Model\YamlAlphabetical;
6
7
use YamlStandards\Model\Component\Parser\YamlParserLineData;
8
use YamlStandards\Model\Component\YamlService;
9
10
class YamlSortService
11
{
12
    private const EXACT_DEFINED_PRIORITIZED_KEY = '::exact';
13
14
    /**
15
     * @param string[] $yamlArrayData
16
     * @param int $depth
17
     * @param string[] $prioritizedKeys
18
     * @return string[]
19
     */
20 10
    public static function sortArray(array $yamlArrayData, int $depth, array $prioritizedKeys): array
21
    {
22 10
        if ($depth > 0) {
23 10
            $yamlArrayData = self::sortArrayKeyWithUnderscoresAsFirst($yamlArrayData, $prioritizedKeys);
24
25 10
            foreach ($yamlArrayData as $key => $value) {
26 10
                if (is_array($value)) {
27
                    // ignore "empty_array" key because they not included in file, they are only auxiliary variables
28 10
                    if ($depth > 1 || preg_match(YamlAlphabeticalDataFactory::REGEX_KEY_EMPTY_ARRAY_WITH_NUMBER_AT_END, $key) === 1) {
29 9
                        $yamlArrayData[$key] = self::recursiveKsort($value, $depth, $prioritizedKeys);
30
                    }
31
                }
32
            }
33
        }
34
35 10
        return $yamlArrayData;
36
    }
37
38
    /**
39
     * @param string[] $yamlArrayData
40
     * @param int $depth
41
     * @param string[] $prioritizedKeys
42
     * @param int $currentDepth
43
     * @return string[]
44
     */
45 9
    private static function recursiveKsort(array $yamlArrayData, int $depth, array $prioritizedKeys, int $currentDepth = 2): array
46
    {
47 9
        $yamlArrayData = self::sortArrayKeyWithUnderscoresAsFirst($yamlArrayData, $prioritizedKeys);
48 9
        foreach ($yamlArrayData as $key => $value) {
49 9
            if (is_array($value)) {
50
                // ignore "empty_array" key because they not included in file, they are only auxiliary variables
51 9
                if ($currentDepth < $depth || preg_match(YamlAlphabeticalDataFactory::REGEX_KEY_EMPTY_ARRAY_WITH_NUMBER_AT_END, $key) === 1) {
52 8
                    $yamlArrayData[$key] = self::recursiveKsort($value, $depth, $prioritizedKeys, $currentDepth + 1);
53
                }
54
            }
55
        }
56
57 9
        return $yamlArrayData;
58
    }
59
60
    /**
61
     * @param string[] $yamlArrayData
62
     * @param string[] $prioritizedKeys
63
     * @return string[]|string[][]
64
     */
65 10
    private static function sortArrayKeyWithUnderscoresAsFirst(array $yamlArrayData, array $prioritizedKeys): array
66
    {
67 10
        $arrayWithUnderscoreKeys = array_filter($yamlArrayData, [YamlService::class, 'hasArrayKeyUnderscoreAsFirstCharacter'], ARRAY_FILTER_USE_KEY);
68 10
        $arrayWithOtherKeys = array_filter($yamlArrayData, [YamlService::class, 'hasNotArrayKeyUnderscoreAsFirstCharacter'], ARRAY_FILTER_USE_KEY);
69
70 10
        uksort($arrayWithUnderscoreKeys, [__CLASS__, 'sortArrayAlphabetical']);
71 10
        uksort($arrayWithOtherKeys, [__CLASS__, 'sortArrayAlphabetical']);
72
73 10
        $arrayData = array_merge($arrayWithUnderscoreKeys, $arrayWithOtherKeys);
74
75 10
        return self::sortArrayElementsByPrioritizedKeys($prioritizedKeys, $arrayData);
76
    }
77
78
    /**
79
     * @param string $key1
80
     * @param string $key2
81
     * @return int
82
     */
83 10
    private static function sortArrayAlphabetical(string $key1, string $key2): int
84
    {
85
        // remove added text for empty line and comment line
86 10
        $key1WithoutNumberAtEnd = preg_replace(YamlAlphabeticalDataFactory::REGEX_KEY_COMMENT_OR_EMPTY_LINE_WITH_NUMBER_AT_END, '', $key1);
87 10
        $key2WithoutNumberAtEnd = preg_replace(YamlAlphabeticalDataFactory::REGEX_KEY_COMMENT_OR_EMPTY_LINE_WITH_NUMBER_AT_END, '', $key2);
88
89
        // add key number to end for fix situation when keys are same
90 10
        preg_match('/\d+$/', $key1WithoutNumberAtEnd, $key1NumberAtEnd);
91 10
        preg_match('/\d+$/', $key2WithoutNumberAtEnd, $key2NumberAtEnd);
92 10
        $key1NumberAtEnd = count($key1NumberAtEnd) === 0 ? 0 : reset($key1NumberAtEnd);
93 10
        $key2NumberAtEnd = count($key2NumberAtEnd) === 0 ? 0 : reset($key2NumberAtEnd);
94
95 10
        $key1WithoutNumberAtEnd = preg_match(YamlAlphabeticalDataFactory::REGEX_KEY_COMMON_LINE_WITH_NUMBER_AT_END, $key1WithoutNumberAtEnd) === 0 ? $key1WithoutNumberAtEnd : preg_replace(YamlAlphabeticalDataFactory::REGEX_KEY_COMMON_LINE_WITH_NUMBER_AT_END, '', $key1WithoutNumberAtEnd);
96 10
        $key1WithoutNumberAtEnd = preg_match(YamlAlphabeticalDataFactory::REGEX_KEY_ARRAY_WITHOUT_KEY_WITH_NUMBER_AT_END, $key1WithoutNumberAtEnd) === 0 ? $key1WithoutNumberAtEnd : preg_replace(YamlAlphabeticalDataFactory::REGEX_KEY_ARRAY_WITHOUT_KEY_WITH_NUMBER_AT_END, '', $key1WithoutNumberAtEnd);
97
98 10
        $key2WithoutNumberAtEnd = preg_match(YamlAlphabeticalDataFactory::REGEX_KEY_COMMON_LINE_WITH_NUMBER_AT_END, $key2WithoutNumberAtEnd) === 0 ? $key2WithoutNumberAtEnd : preg_replace(YamlAlphabeticalDataFactory::REGEX_KEY_COMMON_LINE_WITH_NUMBER_AT_END, '', $key2WithoutNumberAtEnd);
99 10
        $key2WithoutNumberAtEnd = preg_match(YamlAlphabeticalDataFactory::REGEX_KEY_ARRAY_WITHOUT_KEY_WITH_NUMBER_AT_END, $key2WithoutNumberAtEnd) === 0 ? $key2WithoutNumberAtEnd : preg_replace(YamlAlphabeticalDataFactory::REGEX_KEY_ARRAY_WITHOUT_KEY_WITH_NUMBER_AT_END, '', $key2WithoutNumberAtEnd);
100
101
        /*
102
         * add exclamation mark (!) to penultimate position in string for fix order for "dot" key scenario, e.g:
103
         * foo.bar:
104
         * foo.bar.baz:
105
         * ":" is in alphabetical higher as ".", https://s2799303.files.wordpress.com/2013/08/ascii-codes-table1.jpg
106
         */
107 10
        $key1WithoutNumberAtEnd = substr_replace($key1WithoutNumberAtEnd, '!', -1, 0);
108 10
        $key2WithoutNumberAtEnd = substr_replace($key2WithoutNumberAtEnd, '!', -1, 0);
109
110 10
        $key1WithoutNumberAtEnd .= $key1NumberAtEnd;
111 10
        $key2WithoutNumberAtEnd .= $key2NumberAtEnd;
112
113 10
        if ($key1WithoutNumberAtEnd === $key2WithoutNumberAtEnd) {
114 5
            return strnatcmp(trim($key2), trim($key1));
115
        }
116
117 10
        return strnatcmp(trim($key1WithoutNumberAtEnd), trim($key2WithoutNumberAtEnd));
118
    }
119
120
    /**
121
     * @param string[] $prioritizedKeys
122
     * @param string[] $arrayData
123
     * @return string[]
124
     */
125 14
    private static function sortArrayElementsByPrioritizedKeys(array $prioritizedKeys, array $arrayData): array
126
    {
127 14
        $positionTo = 0;
128 14
        $exactDefinedPrioritizedKeyLength = strlen(self::EXACT_DEFINED_PRIORITIZED_KEY);
129
130 14
        foreach ($prioritizedKeys as $prioritizedKey) {
131
            // check if prioritized key is defined as exact in end
132 5
            if (substr($prioritizedKey, -$exactDefinedPrioritizedKeyLength) === self::EXACT_DEFINED_PRIORITIZED_KEY) {
133 1
                $prioritizedKey = str_replace(self::EXACT_DEFINED_PRIORITIZED_KEY, '', $prioritizedKey);
134 1
                $foundKeys = preg_grep('/' . preg_quote($prioritizedKey, '/') . ':' . YamlParserLineData::KEY . '/', array_keys($arrayData));
135
            } else {
136 5
                $foundKeys = preg_grep('/' . preg_quote($prioritizedKey, '/') . '/', array_keys($arrayData));
137
            }
138
139 5
            foreach ($foundKeys as $foundKey) {
140 5
                $positionFrom = (int)array_search($foundKey, array_keys($arrayData), true);
141 5
                self::changeElementPositionInArray($arrayData, $positionFrom, $positionTo);
142 5
                $positionTo++;
143
            }
144
        }
145
146 14
        return $arrayData;
147
    }
148
149
    /**
150
     * @param string[] $array
151
     * @param int $positionFrom
152
     * @param int $positionTo
153
     *
154
     * @link https://stackoverflow.com/a/28831998
155
     */
156 5
    private static function changeElementPositionInArray(array &$array, int $positionFrom, int $positionTo): void
157
    {
158 5
        $p1 = array_splice($array, $positionFrom, 1);
159 5
        $p2 = array_splice($array, 0, $positionTo);
160 5
        $array = array_merge($p2, $p1, $array);
161
    }
162
}
163