ArrayEncoder::getAlignedArray()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
c 0
b 0
f 0
nc 6
nop 4
dl 0
loc 15
rs 9.9666
1
<?php
2
3
namespace Riimu\Kit\PHPEncoder\Encoder;
4
5
/**
6
 * Encoder for array values.
7
 * @author Riikka Kalliomäki <[email protected]>
8
 * @copyright Copyright (c) 2014-2020 Riikka Kalliomäki
9
 * @license http://opensource.org/licenses/mit-license.php MIT License
10
 */
11
class ArrayEncoder implements Encoder
12
{
13
    /** @var array Default values for options in the encoder */
14
    private static $defaultOptions = [
15
        'array.short' => true,
16
        'array.base' => 0,
17
        'array.indent' => 4,
18
        'array.align' => false,
19
        'array.inline' => 70,
20
        'array.omit' => true,
21
        'array.eol' => false,
22
    ];
23
24
    public function getDefaultOptions()
25
    {
26
        return self::$defaultOptions;
27
    }
28
29
    public function supports($value)
30
    {
31
        return \is_array($value);
32
    }
33
34
    public function encode($value, $depth, array $options, callable $encode)
35
    {
36
        if ($value === []) {
37
            return $this->wrap('', $options['array.short']);
38
        } elseif (!$options['whitespace']) {
39
            return $this->wrap(
40
                implode(',', $this->getPairs($value, '', $options['array.omit'], $encode)),
41
                $options['array.short']
42
            );
43
        } elseif ($options['array.align']) {
44
            return $this->getAlignedArray($value, $depth, $options, $encode);
45
        }
46
47
        return $this->getFormattedArray($value, $depth, $options, $encode);
48
    }
49
50
    /**
51
     * Returns the PHP code for aligned array accounting for omitted keys and inline arrays.
52
     * @param array $array Array to encode
53
     * @param int $depth Current indentation depth of the output
54
     * @param array $options List of encoder options
55
     * @param callable $encode Callback used to encode values
56
     * @return string The PHP code representation for the array
57
     */
58
    private function getAlignedArray(array $array, $depth, array $options, callable $encode)
59
    {
60
        $next = 0;
61
        $omit = $options['array.omit'];
62
63
        foreach (array_keys($array) as $key) {
64
            if ($key !== $next++) {
65
                $omit = false;
66
                break;
67
            }
68
        }
69
70
        return $omit
71
            ? $this->getFormattedArray($array, $depth, $options, $encode)
72
            : $this->buildArray($this->getAlignedPairs($array, $encode), $depth, $options);
73
    }
74
75
    /**
76
     * Returns the PHP code for the array as inline or multi line array.
77
     * @param array $array Array to encode
78
     * @param int $depth Current indentation depth of the output
79
     * @param array $options List of encoder options
80
     * @param callable $encode Callback used to encode values
81
     * @return string The PHP code representation for the array
82
     */
83
    private function getFormattedArray(array $array, $depth, array $options, callable $encode)
84
    {
85
        $lines = $this->getPairs($array, ' ', $options['array.omit'], $encode, $omitted);
86
87
        if ($omitted && $options['array.inline'] !== false) {
88
            $output = $this->getInlineArray($lines, $options);
89
90
            if ($output !== false) {
91
                return $output;
92
            }
93
        }
94
95
        return $this->buildArray($lines, $depth, $options);
96
    }
97
98
    /**
99
     * Returns the code for the inline array, if possible.
100
     * @param string[] $lines Encoded key and value pairs
101
     * @param array $options List of encoder options
102
     * @return string|false Array encoded as single line of PHP code or false if not possible
103
     */
104
    private function getInlineArray(array $lines, array $options)
105
    {
106
        $output = $this->wrap(implode(', ', $lines), $options['array.short']);
107
108
        if (preg_match('/[\r\n\t]/', $output)) {
109
            return false;
110
        } elseif ($options['array.inline'] === true || \strlen($output) <= (int) $options['array.inline']) {
111
            return $output;
112
        }
113
114
        return false;
115
    }
116
117
    /**
118
     * Builds the complete array from the encoded key and value pairs.
119
     * @param string[] $lines Encoded key and value pairs
120
     * @param int $depth Current indentation depth of the output
121
     * @param array $options List of encoder options
122
     * @return string Array encoded as PHP code
123
     */
124
    private function buildArray(array $lines, $depth, array $options)
125
    {
126
        $indent = $this->buildIndent($options['array.base'], $options['array.indent'], $depth + 1);
127
        $last = $this->buildIndent($options['array.base'], $options['array.indent'], $depth);
128
        $eol = $options['array.eol'] === false ? \PHP_EOL : (string) $options['array.eol'];
129
130
        return $this->wrap(
131
            sprintf('%s%s%s,%1$s%s', $eol, $indent, implode(',' . $eol . $indent, $lines), $last),
132
            $options['array.short']
133
        );
134
    }
135
136
    /**
137
     * Wraps the array code using short or long array notation.
138
     * @param string $string Array string representation to wrap
139
     * @param bool $short True to use short notation, false to use long notation
140
     * @return string The array wrapped appropriately
141
     */
142
    private function wrap($string, $short)
143
    {
144
        return sprintf($short ? '[%s]' : 'array(%s)', $string);
145
    }
146
147
    /**
148
     * Builds the indentation based on the options.
149
     * @param string|int $base The base indentation
150
     * @param string|int $indent A single indentation level
151
     * @param int $depth The level of indentation
152
     * @return string The indentation for the current depth
153
     */
154
    private function buildIndent($base, $indent, $depth)
155
    {
156
        $base = \is_int($base) ? str_repeat(' ', $base) : (string) $base;
157
158
        return $depth === 0 ? $base : $base . str_repeat(
159
            \is_int($indent) ? str_repeat(' ', $indent) : (string) $indent,
160
            $depth
161
        );
162
    }
163
164
    /**
165
     * Returns each encoded key and value pair with aligned assignment operators.
166
     * @param array $array Array to convert into code
167
     * @param callable $encode Callback used to encode values
168
     * @return string[] Each of key and value pair encoded as php
169
     */
170
    private function getAlignedPairs(array $array, callable $encode)
171
    {
172
        $keys = [];
173
        $values = [];
174
175
        foreach ($array as $key => $value) {
176
            $keys[] = $encode($key, 1);
177
            $values[] = $encode($value, 1);
178
        }
179
180
        $format = sprintf('%%-%ds => %%s', max(array_map('strlen', $keys)));
181
        $pairs = [];
182
183
        for ($i = 0, $count = \count($keys); $i < $count; $i++) {
184
            $pairs[] = sprintf($format, $keys[$i], $values[$i]);
185
        }
186
187
        return $pairs;
188
    }
189
190
    /**
191
     * Returns each key and value pair encoded as array assignment.
192
     * @param array $array Array to convert into code
193
     * @param string $space Whitespace between array assignment operator
194
     * @param bool $omit True to omit unnecessary keys, false to not
195
     * @param callable $encode Callback used to encode values
196
     * @param bool $omitted Set to true, if all the keys were omitted, false otherwise
197
     * @return string[] Each of key and value pair encoded as php
198
     */
199
    private function getPairs(array $array, $space, $omit, callable $encode, &$omitted = true)
200
    {
201
        $pairs = [];
202
        $nextIndex = 0;
203
        $omitted = true;
204
        $format = '%s' . $space . '=>' . $space . '%s';
205
206
        foreach ($array as $key => $value) {
207
            if ($omit && $this->canOmitKey($key, $nextIndex)) {
208
                $pairs[] = $encode($value, 1);
209
            } else {
210
                $pairs[] = sprintf($format, $encode($key, 1), $encode($value, 1));
211
                $omitted = false;
212
            }
213
        }
214
215
        return $pairs;
216
    }
217
218
    /**
219
     * Tells if the key can be omitted from array output based on expected index.
220
     * @param int|string $key Current array key
221
     * @param int $nextIndex Next expected key that can be omitted
222
     * @return bool True if the key can be omitted, false if not
223
     */
224
    private function canOmitKey($key, &$nextIndex)
225
    {
226
        $result = $key === $nextIndex;
227
228
        if (\is_int($key)) {
229
            $nextIndex = max($key + 1, $nextIndex);
230
        }
231
232
        return $result;
233
    }
234
}
235