Completed
Push — 5.x ( 546220...bd8586 )
by Lars
16:02
created

setCharReaderForCurrentCharset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 3
c 1
b 1
f 0
nc 2
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
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
 * A CharacterStream implementation which stores characters in an internal array.
13
 *
14
 * @author Chris Corbyn
15
 */
16
class Swift_CharacterStream_ArrayCharacterStream implements Swift_CharacterStream
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
    /**
19
     * A map of byte values and their respective characters
20
     *
21
     * @var array
22
     */
23
    private static $_charMap;
24
25
    /**
26
     * A map of characters and their derivative byte values
27
     *
28
     * @var array
29
     */
30
    private static $_byteMap;
31
32
    /**
33
     * The char reader (lazy-loaded) for the current charset
34
     *
35
     * @var Swift_CharacterReader
36
     */
37
    private $_charReader;
38
39
    /**
40
     * A factory for creating CharacterReader instance
41
     *
42
     * @var Swift_CharacterReaderFactory
43
     */
44
    private $_charReaderFactory;
45
46
    /**
47
     * The character set this stream is using
48
     *
49
     * @var string
50
     */
51
    private $_charset;
52
53
    /**
54
     * Array of characters
55
     *
56
     * @var array
57
     */
58
    private $_array = array();
59
60
    /**
61
     * Size of the array of character
62
     *
63
     * @var int
64
     */
65
    private $_array_size = 0;
66
67
    /**
68
     * The current character offset in the stream
69
     *
70
     * @var int
71
     */
72
    private $_offset = 0;
73
74
    /**
75
     * Create a new CharacterStream with the given $chars, if set.
76
     *
77
     * @param Swift_CharacterReaderFactory $factory for loading validators
78
     * @param string                       $charset used in the stream
79
     */
80 54
    public function __construct(Swift_CharacterReaderFactory $factory, $charset)
81
    {
82 54
        self::_initializeMaps();
83 54
        $this->setCharacterReaderFactory($factory);
84 54
        $this->setCharacterSet($charset);
85 54
    }
86
87
    /**
88
     * Set the character set used in this CharacterStream.
89
     *
90
     * @param string $charset
91
     */
92 54
    public function setCharacterSet($charset)
93
    {
94 54
        if ($charset) {
95 54
            $this->_charset = $charset;
96 54
97
            $this->_charReader = null;
98
            $this->setCharReaderForCurrentCharset();
99
        }
100
    }
101
102
    /**
103 54
     * Set the CharacterReaderFactory for multi charset support.
104
     *
105 54
     * @param Swift_CharacterReaderFactory $factory
106 54
     */
107
    public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
108
    {
109
        $this->_charReaderFactory = $factory;
110
    }
111
112
    /**
113 2
     * Set the char-reader for the current charset.
114
     */
115 2
    private function setCharReaderForCurrentCharset()
116 2
    {
117
        if (null === $this->_charReader) {
118
            $this->_charReader = $this->_charReaderFactory->getReaderFor($this->_charset);
119 2
        }
120 2
    }
121
122 2
    /**
123 2
     * Overwrite this character stream using the byte sequence in the byte stream.
124 2
     *
125 2
     * @param Swift_OutputByteStream $os output stream to read from
126
     */
127
    public function importByteStream(Swift_OutputByteStream $os)
128 2
    {
129 2
        $this->setCharReaderForCurrentCharset();
130 2
131 2
        $startLength = $this->_charReader->getInitialByteSize();
132 2
        while (false !== $bytes = $os->read($startLength)) {
133 2
134 2
            $c = array();
135 2
            $len = strlen($bytes);
136
            for ($i = 0; $i < $len; ++$i) {
137
                $c[] = self::$_byteMap[$bytes[$i]];
138
            }
139
140 2
            $size = count($c);
141 2
            $need = $this->_charReader->validateByteSequence($c, $size);
142
143 2
            if (
144
                $need > 0
145
                &&
146
                false !== $bytes = $os->read($need)
147
            ) {
148
149
                $len = strlen($bytes);
150
                for ($i = 0; $i < $len; ++$i) {
151 17
                    $c[] = self::$_byteMap[$bytes[$i]];
152
                }
153 17
154 17
            }
155 17
156
            $this->_array[] = $c;
157
            ++$this->_array_size;
158
        }
159
    }
160
161
    /**
162
     * Import a string a bytes into this CharacterStream, overwriting any existing
163
     * data in the stream.
164
     *
165 7
     * @param string $string
166
     */
167 7
    public function importString($string)
168 6
    {
169
        $this->flushContents();
170
        $this->write($string);
171
    }
172 6
173 6
    /**
174 6
     * Read $length characters from the stream and move the internal pointer
175 6
     * $length further into the stream.
176 2
     *
177
     * @param int $length
178 6
     *
179
     * @return false|string false on error
180
     */
181 6
    public function read($length)
182
    {
183 6
        if ($this->_offset === $this->_array_size) {
184 6
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Swift_CharacterStream::read of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
185 6
        }
186
187
        // Don't use array slice
188 6
        $arrays = array();
189
        $end = $length + $this->_offset;
190 View Code Duplication
        for ($i = $this->_offset; $i < $end; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
            if (!isset($this->_array[$i])) {
192
                break;
193
            }
194
            $arrays[] = $this->_array[$i];
195
        }
196
197
        $this->_offset += $i - $this->_offset; // Limit function calls
198
199 9
        $chars = false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression false; of type false adds false to the return on line 204 which is incompatible with the return type declared by the interface Swift_CharacterStream::read of type string. It seems like you forgot to handle an error condition.
Loading history...
200
        foreach ($arrays as $array) {
201 9
            $chars .= implode('', array_map('chr', $array));
202 9
        }
203
204
        return $chars;
205 9
    }
206 9
207 9
    /**
208 9
     * Read $length characters from the stream and return a 1-dimensional array
209 8
     * containing there octet values.
210
     *
211 9
     * @param int $length
212
     *
213
     * @return integer[]
214 9
     */
215
    public function readBytes($length)
216 9
    {
217
        if ($this->_offset === $this->_array_size) {
218
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Swift_CharacterStream::readBytes of type integer[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
219
        }
220
221
        $arrays = array();
222
        $end = $length + $this->_offset;
223 View Code Duplication
        for ($i = $this->_offset; $i < $end; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224 17
            if (!isset($this->_array[$i])) {
225
                break;
226 17
            }
227 17
            $arrays[] = $this->_array[$i];
228
        }
229
230 17
        $this->_offset += ($i - $this->_offset); // Limit function calls
231
232 17
        return call_user_func_array('array_merge', $arrays);
233 17
    }
234 17
235 17
    /**
236
     * Write $chars to the end of the stream.
237 17
     *
238 17
     * @param string $chars
239 17
     */
240 17
    public function write($chars)
241
    {
242 17
        $this->setCharReaderForCurrentCharset();
243
244 17
        $startLength = $this->_charReader->getInitialByteSize();
245 17
246 17
        $fp = fopen('php://memory', 'w+b');
247 17
        fwrite($fp, $chars);
248 17
        unset($chars);
249 17
        fseek($fp, 0, SEEK_SET);
250 17
251
        $buffer = array(0);
252 17
        $buf_pos = 1;
253
        $buf_len = 1;
254
        $has_data = true;
255 17
        do {
256 17
            $bytes = array();
257 17
258 17
            // Buffer Filing
259 17
            if ($buf_len - $buf_pos < $startLength) {
260
                $buf = array_splice($buffer, $buf_pos);
261 17
                $new = $this->_reloadBuffer($fp, 100);
262
263
                if ($new) {
264 17
                    $buffer = array_merge($buf, $new);
265 11
                    $buf_len = count($buffer);
266 2
                    $buf_pos = 0;
267
                } else {
268 2
                    $has_data = false;
269 2
                }
270 2
            }
271
272
            if ($buf_len - $buf_pos > 0) {
273 11
274 11
                $size = 0;
275 View Code Duplication
                for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
276
                    ++$size;
277 17
                    $bytes[] = $buffer[$buf_pos++];
278 17
                }
279
                $need = $this->_charReader->validateByteSequence($bytes, $size);
280 17
281
                if ($need > 0) {
282 17
                    if ($buf_len - $buf_pos < $need) {
283 17
                        $new = $this->_reloadBuffer($fp, $need);
284
285
                        if ($new) {
286
                            $buffer = array_merge($buffer, $new);
287
                            $buf_len = count($buffer);
288
                        }
289
                    }
290 1
291 View Code Duplication
                    for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
292 1
                        $bytes[] = $buffer[$buf_pos++];
293
                    }
294 1
                }
295
296
                $this->_array[] = $bytes;
297 1
                ++$this->_array_size;
298 1
            }
299
        } while ($has_data);
300
301
        fclose($fp);
302
    }
303 17
304
    /**
305 17
     * Move the internal pointer to $charOffset in the stream.
306 17
     *
307 17
     * @param int $charOffset
308 17
     */
309
    public function setPointer($charOffset)
310
    {
311
        if ($charOffset > $this->_array_size) {
312
            $charOffset = $this->_array_size;
313
        } elseif ($charOffset < 0) {
314
            $charOffset = 0;
315
        }
316 17
317
        $this->_offset = $charOffset;
318 17
    }
319 17
320 17
    /**
321 17
     * Empty the stream and reset the internal pointer.
322 17
     */
323 17
    public function flushContents()
324 17
    {
325
        $this->_offset = 0;
326
        $this->_array = array();
327 17
        $this->_array_size = 0;
328
    }
329
330
    /**
331
     * @param resource $fp
332
     * @param int      $len
333
     *
334 54
     * @return array|false false on error
335
     */
336 54
    private function _reloadBuffer($fp, $len)
337 1
    {
338 1
        if (!feof($fp)) {
339 1
            $bytes = fread($fp, $len);
340
341 1
            if ($bytes !== false) {
342
                $buf = array();
343 54
                $len = strlen($bytes);
344
345
                for ($i = 0; $i < $len; ++$i) {
346
                    $buf[] = self::$_byteMap[$bytes[$i]];
347
                }
348
349
                return $buf;
350
            }
351
        }
352
353
        return false;
354
    }
355
356
    private static function _initializeMaps()
357
    {
358
        if (!isset(self::$_charMap)) {
359
            self::$_charMap = array();
360
361
            for ($byte = 0; $byte < 256; ++$byte) {
362
                self::$_charMap[$byte] = chr($byte);
363
            }
364
365
            self::$_byteMap = array_flip(self::$_charMap);
366
        }
367
    }
368
}
369