Passed
Pull Request — master (#22)
by Aleksei
07:47
created

ValueFieldParser   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 98.15%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 122
c 2
b 0
f 0
dl 0
loc 165
ccs 106
cts 108
cp 0.9815
rs 8.4
wmc 50

2 Methods

Rating   Name   Duplication   Size   Complexity  
A createHeaderValue() 0 19 6
F parse() 0 129 44

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

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

167
                /** @scrutinizer ignore-call */ 
168
                $item = $item->withDirective(key($state->params), current($state->params));
Loading history...
168
            }
169 80
        } elseif ($params->withParams) {
170 73
            $item = $item->withParams($state->params);
0 ignored issues
show
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

170
            /** @scrutinizer ignore-call */ 
171
            $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...
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

170
            /** @scrutinizer ignore-call */ 
171
            $item = $item->withParams($state->params);
Loading history...
171
        }
172 83
        if ($state->error !== null) {
173 19
            $item = $item->withError($state->error);
174
        }
175 83
        $state->clear();
176 83
        return $item;
177
    }
178
}
179