IncludeParser::flattenKeys()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Rexlabs\Smokescreen\Includes;
4
5
class IncludeParser implements IncludeParserInterface
6
{
7
    /**
8
     * Parse given string into an Includes object.
9
     *
10
     * @param string $str
11
     *
12
     * @return Includes
13
     */
14 17
    public function parse(string $str): Includes
15
    {
16
        // Ignore whitespace
17 17
        $str = preg_replace('/\s/', '', $str);
18
19 17
        if (empty($str)) {
20 1
            return new Includes();
21
        }
22
23
        // Parse a string in the following format:
24
        // pets{id,name,owner{id,name},photos:limit(3)}:limit(5):offset(10)
25
26
        // Define the current parse state
27 16
        $state = [
28
            // Original string
29 16
            'string' => $str,
30
            // Position in string
31 16
            'pos' => 0,
32
            // Length of the original string being processed
33 16
            'len' => 0,
34
            // Current character being processed
35 16
            'char' => null,
36
            // The accumulated current key for the field
37 16
            'buffer' => '',
38
            // The current parent keys
39 16
            'parent' => [],
40
            // Previous parent
41 16
            'prevParent' => null,
42
            // Our list of keys
43 16
            'keys' => [],
44
            // Our list of params
45 16
            'params' => [],
46 16
        ];
47
48
        // Process each character, moving through the state and build
49 16
        while ($state['pos'] < ($len = \strlen($str))) {
50 16
            $state['char'] = $str[$state['pos']];
51 16
            $state['len'] = $len;
52
53 16
            switch ($state['char']) {
54 16
                case '{':
55
                    // Begin children
56 10
                    if (!empty($state['buffer'])) {
57 10
                        $state['keys'][] = $this->prefixParentKeys($state['buffer'], $state['parent']);
58 10
                        $state['parent'][] = $state['buffer'];
59 10
                        $state['buffer'] = '';
60
                    }
61 10
                    break;
62 16
                case ',':
63
                    // Delimiter
64 11
                    if (!empty($state['buffer'])) {
65 11
                        $state['keys'][] = $this->prefixParentKeys($state['buffer'], $state['parent']);
66 11
                        $state['buffer'] = '';
67
                    }
68 11
                    break;
69 16
                case '}':
70
                    // End children
71 10
                    if (!empty($state['buffer'])) {
72 10
                        $state['keys'][] = $this->prefixParentKeys($state['buffer'], $state['parent']);
73 10
                        $state['buffer'] = '';
74
                    }
75 10
                    if (!empty($state['parent'])) {
76 10
                        $state['prevParent'] = $state['parent'];
77 10
                        array_pop($state['parent']);
78
                    }
79 10
                    break;
80 16
                case ':':
81
                    // Looks like it's a parameter. Eg. :limit(10)
82
                    // Well, if we have a buffer, then that's our parent, if we don't
83
                    // we will use the parent we saved when we popped the last parent state.
84 3
                    $parentKey = !empty($state['buffer']) ?
85 3
                        $this->prefixParentKeys($state['buffer'], $state['parent']) : $this->flattenKeys($state['prevParent']);
86
87 3
                    if (preg_match('/^:(\w+)\(([^)]+)\)/', substr($str, $state['pos']), $match)) {
88
                        // We have a match
89 3
                        list($param, $key, $val) = $match;
90 3
                        $len = \strlen($param);
91
92
                        // Initialise the parent key in our params associative array
93 3
                        if (!isset($state['params'][$parentKey])) {
94 3
                            $state['params'][$parentKey] = [];
95
                        }
96
97
                        // Store the param key and value
98 3
                        $state['params'][$parentKey][$key] = $val;
99
100
                        // Chop our parameter out of the original string
101 3
                        $str =
102 3
                            substr($str, 0, $state['pos']).
103 3
                            substr($str, $state['pos'] + $len);
104
105
                        // We need to move the position head back one after the chop
106
                        // since it will be advanced at the end of the loop
107 3
                        $state['pos']--;
108
                    }
109 3
                    break;
110
                default:
111
                    // Any other character should just be appended
112 16
                    $state['buffer'] .= $state['char'];
113 16
                    break;
114
            }
115 16
            $state['pos']++;
116
        }
117
118
        // Finally handle any non-empty buffer
119 16
        if (!empty($state['buffer'])) {
120 6
            $state['keys'][] = $this->prefixParentKeys($state['buffer'], $state['parent']);
121 6
            $state['buffer'] = '';
122
        }
123
124 16
        return (new Includes())
125 16
            ->set($state['keys'])
126 16
            ->setParams($state['params']);
127
    }
128
129
    /**
130
     * Helper function to prefix all of the parent keys.
131
     *
132
     * @param string $key
133
     * @param array  $parent
134
     *
135
     * @return string
136
     */
137 16
    protected function prefixParentKeys($key, array $parent): string
138
    {
139 16
        return !empty($parent) ?
140 16
            $this->flattenKeys($parent).".$key" : $key;
141
    }
142
143
    /**
144
     * @param array $keys
145
     *
146
     * @return string
147
     */
148 10
    protected function flattenKeys(array $keys): string
149
    {
150 10
        return implode('.', $keys);
151
    }
152
}
153