Passed
Pull Request — master (#22)
by Aleksei
02:39
created

ValueFieldParser   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Test Coverage

Coverage 97.27%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 123
c 3
b 0
f 0
dl 0
loc 170
ccs 107
cts 110
cp 0.9727
rs 7.44
wmc 52

2 Methods

Rating   Name   Duplication   Size   Complexity  
B createHeaderValue() 0 19 7
F parse() 0 131 45

How to fix   Complexity   

Complex Class

Complex classes like ValueFieldParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ValueFieldParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Http\Header\Parser;
6
7
use Generator;
8
use InvalidArgumentException;
9
use Yiisoft\Http\Header\Internal\BaseHeaderValue;
10
use Yiisoft\Http\Header\Internal\DirectivesHeaderValue;
11
12
final class ValueFieldParser
13
{
14
    // Parsing's constants
15
    private const
16
        DELIMITERS = '"(),/:;<=>?@[\\]{}',
17
        READ_NONE = 0,
18
        READ_VALUE = 1,
19
        READ_PARAM_NAME = 2,
20
        READ_PARAM_QUOTED_VALUE = 3,
21
        READ_PARAM_VALUE = 4;
22
23
    /**
24
     * @psalm-param class-string<BaseHeaderValue> $class
25
     * @psalm-return Generator<int, BaseHeaderValue, void, void>
26
     */
27 87
    public static function parse(string $body, string $class, HeaderParsingParams $params): Generator
28
    {
29 87
        if (!is_a($class, BaseHeaderValue::class, true)) {
30
            throw new InvalidArgumentException('$class should be instance of BaseHeaderValue.');
31
        }
32 87
        if (!$params->valuesList && !$params->withParams && !$params->directives) {
33 4
            yield new $class(trim($body));
34 4
            return;
35
        }
36
37 83
        $state = new ValueFieldState();
38 83
        $state->part = self::READ_VALUE;
39
        try {
40
            /** @link https://tools.ietf.org/html/rfc7230#section-3.2.6 */
41 83
            for ($pos = 0, $length = strlen($body); $pos < $length; ++$pos) {
42 82
                $sym = $body[$pos];
43 82
                if ($state->part === self::READ_VALUE) {
44 82
                    if ($sym === '=' && $params->withParams) {
45 28
                        $state->key = ltrim($state->buffer);
46 28
                        $state->buffer = '';
47 28
                        if (preg_match('/\s/', $state->key) === 0) {
48 25
                            $state->part = self::READ_PARAM_VALUE;
49 25
                            continue;
50
                        }
51 4
                        $state->key = preg_replace('/\s+/', ' ', $state->key);
52 4
                        $chunks = explode(' ', $state->key);
53 4
                        if (count($chunks) > 2 || preg_match('/\s$/', $state->key) === 1) {
54 2
                            array_pop($chunks);
55 2
                            $state->buffer = implode(' ', $chunks);
56 2
                            throw new ParsingException($body, $pos, 'Syntax error');
57
                        }
58 2
                        $state->part = self::READ_PARAM_VALUE;
59 2
                        [$state->value, $state->key] = $chunks;
60 82
                    } elseif ($sym === ';' && $params->withParams) {
61 38
                        $state->part = self::READ_PARAM_NAME;
62 38
                        $state->value = trim($state->buffer);
63 38
                        $state->buffer = '';
64 80
                    } elseif ($sym === ',' && $params->valuesList) {
65 14
                        $state->value = trim($state->buffer);
66 14
                        yield self::createHeaderValue($class, $params, $state);
67
                    } else {
68 80
                        $state->buffer .= $sym;
69
                    }
70 82
                    continue;
71
                }
72 63
                if ($state->part === self::READ_PARAM_NAME) {
73 46
                    if ($sym === '=') {
74 39
                        $state->key = $state->buffer;
75 39
                        $state->buffer = '';
76 39
                        $state->part = self::READ_PARAM_VALUE;
77 46
                    } elseif (strpos(self::DELIMITERS, $sym) !== false) {
78 4
                        throw new ParsingException($body, $pos, 'Delimiter char in a param name');
79 44
                    } elseif (ord($sym) <= 32) {
80 12
                        if ($state->buffer !== '') {
81 12
                            throw new ParsingException($body, $pos, 'Space in a param name');
82
                        }
83
                    } else {
84 43
                        $state->buffer .= $sym;
85
                    }
86 44
                    continue;
87
                }
88 57
                if ($state->part === self::READ_PARAM_VALUE) {
89 57
                    if ($state->buffer === '') {
90 57
                        if ($sym === '"') {
91 13
                            $state->part = self::READ_PARAM_QUOTED_VALUE;
92 52
                        } elseif (ord($sym) <= 32) {
93
                            continue;
94 52
                        } elseif (strpos(self::DELIMITERS, $sym) === false) {
95 51
                            $state->buffer .= $sym;
96
                        } else {
97 57
                            throw new ParsingException($body, $pos, 'Delimiter char in a unquoted param value');
98
                        }
99 49
                    } elseif (ord($sym) <= 32) {
100 4
                        $state->part = self::READ_NONE;
101 4
                        $state->addParamFromBuffer();
102 48
                    } elseif (strpos(self::DELIMITERS, $sym) === false) {
103 40
                        $state->buffer .= $sym;
104 30
                    } elseif ($sym === ';') {
105 21
                        $state->part = self::READ_PARAM_NAME;
106 21
                        $state->addParamFromBuffer();
107 12
                    } elseif ($sym === ',' && $params->valuesList) {
108 8
                        $state->part = self::READ_VALUE;
109 8
                        $state->addParamFromBuffer();
110 8
                        yield self::createHeaderValue($class, $params, $state);
111
                    } else {
112 4
                        $state->buffer = '';
113 4
                        throw new ParsingException($body, $pos, 'Delimiter char in a unquoted param value');
114
                    }
115 56
                    continue;
116
                }
117 16
                if ($state->part === self::READ_PARAM_QUOTED_VALUE) {
118 12
                    if ($sym === '\\') { // quoted pair
119 3
                        if (++$pos >= $length) {
120
                            throw new ParsingException($body, $pos, 'Incorrect quoted pair');
121
                        }
122 3
                        $state->buffer .= $body[$pos];
123 12
                    } elseif ($sym === '"') { // end
124 11
                        $state->part = self::READ_NONE;
125 11
                        $state->addParamFromBuffer();
126
                    } else {
127 11
                        $state->buffer .= $sym;
128
                    }
129 12
                    continue;
130
                }
131 8
                if ($state->part === self::READ_NONE) {
132 8
                    if (ord($sym) <= 32) {
133 1
                        continue;
134
                    }
135 8
                    if ($sym === ';' && $params->withParams) {
136 3
                        $state->part = self::READ_PARAM_NAME;
137 5
                    } elseif ($sym === ',' && $params->valuesList) {
138 3
                        $state->part = self::READ_VALUE;
139 3
                        yield self::createHeaderValue($class, $params, $state);
140
                    } else {
141 2
                        throw new ParsingException($body, $pos, 'Expected Separator');
142
                    }
143
                }
144
            }
145 17
        } catch (ParsingException $e) {
146 17
            $state->error = $e;
147
        }
148 83
        if ($state->part === self::READ_VALUE) {
149 28
            $state->value = trim($state->buffer);
150 63
        } elseif (in_array($state->part, [self::READ_PARAM_VALUE, self::READ_PARAM_QUOTED_VALUE], true)) {
151 43
            if ($state->buffer === '') {
152 7
                $state->error = $state->error ?? new ParsingException($body, $pos, 'Empty value should be quoted');
153
            } else {
154 36
                $state->addParamFromBuffer();
155
            }
156
        }
157 83
        yield self::createHeaderValue($class, $params, $state);
158 83
    }
159
160
    /**
161
     * @psalm-param class-string<BaseHeaderValue> $class
162
     */
163 83
    protected static function createHeaderValue(
164
        string $class,
165
        HeaderParsingParams $params,
166
        ValueFieldState $state
167
    ): BaseHeaderValue {
168
        /** @var BaseHeaderValue|DirectivesHeaderValue $item */
169 83
        $item = new $class($state->value);
170 83
        if ($params->directives && $item instanceof DirectivesHeaderValue) {
171 3
            if ($state->value === '' && count($state->params) > 0) {
172 3
                $item = $item->withDirective(key($state->params), current($state->params));
173
            }
174 80
        } elseif ($params->withParams) {
175 73
            $item = $item->withParams($state->params);
0 ignored issues
show
Bug introduced by
The method withParams() does not exist on Yiisoft\Http\Header\Internal\BaseHeaderValue. It seems like you code against a sub-type of Yiisoft\Http\Header\Internal\BaseHeaderValue such as Yiisoft\Http\Header\Internal\WithParamsHeaderValue. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

175
            /** @scrutinizer ignore-call */ 
176
            $item = $item->withParams($state->params);
Loading history...
Bug introduced by
The method withParams() does not exist on Yiisoft\Http\Header\Internal\DirectivesHeaderValue. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

175
            /** @scrutinizer ignore-call */ 
176
            $item = $item->withParams($state->params);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
176
        }
177 83
        if ($state->error !== null) {
178 19
            $item = $item->withError($state->error);
179
        }
180 83
        $state->clear();
181 83
        return $item;
182
    }
183
}
184