Completed
Push — add/amp-pwa-experiment ( efea12 )
by
unknown
11:53
created

FastImage   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 240
rs 8.3673
c 0
b 0
f 0
wmc 45
lcom 1
cbo 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A load() 0 6 2
A close() 0 10 2
A getSize() 0 15 3
C getType() 0 28 7
B parseSize() 0 18 5
A parseSizeForPNG() 0 6 1
A parseSizeForGIF() 0 6 1
A parseSizeForBMP() 0 8 2
C parseSizeForJPEG() 0 58 12
B getChars() 0 30 5
A getByte() 0 7 1
A readInt() 0 6 1
A __destruct() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FastImage 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 FastImage, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * FastImage - Because sometimes you just want the size!
5
 * Based on the Ruby Implementation by Steven Sykes (https://github.com/sdsykes/fastimage)
6
 *
7
 * Copyright (c) 2012 Tom Moor
8
 * Tom Moor, http://tommoor.com
9
 *
10
 * MIT Licensed
11
 * @version 0.1
12
 */
13
14
class FastImage
15
{
16
	private $strpos = 0;
17
	private $str;
18
	private $type;
19
	private $handle;
20
21
	public function __construct($uri = null)
22
	{
23
		if ($uri) $this->load($uri);
24
	}
25
26
27
	public function load($uri)
28
	{
29
		if ($this->handle) $this->close();
30
31
		$this->handle = fopen($uri, 'r');
32
	}
33
34
35
	public function close()
36
	{
37
		if ($this->handle)
38
		{
39
			fclose($this->handle);
40
			$this->handle = null;
41
			$this->type = null;
42
			$this->str = null;
43
		}
44
	}
45
46
47
	public function getSize()
48
	{
49
		if (!$this->handle)
50
		{
51
			return false;
52
		}
53
54
		$this->strpos = 0;
55
		if ($this->getType())
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getType() of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
56
		{
57
			return array_values($this->parseSize());
58
		}
59
60
		return false;
61
	}
62
63
64
	public function getType()
65
	{
66
		if (!$this->handle)
67
		{
68
			return false;
69
		}
70
71
		$this->strpos = 0;
72
73
		if (!$this->type)
74
		{
75
			switch ($this->getChars(2))
76
			{
77
				case "BM":
78
					return $this->type = 'bmp';
79
				case "GI":
80
					return $this->type = 'gif';
81
				case chr(0xFF).chr(0xd8):
82
					return $this->type = 'jpeg';
83
				case chr(0x89).'P':
84
					return $this->type = 'png';
85
				default:
86
					return false;
87
			}
88
		}
89
90
		return $this->type;
91
	}
92
93
94
	private function parseSize()
95
	{
96
		$this->strpos = 0;
97
98
		switch ($this->type)
99
		{
100
			case 'png':
101
				return $this->parseSizeForPNG();
102
			case 'gif':
103
				return $this->parseSizeForGIF();
104
			case 'bmp':
105
				return $this->parseSizeForBMP();
106
			case 'jpeg':
107
				return $this->parseSizeForJPEG();
108
		}
109
110
		return null;
111
	}
112
113
114
	private function parseSizeForPNG()
115
	{
116
		$chars = $this->getChars(25);
117
118
		return unpack("N*", substr($chars, 16, 8));
119
	}
120
121
122
	private function parseSizeForGIF()
123
	{
124
		$chars = $this->getChars(11);
125
126
		return unpack("S*", substr($chars, 6, 4));
127
	}
128
129
130
	private function parseSizeForBMP()
131
	{
132
		$chars = $this->getChars(29);
133
	 	$chars = substr($chars, 14, 14);
134
		$type = unpack('C', $chars);
135
136
		return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8));
137
	}
138
139
140
	private function parseSizeForJPEG()
141
	{
142
		$state = null;
143
		$i = 0;
0 ignored issues
show
Unused Code introduced by
$i is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
144
145
		while (true)
146
		{
147
			switch ($state)
148
			{
149
				default:
150
					$this->getChars(2);
151
					$state = 'started';
152
					break;
153
154
				case 'started':
155
					$b = $this->getByte();
156
					if ($b === false) return false;
157
158
					$state = $b == 0xFF ? 'sof' : 'started';
159
					break;
160
161
				case 'sof':
162
					$b = $this->getByte();
163
					if (in_array($b, range(0xe0, 0xef)))
164
					{
165
						$state = 'skipframe';
166
					}
167
					elseif (in_array($b, array_merge(range(0xC0,0xC3), range(0xC5,0xC7), range(0xC9,0xCB), range(0xCD,0xCF))))
168
					{
169
						$state = 'readsize';
170
					}
171
					elseif ($b == 0xFF)
172
					{
173
						$state = 'sof';
174
					}
175
					else
176
					{
177
						$state = 'skipframe';
178
					}
179
					break;
180
181
				case 'skipframe':
182
					$skip = $this->readInt($this->getChars(2)) - 2;
183
					$state = 'doskip';
184
					break;
185
186
				case 'doskip':
187
					$this->getChars($skip);
0 ignored issues
show
Bug introduced by
The variable $skip does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
188
					$state = 'started';
189
					break;
190
191
				case 'readsize':
192
					$c = $this->getChars(7);
193
194
					return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2)));
195
			}
196
		}
197
	}
198
199
200
	private function getChars($n)
201
	{
202
		$response = null;
203
204
		// do we need more data?
205
		if ($this->strpos + $n -1 >= strlen($this->str))
206
		{
207
			$end = ($this->strpos + $n);
208
209
			while (strlen($this->str) < $end && $response !== false)
210
			{
211
				// read more from the file handle
212
				$need = $end - ftell($this->handle);
213
214
				if ($response = fread($this->handle, $need))
215
				{
216
					$this->str .= $response;
217
				}
218
				else
219
				{
220
					return false;
221
				}
222
			}
223
		}
224
225
		$result = substr($this->str, $this->strpos, $n);
226
		$this->strpos += $n;
227
228
		return $result;
229
	}
230
231
232
	private function getByte()
233
	{
234
		$c = $this->getChars(1);
235
		$b = unpack("C", $c);
236
237
		return reset($b);
238
	}
239
240
241
	private function readInt($str)
242
	{
243
		$size = unpack("C*", $str);
244
245
		return ($size[1] << 8) + $size[2];
246
	}
247
248
249
	public function __destruct()
250
	{
251
		$this->close();
252
	}
253
}
254