Completed
Push — 5.x ( d13b87...db283c )
by Lars
16:18
created

Swift_Mime_Headers_AbstractHeader   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 483
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 97.76%

Importance

Changes 10
Bugs 2 Features 1
Metric Value
c 10
b 2
f 1
dl 0
loc 483
ccs 131
cts 134
cp 0.9776
rs 7.0642
wmc 54
lcom 1
cbo 4

26 Methods

Rating   Name   Duplication   Size   Complexity  
A getCharset() 0 4 1
A getLanguage() 0 4 1
A getEncoder() 0 4 1
A getFieldName() 0 4 1
A toString() 0 4 1
A __toString() 0 4 1
A setFieldName() 0 4 1
A setLanguage() 0 5 1
A __construct() 0 3 1
A setEncoder() 0 7 1
A setMaxLineLength() 0 5 1
A getMaxLineLength() 0 4 1
A setCharset() 0 8 2
A createPhrase() 0 22 3
A escapeSpecials() 0 8 2
B encodeWords() 0 29 5
A tokenNeedsEncoding() 0 8 2
B getEncodableWordTokens() 0 24 5
B getTokenAsEncodedWord() 0 32 5
A generateTokenLines() 0 4 1
A setCachedValue() 0 4 1
A getCachedValue() 0 4 1
A clearCachedValueIf() 0 6 2
A toTokens() 0 18 4
C _tokensToString() 0 30 7
A __clone() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like Swift_Mime_Headers_AbstractHeader 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Swift_Mime_Headers_AbstractHeader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of SwiftMailer.
5
 * (c) 2004-2009 Chris Corbyn
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
/**
12
 * An abstract base MIME Header.
13
 *
14
 * @author Chris Corbyn
15
 */
16
abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
17
{
18
    const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';
19
20
    /**
21
     * The name of this Header.
22
     *
23
     * @var string
24
     */
25
    private $_name;
26
27
    /**
28
     * The Encoder used to encode this Header.
29
     *
30
     * @var Swift_Encoder|Swift_Mime_ContentEncoder
31
     */
32
    private $_encoder;
33
34
    /**
35
     * The maximum length of a line in the header.
36
     *
37
     * @var int
38
     */
39
    private $_lineLength = 78;
40
41
    /**
42
     * The language used in this Header.
43
     *
44
     * @var string
45
     */
46
    private $_lang;
47
48
    /**
49
     * The character set of the text in this Header.
50
     *
51
     * @var string
52
     */
53
    private $_charset = 'utf-8';
54
55
    /**
56
     * The value of this Header, cached.
57
     *
58
     * @var string
59
     */
60
    private $_cachedValue = null;
61
62
    /**
63
     * Creates a new Header.
64
     */
65 256
    public function __construct()
66
    {
67 256
    }
68
69
    /**
70
     * Set the character set used in this Header.
71
     *
72
     * @param string $charset
73
     */
74 185
    public function setCharset($charset)
75
    {
76 185
        $this->clearCachedValueIf($charset != $this->_charset);
77 185
        $this->_charset = $charset;
78 185
        if (isset($this->_encoder)) {
79 185
            $this->_encoder->charsetChanged($charset);
80
        }
81 185
    }
82
83
    /**
84
     * Get the character set used in this Header.
85
     *
86
     * @return string
87
     */
88 83
    public function getCharset()
89
    {
90 83
        return $this->_charset;
91
    }
92
93
    /**
94
     * Set the language used in this Header.
95
     *
96
     * For example, for US English, 'en-us'.
97
     * This can be unspecified.
98
     *
99
     * @param string $lang
100
     */
101 4
    public function setLanguage($lang)
102
    {
103 4
        $this->clearCachedValueIf($this->_lang != $lang);
104 4
        $this->_lang = $lang;
105 4
    }
106
107
    /**
108
     * Get the language used in this Header.
109
     *
110
     * @return string
111
     */
112 7
    public function getLanguage()
113
    {
114 7
        return $this->_lang;
115
    }
116
117
    /**
118
     * Set the encoder used for encoding the header.
119
     *
120
     * @param Swift_Mime_HeaderEncoder $encoder
121
     */
122 216
    public function setEncoder(Swift_Mime_HeaderEncoder $encoder)
123
    {
124 216
        $this->_encoder = $encoder;
125 216
        $this->_encoder->charsetChanged($this->_charset);
126
127 216
        $this->setCachedValue(null);
128 216
    }
129
130
    /**
131
     * Get the encoder used for encoding this Header.
132
     *
133
     * @return Swift_Mime_HeaderEncoder
134
     */
135 77
    public function getEncoder()
136
    {
137 77
        return $this->_encoder;
138
    }
139
140
    /**
141
     * Get the name of this header (e.g. charset).
142
     *
143
     * @return string
144
     */
145 153
    public function getFieldName()
146
    {
147 153
        return $this->_name;
148
    }
149
150
    /**
151
     * Set the maximum length of lines in the header (excluding EOL).
152
     *
153
     * @param int $lineLength
154
     */
155 20
    public function setMaxLineLength($lineLength)
156
    {
157 20
        $this->clearCachedValueIf($this->_lineLength != $lineLength);
158 20
        $this->_lineLength = $lineLength;
159 20
    }
160
161
    /**
162
     * Get the maximum permitted length of lines in this Header.
163
     *
164
     * @return int
165
     */
166 82
    public function getMaxLineLength()
167
    {
168 82
        return $this->_lineLength;
169
    }
170
171
    /**
172
     * Get this Header rendered as a RFC 2822 compliant string.
173
     *
174
     * @return string
175
     */
176 157
    public function toString()
177
    {
178 157
        return $this->_tokensToString($this->toTokens());
179
    }
180
181
    /**
182
     * Returns a string representation of this object.
183
     *
184
     * @return string
185
     *
186
     * @see toString()
187
     */
188
    public function __toString()
189
    {
190
        return $this->toString();
191
    }
192
193
    // -- Points of extension
194
195
    /**
196
     * Set the name of this Header field.
197
     *
198
     * @param string $name
199
     */
200 256
    protected function setFieldName($name)
201
    {
202 256
        $this->_name = $name;
203 256
    }
204
205
    /**
206
     * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
207
     *
208
     * @param Swift_Mime_Header        $header
209
     * @param string                   $string  as displayed
210
     *
211
     * @return string
212
     */
213 77
    protected function createPhrase(Swift_Mime_Header $header, $string)
214
    {
215
        // Treat token as exactly what was given
216 77
        $phraseStr = $string;
217
218
        // If it's not valid
219 77
        if (!preg_match('/^' . self::PHRASE_PATTERN . '$/D', $phraseStr)) {
220
221 6
            if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
222
                // .. but it is just ascii text, try escaping some characters
223 3
                $phraseStr = $this->escapeSpecials($phraseStr, array('"'));
224
            } else {
225
                // ... otherwise it needs encoding
226 4
                $phraseStr = $this->encodeWords($header, $string);
227
            }
228
229
            // and make it a quoted-string
230 6
            $phraseStr = '"' . $phraseStr . '"';
231
        }
232
233 77
        return $phraseStr;
234
    }
235
236
    /**
237
     * Escape special characters in a string (convert to quoted-pairs).
238
     *
239
     * @param string   $token
240
     * @param string[] $include additional chars to escape
241
     *
242
     * @return string
243
     */
244 3
    protected function escapeSpecials($token, array $include = array())
245
    {
246 3
        foreach (array_merge(array('\\'), $include) as $char) {
247 3
            $token = str_replace($char, '\\' . $char, $token);
248
        }
249
250 3
        return $token;
251
    }
252
253
    /**
254
     * Encode needed word tokens within a string of input.
255
     *
256
     * @param Swift_Mime_Header|Swift_Mime_Headers_AbstractHeader $header
257
     * @param string                                              $input
258
     *
259
     * @return string
260
     */
261 161
    protected function encodeWords(Swift_Mime_Header $header, $input)
262
    {
263 161
        $value = '';
264
265 161
        $tokens = $this->getEncodableWordTokens($input);
266
267 161
        foreach ($tokens as $token) {
268
            // See RFC 2822, Sect 2.2 (really 2.2 ?? TODO ?)
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
269 161
            if ($this->tokenNeedsEncoding($token)) {
270
                // Don't encode starting WSP
271 16
                $firstChar = substr($token, 0, 1);
272
                switch ($firstChar) {
273 16
                    case ' ':
274 12
                    case "\t":
275 5
                        $value .= $firstChar;
276 5
                        $token = substr($token, 1);
277
                }
278
279 16
                $usedLength = strlen($header->getFieldName() . ': ') + strlen($value);
280 16
                $value .= $this->getTokenAsEncodedWord($token, $usedLength);
281
282 16
                $header->setMaxLineLength(76); // Forcefully override
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Mime_Header as the method setMaxLineLength() does only exist in the following implementations of said interface: Swift_Mime_Headers_AbstractHeader, Swift_Mime_Headers_DateHeader, Swift_Mime_Headers_IdentificationHeader, Swift_Mime_Headers_MailboxHeader, Swift_Mime_Headers_ParameterizedHeader, Swift_Mime_Headers_PathHeader, Swift_Mime_Headers_UnstructuredHeader.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
283
            } else {
284 161
                $value .= $token;
285
            }
286
        }
287
288 161
        return $value;
289
    }
290
291
    /**
292
     * Test if a token needs to be encoded or not.
293
     *
294
     * @param string $token
295
     *
296
     * @return integer
297
     */
298 161
    protected function tokenNeedsEncoding($token)
299
    {
300 161
        if (preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token)) {
301 15
            return 1;
302
        } else {
303 150
            return 0;
304
        }
305
    }
306
307
    /**
308
     * Splits a string into tokens in blocks of words which can be encoded quickly.
309
     *
310
     * @param string $string
311
     *
312
     * @return string[]
313
     */
314 161
    protected function getEncodableWordTokens($string)
315
    {
316 161
        $tokens = array();
317
318 161
        $encodedToken = '';
319
        // Split at all whitespace boundaries
320 161
        foreach (preg_split('~(?=[\t ])~', $string) as $token) {
321 161
            if ($this->tokenNeedsEncoding($token)) {
322 16
                $encodedToken .= $token;
323
            } else {
324 150
                if ($encodedToken !== '') {
325 3
                    $tokens[] = $encodedToken;
326 3
                    $encodedToken = '';
327
                }
328 161
                $tokens[] = $token;
329
            }
330
        }
331
332 161
        if ($encodedToken !== '') {
333 15
            $tokens[] = $encodedToken;
334
        }
335
336 161
        return $tokens;
337
    }
338
339
    /**
340
     * Get a token as an encoded word for safe insertion into headers.
341
     *
342
     * @param string $token           token to encode
343
     * @param int    $firstLineOffset optional
344
     *
345
     * @return string
346
     */
347 17
    protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
348
    {
349
        // Adjust $firstLineOffset to account for space needed for syntax.
350
351 17
        $charsetDecl = $this->_charset;
352
353 17
        if (isset($this->_lang)) {
354 2
            $charsetDecl .= '*' . $this->_lang;
355
        }
356
357 17
        $encodingWrapperLength = strlen('=?' . $charsetDecl . '?' . $this->_encoder->getName() . '??=');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Encoder as the method getName() does only exist in the following implementations of said interface: Swift_Mime_ContentEncoder_Base64ContentEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder, Swift_Mime_ContentEncoder_PlainContentEncoder, Swift_Mime_ContentEncoder_QpContentEncoder, Swift_Mime_ContentEncoder_QpContentEncoderProxy, Swift_Mime_ContentEncoder_RawContentEncoder, Swift_Mime_HeaderEncoder_Base64HeaderEncoder, Swift_Mime_HeaderEncoder_QpHeaderEncoder.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
358
359 17
        if ($firstLineOffset >= 75) {
360
            // Does this logic need to be here?
361
            $firstLineOffset = 0;
362
        }
363
364
        /** @noinspection PhpMethodParametersCountMismatchInspection */
365 17
        $encodedTextLines = explode(
366 17
            "\r\n",
367 17
            $this->_encoder->encodeString($token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset)
0 ignored issues
show
Unused Code introduced by
The call to Swift_Encoder::encodeString() has too many arguments starting with $this->_charset.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
368
        );
369
370 17
        if (Swift::strtolowerWithStaticCache($this->_charset) !== 'iso-2022-jp') {
371
            // special encoding for iso-2022-jp using mb_encode_mimeheader
372 17
            foreach ($encodedTextLines as $lineNum => $line) {
373 17
                $encodedTextLines[$lineNum] = '=?' . $charsetDecl . '?' . $this->_encoder->getName() . '?' . $line . '?=';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Encoder as the method getName() does only exist in the following implementations of said interface: Swift_Mime_ContentEncoder_Base64ContentEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder, Swift_Mime_ContentEncoder_PlainContentEncoder, Swift_Mime_ContentEncoder_QpContentEncoder, Swift_Mime_ContentEncoder_QpContentEncoderProxy, Swift_Mime_ContentEncoder_RawContentEncoder, Swift_Mime_HeaderEncoder_Base64HeaderEncoder, Swift_Mime_HeaderEncoder_QpHeaderEncoder.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
374
            }
375
        }
376
377 17
        return implode("\r\n ", $encodedTextLines);
378
    }
379
380
    /**
381
     * Generates tokens from the given string which include CRLF as individual tokens.
382
     *
383
     * @param string $token
384
     *
385
     * @return string[]
386
     */
387 157
    protected function generateTokenLines($token)
388
    {
389 157
        return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
390
    }
391
392
    /**
393
     * Set a value into the cache.
394
     *
395
     * @param string $value
396
     */
397 245
    protected function setCachedValue($value)
398
    {
399 245
        $this->_cachedValue = $value;
400 245
    }
401
402
    /**
403
     * Get the value in the cache.
404
     *
405
     * @return string
406
     */
407 176
    protected function getCachedValue()
408
    {
409 176
        return $this->_cachedValue;
410
    }
411
412
    /**
413
     * Clear the cached value if $condition is met.
414
     *
415
     * @param bool $condition
416
     */
417 231
    protected function clearCachedValueIf($condition)
418
    {
419 231
        if ($condition) {
420 206
            $this->setCachedValue(null);
421
        }
422 231
    }
423
424
    /**
425
     * Generate a list of all tokens in the final header.
426
     *
427
     * @param string $string The string to tokenize
428
     *
429
     * @return array An array of tokens as strings
430
     */
431 157
    protected function toTokens($string = null)
432
    {
433 157
        if (null === $string) {
434 152
            $string = $this->getFieldBody();
435
        }
436
437 157
        $tokens = array();
438
439
        // Generate atoms; split at all invisible boundaries followed by WSP
440 157
        foreach (preg_split('~(?=[ \t])~', $string) as $token) {
441 157
            $newTokens = $this->generateTokenLines($token);
442 157
            foreach ($newTokens as $newToken) {
443 157
                $tokens[] = $newToken;
444
            }
445
        }
446
447 157
        return $tokens;
448
    }
449
450
    /**
451
     * Takes an array of tokens which appear in the header and turns them into
452
     * an RFC 2822 compliant string, adding FWSP where needed.
453
     *
454
     * @param string[] $tokens
455
     *
456
     * @return string
457
     */
458 157
    private function _tokensToString(array $tokens)
459
    {
460 157
        $lineCount = 0;
461 157
        $headerLines = array();
462 157
        $headerLines[] = $this->_name . ': ';
463 157
        $currentLine = &$headerLines[$lineCount++];
464
465
        // Build all tokens back into compliant header
466 157
        foreach ($tokens as $i => $token) {
467
            // Line longer than specified maximum or token was just a new line
468
            if (
469 157
                ("\r\n" === $token)
470
                ||
471 157
                ($i > 0 && strlen($currentLine . $token) > $this->_lineLength)
472
                &&
473 157
                0 < strlen($currentLine)
474
            ) {
475 47
                $headerLines[] = '';
476 47
                $currentLine = &$headerLines[$lineCount++];
477
            }
478
479
            // Append token to the line
480 157
            if ("\r\n" != $token) {
481 157
                $currentLine .= $token;
482
            }
483
        }
484
485
        // Implode with FWS (RFC 2822, 2.2.3)
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
486 157
        return implode("\r\n", $headerLines) . "\r\n";
487
    }
488
489
    /**
490
     * Make a deep copy of object
491
     */
492 5
    public function __clone()
493
    {
494 5
        if ($this->_encoder !== null) {
495 5
            $this->_encoder = clone $this->_encoder;
496
        }
497 5
    }
498
}
499