HeaderValue::fromString()   C
last analyzed

Complexity

Conditions 10
Paths 36

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 10.1

Importance

Changes 0
Metric Value
dl 0
loc 68
ccs 36
cts 40
cp 0.9
rs 6.8315
c 0
b 0
f 0
cc 10
nc 36
nop 1
crap 10.1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Header;
5
6
final class HeaderValue
7
{
8
    private const PARSE_START = 1;
9
    
10
    private const PARSE_QUOTE = 2;
11
12
    /**
13
     * @var string
14
     */
15
    private $value;
16
17
    /**
18
     * @var array<string, HeaderValueParameter>
19
     */
20
    private $parameters = [];
21
22
    /**
23
     * @var array<string, bool>
24
     */
25
    private $parametersForceNewLine = [];
26
27
    /**
28
     * @var bool
29
     */
30
    private $needsEncoding = true;
31
32
    /**
33
     * @param string $value
34
     */
35 186
    public function __construct(string $value)
36
    {
37 186
        $this->assertValid($value);
38
39 183
        $this->value = $value;
40 183
    }
41
42
    /**
43
     * @param HeaderValueParameter $parameter
44
     * @param bool $forceNewLine
45
     * @return HeaderValue
46
     */
47 69
    public function withParameter(HeaderValueParameter $parameter, bool $forceNewLine = false): HeaderValue
48
    {
49 69
        $clone = clone $this;
50 69
        $clone->parameters[$parameter->getName()] = $parameter;
51 69
        $clone->parametersForceNewLine[$parameter->getName()] = $forceNewLine;
52 69
        return $clone;
53
    }
54
55
    /**
56
     * @return string
57
     */
58 50
    public function getRaw(): string
59
    {
60 50
        return $this->value;
61
    }
62
63
    /**
64
     * @return string
65
     */
66 159
    public function __toString(): string
67
    {
68 159
        $value = $this->value;
69
70 159
        if ($this->needsEncoding) {
71 147
            $value = new OptimalEncodedHeaderValue($value);
72
        }
73
74 159
        $parameters = [];
75
76 159
        foreach ($this->parameters as $name => $parameter) {
77 57
            $parameters[] = \sprintf(
78 57
                ';%s%s',
79 57
                empty($this->parametersForceNewLine[$name]) ? ' ' : "\r\n ",
80 57
                (string) $parameter
81
            );
82
        }
83
84 159
        return \implode('', \array_merge([$value], $parameters));
85
    }
86
87
    /**
88
     * @param string $value
89
     */
90 186
    private function assertValid(string $value): void
91
    {
92 186
        if ($this->validate($value) === false) {
93 3
            throw new \InvalidArgumentException('Invalid header value ' . $value);
94
        }
95 183
    }
96
97
    /**
98
     * @param string $value
99
     * @return bool
100
     */
101 186
    private function validate(string $value): bool
102
    {
103 186
        $total = \strlen($value);
104
105 186
        for ($i = 0; $i < $total; $i += 1) {
106 186
            $ord = \ord($value[$i]);
107 186
            if ($ord === 10) {
108 1
                return false;
109
            }
110 186
            if ($ord === 13) {
111 7
                if ($i + 2 >= $total) {
112
                    return false;
113
                }
114 7
                $lf = \ord($value[$i + 1]);
115 7
                $sp = \ord($value[$i + 2]);
116 7
                if ($lf !== 10 || ! \in_array($sp, [9, 32], true)) {
117 2
                    return false;
118
                }
119
                // skip over the LF following this
120 5
                $i += 2;
121
            }
122
        }
123
124 183
        return true;
125
    }
126
127
    /**
128
     * @param string $name
129
     * @return HeaderValueParameter
130
     */
131 41
    public function getParameter(string $name): HeaderValueParameter
132
    {
133 41
        if (isset($this->parameters[$name])) {
134 41
            return $this->parameters[$name];
135
        }
136
137 2
        throw new \UnexpectedValueException('No parameter with name ' . $name);
138
    }
139
140
    /**
141
     * @param string $value
142
     * @return HeaderValue
143
     */
144 37
    public static function fromEncodedString(string $value): HeaderValue
145
    {
146 37
        $headerValue = new self($value);
147 37
        $headerValue->needsEncoding = false;
148 37
        return $headerValue;
149
    }
150
151
    /**
152
     * @param string $headerValueAsString
153
     * @return HeaderValue
154
     */
155 56
    public static function fromString(string $headerValueAsString): HeaderValue
156
    {
157 56
        $values = [];
158
159 56
        $headerValueAsString = \str_replace("\r\n ", ' ', \trim($headerValueAsString));
160
161 56
        $length = \strlen($headerValueAsString) - 1;
162 56
        $n = -1;
163 56
        $state = self::PARSE_START;
164 56
        $escapeNext = false;
165 56
        $sequence = '';
166
167 56
        while ($n < $length) {
168 56
            $n++;
169
170 56
            $char = $headerValueAsString[$n];
171
172 56
            $sequence .= $char;
173
174 56
            if ($char === '\\') {
175
                $escapeNext = true;
176
                continue;
177
            }
178
179 56
            if ($escapeNext) {
180
                $escapeNext = false;
181
                continue;
182
            }
183
184
            switch ($state) {
185 56
                case self::PARSE_QUOTE:
186 6
                    if ($char === '"') {
187 6
                        $state = self::PARSE_START;
188
                    }
189
190 6
                    break;
191
                default:
192 56
                    if ($char === '"') {
193 6
                        $state = self::PARSE_QUOTE;
194
                    }
195
196 56
                    if ($char === ';') {
197 28
                        $values[] = \trim(\substr($sequence, 0, -1));
198 28
                        $sequence = '';
199 28
                        $state = self::PARSE_START;
200
                    }
201 56
                    break;
202
            }
203
        }
204
205 56
        $values[] = \trim($sequence);
206
207 56
        $primaryValue = $values[0];
208
209 56
        $parameters = [];
210 56
        foreach (\array_slice($values, 1) as $parameterString) {
211
            try {
212 28
                $parameter = HeaderValueParameter::fromString($parameterString);
213 27
                $parameters[$parameter->getName()] = $parameter;
214 1
            } catch (\InvalidArgumentException $e) {
215 1
                $primaryValue .= '; ' . $parameterString;
216
            }
217
        }
218
219 56
        $headerValue = new self($primaryValue);
220 56
        $headerValue->parameters = $parameters;
221 56
        return $headerValue;
222
    }
223
}
224