Completed
Push — master ( 0a636a...80871c )
by Peter
15:13
created

DocComment   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 96.91%

Importance

Changes 9
Bugs 2 Features 1
Metric Value
wmc 74
c 9
b 2
f 1
lcom 1
cbo 0
dl 0
loc 286
ccs 188
cts 194
cp 0.9691
rs 5.5245

10 Methods

Rating   Name   Duplication   Size   Complexity  
A clearCache() 0 9 1
C forFile() 0 19 9
B forClass() 0 15 8
A process() 0 9 2
D parse() 0 146 40
B getString() 0 25 5
A getTokens() 0 4 1
A get() 0 15 4
A forMethod() 0 7 2
A forProperty() 0 7 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 * This software package is licensed under AGPL, Commercial license.
5
 *
6
 * @package maslosoft/addendum
7
 * @licence AGPL, Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]> (Meta container, further improvements, bugfixes)
9
 * @copyright Copyright (c) Maslosoft (Meta container, further improvements, bugfixes)
10
 * @copyright Copyright (c) Jan Suchal (Original version, builder, parser)
11
 * @link http://maslosoft.com/addendum/ - maslosoft addendum
12
 * @link https://code.google.com/p/addendum/ - original addendum project
13
 */
14
15
namespace Maslosoft\Addendum\Builder;
16
17
use ReflectionClass;
18
use ReflectionMethod;
19
use ReflectionProperty;
20
21
class DocComment
22
{
23
24
	private static $use = [];
25
	private static $useAliases = [];
26
	private static $namespaces = [];
27
	private static $classNames = [];
28
	private static $classes = [];
29
	private static $methods = [];
30
	private static $fields = [];
31
	private static $parsedFiles = [];
32
33 1
	public static function clearCache()
34
	{
35 1
		self::$namespaces = [];
36 1
		self::$classNames = [];
37 1
		self::$classes = [];
38 1
		self::$methods = [];
39 1
		self::$fields = [];
40 1
		self::$parsedFiles = [];
41 1
	}
42
43 32
	public function get($reflection)
44
	{
45 32
		if ($reflection instanceof ReflectionClass)
46 32
		{
47 32
			return $this->forClass($reflection);
48
		}
49 17
		elseif ($reflection instanceof ReflectionMethod)
50
		{
51 4
			return $this->forMethod($reflection);
52
		}
53 15
		elseif ($reflection instanceof ReflectionProperty)
54
		{
55 15
			return $this->forProperty($reflection);
56
		}
57
	}
58
59
	/**
60
	 * Get doc comment for file
61
	 * If file contains several classes, $className will be returned
62
	 * If file name matches class name, this class will be returned
63
	 * @param string $name
64
	 * @param string $className
65
	 */
66 1
	public function forFile($name, $className = null)
67
	{
68 1
		$fqn = $this->process($name);
69 1
		if (null !== $className)
70 1
		{
71
			$fqn = $className;
72
		}
73
		$result = [
74 1
			'namespace' => isset(self::$namespaces[$fqn]) ? self::$namespaces[$fqn] : [],
75 1
			'use' => isset(self::$use[$fqn]) ? self::$use[$fqn] : [],
76 1
			'useAliases' => isset(self::$useAliases[$fqn]) ? self::$useAliases[$fqn] : [],
77 1
			'className' => isset(self::$classNames[$fqn]) ? self::$classNames[$fqn] : [],
78 1
			'class' => isset(self::$classes[$fqn]) ? self::$classes[$fqn] : '',
79 1
			'methods' => isset(self::$methods[$fqn]) ? self::$methods[$fqn] : [],
80 1
			'fields' => isset(self::$fields[$fqn]) ? self::$fields[$fqn] : []
81 1
		];
82
83 1
		return $result;
84
	}
85
86 37
	public function forClass($reflection)
87
	{
88 37
		$this->process($reflection->getFileName());
89 37
		$fqn = $reflection->getName();
90
		$result = [
91 37
			'namespace' => isset(self::$namespaces[$fqn]) ? self::$namespaces[$fqn] : [],
92 37
			'use' => isset(self::$use[$fqn]) ? self::$use[$fqn] : [],
93 37
			'useAliases' => isset(self::$useAliases[$fqn]) ? self::$useAliases[$fqn] : [],
94 37
			'className' => isset(self::$classNames[$fqn]) ? self::$classNames[$fqn] : [],
95 37
			'class' => isset(self::$classes[$fqn]) ? self::$classes[$fqn] : '',
96 37
			'methods' => isset(self::$methods[$fqn]) ? self::$methods[$fqn] : [],
97 37
			'fields' => isset(self::$fields[$fqn]) ? self::$fields[$fqn] : []
98 37
		];
99 37
		return $result;
100
	}
101
102 4
	public function forMethod($reflection)
103
	{
104 4
		$this->process($reflection->getDeclaringClass()->getFileName());
105 4
		$class = $reflection->getDeclaringClass()->getName();
106 4
		$method = $reflection->getName();
107 4
		return isset(self::$methods[$class][$method]) ? self::$methods[$class][$method] : false;
108
	}
109
110 15
	public function forProperty($reflection)
111
	{
112 15
		$this->process($reflection->getDeclaringClass()->getFileName());
113 15
		$class = $reflection->getDeclaringClass()->getName();
114 15
		$field = $reflection->getName();
115 15
		return isset(self::$fields[$class][$field]) ? self::$fields[$class][$field] : false;
116
	}
117
118 37
	private function process($file)
119
	{
120 37
		if (!isset(self::$parsedFiles[$file]))
121 37
		{
122 34
			$fqn = $this->parse($file);
123 34
			self::$parsedFiles[$file] = $fqn;
124 34
		}
125 37
		return self::$parsedFiles[$file];
126
	}
127
128 34
	protected function parse($file)
129
	{
130 34
		$use = [];
131 34
		$aliases = [];
132 34
		$namespace = '\\';
133 34
		$tokens = $this->getTokens($file);
134 34
		$class = false;
135 34
		$fqn = false;
136 34
		$comment = null;
137 34
		$max = count($tokens);
138 34
		$i = 0;
139 34
		while ($i < $max)
140
		{
141 34
			$token = $tokens[$i];
142 34
			if (is_array($token))
143 34
			{
144 34
				list($code, $value) = $token;
145
//				$tokenName = token_name($code);
146
147
				switch ($code)
148
				{
149 34
					case T_DOC_COMMENT:
150 34
						$comment = $value;
151 34
						break;
152
153 34
					case T_NAMESPACE:
154 34
						$comment = false;
155 34
						$tokensCount = count($tokens);
156 34
						for ($j = $i + 1; $j < $tokensCount; $j++)
157
						{
158 34
							if ($tokens[$j][0] === T_STRING)
159 34
							{
160 34
								$namespace .= '\\' . $tokens[$j][1];
161 34
							}
162 34
							elseif ($tokens[$j] === '{' || $tokens[$j] === ';')
163
							{
164 34
								break;
165
							}
166 34
						}
167
168 34
						$namespace = preg_replace('~^\\\\+~', '', $namespace);
169 34
						break;
170 34
					case T_USE:
171
						// After class declaration, this should ignore `use` trait
172
						if ($class)
173 33
						{
174 3
							break;
175
						}
176 33
						$comment = false;
177 33
						$useNs = '';
178 33
						$tokensCount = count($tokens);
179 33
						$as = false;
180 33
						for ($j = $i + 1; $j < $tokensCount; $j++)
181
						{
182 33
							$tokenName = $tokens[$j][0];
183 33
							if (isset($tokens[$j][1]) && $tokens[$j][1] == 'IMatcher')
184 33
							{
185 1
								echo 's';
186 1
							}
187 33
							if ($tokenName === T_STRING && !$as)
188 33
							{
189 33
								$useNs .= '\\' . $tokens[$j][1];
190 33
							}
191 33
							if ($tokenName === T_STRING && $as)
192 33
							{
193 2
								$alias = $tokens[$j][1];
194 2
								break;
195
							}
196 33
							if ($tokenName === T_AS)
197 33
							{
198 2
								$as = true;
199 2
							}
200 33
							if ($tokens[$j] === '{' || $tokens[$j] === ';')
201 33
							{
202 33
								break;
203
							}
204 33
						}
205 33
						$use[] = preg_replace('~^\\\\+~', '', $useNs);
206
						if ($as)
207 33
						{
208 2
							$aliases[$useNs] = $alias;
0 ignored issues
show
Bug introduced by
The variable $alias 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...
209 2
						}
210 33
						break;
211 34
					case T_TRAIT:
212 34
					case T_CLASS:
213 34
					case T_INTERFACE:
214
						// Ignore magic constant `::class`
215 34
						if ($tokens[$i - 1][0] == T_DOUBLE_COLON)
216 34
						{
217 4
							break;
218
						}
219 34
						$class = $this->getString($tokens, $i, $max);
220 34
						$fqn = sprintf('%s\%s', $namespace, $class);
221 34
						if ($comment !== false)
222 34
						{
223 27
							self::$classes[$fqn] = $comment;
224 27
							$comment = false;
225 27
						}
226 34
						self::$namespaces[$fqn] = $namespace;
227 34
						self::$classNames[$fqn] = $class;
228 34
						self::$use[$fqn] = $use;
229 34
						self::$useAliases[$fqn] = $aliases;
230 34
						break;
231
232 34
					case T_VARIABLE:
233 33
						if ($comment !== false && $class)
234 33
						{
235 23
							$field = substr($token[1], 1);
236 23
							self::$fields[$fqn][$field] = $comment;
237 23
							$comment = false;
238 23
						}
239 33
						break;
240
241 34
					case T_FUNCTION:
242 22
						if ($comment !== false && $class)
243 22
						{
244 6
							$function = $this->getString($tokens, $i, $max);
245 6
							self::$methods[$fqn][$function] = $comment;
246 6
							$comment = false;
247 6
						}
248
249 22
						break;
250
251
					// ignore
252 34
					case T_WHITESPACE:
253 34
					case T_PUBLIC:
254 34
					case T_PROTECTED:
255 34
					case T_PRIVATE:
256 34
					case T_ABSTRACT:
257 34
					case T_FINAL:
258 34
					case T_VAR:
259 34
						break;
260
261 34
					default:
262 34
						$comment = false;
263 34
						break;
264 34
				}
265 34
			}
266
			else
267
			{
268 34
				$comment = false;
269
			}
270 34
			$i++;
271 34
		}
272 34
		return $fqn;
273
	}
274
275 34
	private function getString($tokens, &$i, $max)
276
	{
277
		do
278
		{
279
			/**
280
			 * TODO Workaround for problem desribed near T_CLASS token
281
			 */
282 34
			if (!isset($tokens[$i]))
283 34
			{
284
				$i++;
285
				continue;
286
			}
287 34
			$token = $tokens[$i];
288 34
			$i++;
289 34
			if (is_array($token))
290 34
			{
291 34
				if ($token[0] == T_STRING)
292 34
				{
293 34
					return $token[1];
294
				}
295 34
			}
296
		}
297 34
		while ($i <= $max);
298
		return false;
299
	}
300
301 34
	private function getTokens($file)
302
	{
303 34
		return token_get_all(file_get_contents($file));
304
	}
305
306
}
307