Completed
Push — 5.x ( c9dc5a...95e895 )
by Lars
17:11 queued 04:33
created

setPointer()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.4746

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 10
ccs 5
cts 8
cp 0.625
crap 3.4746
rs 9.4285
c 0
b 0
f 0
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 48
    public function __construct(Swift_CharacterReaderFactory $factory, $charset)
81
    {
82 48
        self::_initializeMaps();
83 48
        $this->setCharacterReaderFactory($factory);
84 48
        $this->setCharacterSet($charset);
85 48
    }
86
87
    /**
88
     * Set the character set used in this CharacterStream.
89
     *
90
     * @param string $charset
91
     */
92 48
    public function setCharacterSet($charset)
93
    {
94 48
        if ($charset) {
95 48
            $this->_charset = $charset;
96
97 48
            $this->_charReader = null;
98 48
            $this->setCharReaderForCurrentCharset();
99 48
        }
100 48
    }
101
102
    /**
103
     * Set the CharacterReaderFactory for multi charset support.
104
     *
105
     * @param Swift_CharacterReaderFactory $factory
106
     */
107 48
    public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
108
    {
109 48
        $this->_charReaderFactory = $factory;
110 48
    }
111
112
    /**
113
     * Set the char-reader for the current charset.
114
     */
115 48
    private function setCharReaderForCurrentCharset()
116
    {
117 48
        if (null === $this->_charReader) {
118 48
            $this->_charReader = $this->_charReaderFactory->getReaderFor($this->_charset);
119 48
        }
120 48
    }
121
122
    /**
123
     * Overwrite this character stream using the byte sequence in the byte stream.
124
     *
125
     * @param Swift_OutputByteStream $os output stream to read from
126
     */
127 2
    public function importByteStream(Swift_OutputByteStream $os)
128
    {
129 2
        $this->setCharReaderForCurrentCharset();
130
131 2
        $startLength = $this->_charReader->getInitialByteSize();
132 2
        while (false !== $bytes = $os->read($startLength)) {
133
134 2
            $c = array();
135 2
            $len = strlen($bytes);
136 2
            for ($i = 0; $i < $len; ++$i) {
137 2
                $c[] = self::$_byteMap[$bytes[$i]];
138 2
            }
139
140 2
            $size = count($c);
141 2
            $need = $this->_charReader->validateByteSequence($c, $size);
142
143
            if (
144
                $need > 0
145 2
                &&
146 2
                false !== $bytes = $os->read($need)
147 2
            ) {
148
149 2
                $len = strlen($bytes);
150 2
                for ($i = 0; $i < $len; ++$i) {
151 2
                    $c[] = self::$_byteMap[$bytes[$i]];
152 2
                }
153
154 2
            }
155
156 2
            $this->_array[] = $c;
157 2
            ++$this->_array_size;
158 2
        }
159 2
    }
160
161
    /**
162
     * Import a string a bytes into this CharacterStream, overwriting any existing
163
     * data in the stream.
164
     *
165
     * @param string $string
166
     */
167 17
    public function importString($string)
168
    {
169 17
        $this->flushContents();
170 17
        $this->write($string);
171 17
    }
172
173
    /**
174
     * Read $length characters from the stream and move the internal pointer
175
     * $length further into the stream.
176
     *
177
     * @param int $length
178
     *
179
     * @return false|string false on error
180
     */
181 7
    public function read($length)
182
    {
183 7
        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
        }
186
187
        // Don't use array slice
188 6
        $arrays = array();
189 6
        $end = $length + $this->_offset;
190 6 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 6
            if (!isset($this->_array[$i])) {
192 2
                break;
193
            }
194 6
            $arrays[] = $this->_array[$i];
195 6
        }
196
197 6
        $this->_offset += $i - $this->_offset; // Limit function calls
198
199 6
        $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 6
        foreach ($arrays as $array) {
201 6
            $chars .= implode('', array_map('chr', $array));
202 6
        }
203
204 6
        return $chars;
205
    }
206
207
    /**
208
     * Read $length characters from the stream and return a 1-dimensional array
209
     * containing there octet values.
210
     *
211
     * @param int $length
212
     *
213
     * @return int[]|false
214
     */
215 9
    public function readBytes($length)
216
    {
217 9
        if ($this->_offset === $this->_array_size) {
218 9
            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 9
        $arrays = array();
222 9
        $end = $length + $this->_offset;
223 9 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 9
            if (!isset($this->_array[$i])) {
225 8
                break;
226
            }
227 9
            $arrays[] = $this->_array[$i];
228 9
        }
229
230 9
        $this->_offset += ($i - $this->_offset); // Limit function calls
231
232 9
        return call_user_func_array('array_merge', $arrays);
233
    }
234
235
    /**
236
     * Write $chars to the end of the stream.
237
     *
238
     * @param string $chars
239
     */
240 17
    public function write($chars)
241
    {
242 17
        $this->setCharReaderForCurrentCharset();
243
244 17
        $startLength = $this->_charReader->getInitialByteSize();
245
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
251 17
        $buffer = array(0);
252 17
        $buf_pos = 1;
253 17
        $buf_len = 1;
254 17
        $has_data = true;
255
        do {
256 17
            $bytes = array();
257
258
            // Buffer Filing
259 17
            if ($buf_len - $buf_pos < $startLength) {
260 17
                $buf = array_splice($buffer, $buf_pos);
261 17
                $new = $this->_reloadBuffer($fp, 100);
262
263 17
                if ($new) {
264 17
                    $buffer = array_merge($buf, $new);
265 17
                    $buf_len = count($buffer);
266 17
                    $buf_pos = 0;
267 17
                } else {
268 17
                    $has_data = false;
269
                }
270 17
            }
271
272 17
            if ($buf_len - $buf_pos > 0) {
273
274 17
                $size = 0;
275 17 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 17
                    ++$size;
277 17
                    $bytes[] = $buffer[$buf_pos++];
278 17
                }
279 17
                $need = $this->_charReader->validateByteSequence($bytes, $size);
280
281 17
                if ($need > 0) {
282 11
                    if ($buf_len - $buf_pos < $need) {
283 2
                        $new = $this->_reloadBuffer($fp, $need);
284
285 2
                        if ($new) {
286 2
                            $buffer = array_merge($buffer, $new);
287 2
                            $buf_len = count($buffer);
288 2
                        }
289 2
                    }
290
291 11 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 11
                        $bytes[] = $buffer[$buf_pos++];
293 11
                    }
294 11
                }
295
296 17
                $this->_array[] = $bytes;
297 17
                ++$this->_array_size;
298 17
            }
299 17
        } while ($has_data);
300
301 17
        fclose($fp);
302 17
    }
303
304
    /**
305
     * Move the internal pointer to $charOffset in the stream.
306
     *
307
     * @param int $charOffset
308
     */
309 1
    public function setPointer($charOffset)
310
    {
311 1
        if ($charOffset > $this->_array_size) {
312
            $charOffset = $this->_array_size;
313 1
        } elseif ($charOffset < 0) {
314
            $charOffset = 0;
315
        }
316
317 1
        $this->_offset = $charOffset;
318 1
    }
319
320
    /**
321
     * Empty the stream and reset the internal pointer.
322
     */
323 17
    public function flushContents()
324
    {
325 17
        $this->_offset = 0;
326 17
        $this->_array = array();
327 17
        $this->_array_size = 0;
328 17
    }
329
330
    /**
331
     * @param resource $fp
332
     * @param int      $len
333
     *
334
     * @return array|false false on error
335
     */
336 17
    private function _reloadBuffer($fp, $len)
337
    {
338 17
        if (!feof($fp)) {
339 17
            $bytes = fread($fp, $len);
340
341 17
            if ($bytes !== false) {
342 17
                $buf = array();
343 17
                $len = strlen($bytes);
344
345 17
                for ($i = 0; $i < $len; ++$i) {
346 17
                    $buf[] = self::$_byteMap[$bytes[$i]];
347 17
                }
348
349 17
                return $buf;
350
            }
351
        }
352
353 17
        return false;
354
    }
355
356 48
    private static function _initializeMaps()
357
    {
358 48
        if (!isset(self::$_charMap)) {
359 1
            self::$_charMap = array();
360
361 1
            for ($byte = 0; $byte < 256; ++$byte) {
362 1
                self::$_charMap[$byte] = chr($byte);
363 1
            }
364
365 1
            self::$_byteMap = array_flip(self::$_charMap);
366 1
        }
367 48
    }
368
}
369