Completed
Push — master ( 7faa0b...742751 )
by Peter
02:10
created

DocComment::parse()   D

Complexity

Conditions 41
Paths 59

Size

Total Lines 147
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 72
CRAP Score 41.1075

Importance

Changes 0
Metric Value
dl 0
loc 147
ccs 72
cts 75
cp 0.96
rs 4.1818
c 0
b 0
f 0
cc 41
eloc 94
nc 59
nop 2
crap 41.1075

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Maslosoft\Addendum\Utilities\ClassChecker;
18
use Maslosoft\Addendum\Utilities\NameNormalizer;
19
use ReflectionClass;
20
use ReflectionMethod;
21
use ReflectionProperty;
22
use Reflector;
23
24
class DocComment
25
{
26
27
	private static $use = [];
28
	private static $useAliases = [];
29
	private static $namespaces = [];
30
	private static $classNames = [];
31
	private static $classes = [];
32
	private static $methods = [];
33
	private static $fields = [];
34 1
	private static $parsedFiles = [];
35
36 1
	public static function clearCache()
37 1
	{
38 1
		self::$namespaces = [];
39 1
		self::$classNames = [];
40 1
		self::$classes = [];
41 1
		self::$methods = [];
42 1
		self::$fields = [];
43
		self::$parsedFiles = [];
44 43
	}
45
46 43
	public function get($reflection)
47
	{
48 36
		if ($reflection instanceof ReflectionClass)
49
		{
50
			return $this->forClass($reflection);
51
		}
52 8
		elseif ($reflection instanceof ReflectionMethod)
53
		{
54
			return $this->forMethod($reflection);
55
		}
56 21
		elseif ($reflection instanceof ReflectionProperty)
57
		{
58
			return $this->forProperty($reflection);
59
		}
60
	}
61
62
	/**
63
	 * Get doc comment for file
64
	 * If file contains several classes, $className will be returned
65
	 * If file name matches class name, this class will be returned
66
	 * @param string $name
67 2
	 * @param string $className
68
	 */
69 2
	public function forFile($name, $className = null)
70 2
	{
71
		$fqn = $this->process($name, $className);
0 ignored issues
show
Documentation introduced by
$className is of type string|null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
72
		if (null !== $className)
73
		{
74
			$fqn = $className;
75
		}
76
		/**
77
		 * TODO Use some container here with ArrayAccess interface. Array-like access is *required* here.
78 2
		 */
79 2
		$result = [
80 2
			'namespace' => isset(self::$namespaces[$fqn]) ? self::$namespaces[$fqn] : [],
81 2
			'use' => isset(self::$use[$fqn]) ? self::$use[$fqn] : [],
82 2
			'useAliases' => isset(self::$useAliases[$fqn]) ? self::$useAliases[$fqn] : [],
83 2
			'className' => isset(self::$classNames[$fqn]) ? self::$classNames[$fqn] : [],
84 2
			'class' => isset(self::$classes[$fqn]) ? self::$classes[$fqn] : '',
85
			'methods' => isset(self::$methods[$fqn]) ? self::$methods[$fqn] : [],
86
			'fields' => isset(self::$fields[$fqn]) ? self::$fields[$fqn] : []
87 2
		];
88
89
		return $result;
90 44
	}
91
92 44
	public function forClass(Reflector $reflection)
93 44
	{
94
		if (ClassChecker::isAnonymous($reflection->name))
0 ignored issues
show
Bug introduced by
Accessing name on the interface Reflector suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
95 44
		{
96 44
			echo '';
97 44
		}
98 44
		$fqn = $reflection->getName();
99 44
		$this->process($reflection->getFileName(), $fqn);
100 44
		if (ClassChecker::isAnonymous($reflection->name))
0 ignored issues
show
Bug introduced by
Accessing name on the interface Reflector suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
101 44
		{
102
			$info = new \ReflectionClass($reflection->getName());
103 44
			$anonFqn = $reflection->getName();
104
			NameNormalizer::normalize($anonFqn);
105
			$this->processAnonymous($info, $anonFqn);
106 8
		}
107
		$result = [
108 8
			'namespace' => isset(self::$namespaces[$fqn]) ? self::$namespaces[$fqn] : [],
109 8
			'use' => isset(self::$use[$fqn]) ? self::$use[$fqn] : [],
110 8
			'useAliases' => isset(self::$useAliases[$fqn]) ? self::$useAliases[$fqn] : [],
111 8
			'className' => isset(self::$classNames[$fqn]) ? self::$classNames[$fqn] : [],
112
			'class' => isset(self::$classes[$fqn]) ? self::$classes[$fqn] : '',
113
			'methods' => isset(self::$methods[$fqn]) ? self::$methods[$fqn] : [],
114 21
			'fields' => isset(self::$fields[$fqn]) ? self::$fields[$fqn] : []
115
		];
116 21
		if (ClassChecker::isAnonymous($reflection->name))
0 ignored issues
show
Bug introduced by
Accessing name on the interface Reflector suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
117 21
		{
118 21
			$result['className'] = self::$classNames[$anonFqn];
0 ignored issues
show
Bug introduced by
The variable $anonFqn 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...
119 21
			$result['class'] = self::$classes[$anonFqn];
120
		}
121
		return $result;
122 51
	}
123
124 51
	public function forMethod(Reflector $reflection)
125
	{
126 43
		$this->process($reflection->getDeclaringClass()->getFileName());
127 43
128
129 51
130
		$class = $reflection->getDeclaringClass()->getName();
131
		$method = $reflection->getName();
132 43
		return isset(self::$methods[$class][$method]) ? self::$methods[$class][$method] : false;
133
	}
134 43
135 43
	public function forProperty(Reflector $reflection)
136 43
	{
137 43
		$this->process($reflection->getDeclaringClass()->getFileName());
138 43
139 43
140 43
		$class = $reflection->getDeclaringClass()->getName();
141 43
		$field = $reflection->getName();
142 43
		return isset(self::$fields[$class][$field]) ? self::$fields[$class][$field] : false;
143 43
	}
144
145 43
	private function processAnonymous(Reflector $reflection, $fqn)
146 43
	{
147
		if (!isset(self::$parsedFiles[$fqn]))
148 43
		{
149
			/* @var $reflection \Maslosoft\Addendum\Reflection\ReflectionAnnotatedClass */
150
			self::$classNames[$fqn] = $fqn;
151
			self::$classes[$fqn] = $reflection->getDocComment();
152 43
			self::$methods[$fqn] = [];
153 43
			self::$fields[$fqn] = [];
154 43
			foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method)
155
			{
156 43
				self::$methods[$fqn][$method->name] = $method->getDocComment();
157 43
			}
158 43
			foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property)
159 43
			{
160
				self::$fields[$fqn][$property->name] = $property->getDocComment();
161 43
			}
162
			self::$parsedFiles[$fqn] = $fqn;
163 43
		}
164
		return self::$parsedFiles[$fqn];
165 43
	}
166
167 43
	private function process($file, $fqn = false)
168
	{
169
		if (!isset(self::$parsedFiles[$file]))
170
		{
171 43
			$fqn = $this->parse($file, $fqn);
172 43
			self::$parsedFiles[$file] = $fqn;
173 43
		}
174
		return self::$parsedFiles[$file];
175 40
	}
176
177 4
	protected function parse($file, $fqn = false)
178
	{
179 40
		$use = [];
180 40
		$aliases = [];
181 40
		$namespace = '\\';
182 40
		$tokens = $this->getTokens($file);
183 40
		$class = false;
184
		$comment = null;
185 40
		$max = count($tokens);
186 40
		$i = 0;
187
		while ($i < $max)
188 1
		{
189
			$token = $tokens[$i];
190 40
			if (is_array($token))
191
			{
192 40
				list($code, $value) = $token;
193
194 40
				switch ($code)
195
				{
196 2
					case T_DOC_COMMENT:
197 2
						$comment = $value;
198
						break;
199 40
200
					case T_NAMESPACE:
201 2
						$comment = false;
202
						$tokensCount = count($tokens);
203 40
						for ($j = $i + 1; $j < $tokensCount; $j++)
204
						{
205 40
							if ($tokens[$j][0] === T_STRING)
206
							{
207
								$namespace .= '\\' . $tokens[$j][1];
208 40
							}
209 40
							elseif ($tokens[$j] === '{' || $tokens[$j] === ';')
210
							{
211 2
								break;
212
							}
213 40
						}
214 43
215 43
						$namespace = preg_replace('~^\\\\+~', '', $namespace);
216 43
						break;
217
					case T_USE:
218 43
						// After class declaration, this should ignore `use` trait
219
						if ($class)
220 4
						{
221
							break;
222 43
						}
223 43
						$comment = false;
224 43
						$useNs = '';
225
						$tokensCount = count($tokens);
226 36
						$as = false;
227 36
						for ($j = $i + 1; $j < $tokensCount; $j++)
228
						{
229 43
							$tokenName = $tokens[$j][0];
230 43
							if (isset($tokens[$j][1]) && $tokens[$j][1] == 'IMatcher')
231 43
							{
232 43
								echo 's';
233 43
							}
234
							if ($tokenName === T_STRING && !$as)
235 43
							{
236 36
								$useNs .= '\\' . $tokens[$j][1];
237
							}
238 30
							if ($tokenName === T_STRING && $as)
239 30
							{
240 30
								$alias = $tokens[$j][1];
241
								break;
242 36
							}
243
							if ($tokenName === T_AS)
244 43
							{
245 18
								$as = true;
246
							}
247 11
							if ($tokens[$j] === '{' || $tokens[$j] === ';')
248 11
							{
249 11
								break;
250
							}
251
						}
252 18
						$use[] = preg_replace('~^\\\\+~', '', $useNs);
253
						if ($as)
254
						{
255 43
							$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...
256 43
						}
257 43
						break;
258 43
					case T_TRAIT:
259 43
					case T_CLASS:
260 43
					case T_INTERFACE:
261 43
						// Ignore magic constant `::class`
262 43
						if ($tokens[$i - 1][0] == T_DOUBLE_COLON)
263
						{
264
							break;
265 43
						}
266 43
						$class = $this->getString($tokens, $i, $max);
267
						if (!$fqn)
268
						{
269
							$fqn = sprintf('%s\%s', $namespace, $class);
270
						}
271 43
						if ($comment !== false)
272
						{
273 43
							self::$classes[$fqn] = $comment;
274
							$comment = false;
275 43
						}
276
						self::$namespaces[$fqn] = $namespace;
277
						self::$classNames[$fqn] = $class;
278 43
						self::$use[$fqn] = $use;
279
						self::$useAliases[$fqn] = $aliases;
280
						break;
281
282
					case T_VARIABLE:
283
						if ($comment !== false && $class)
284
						{
285 43
							$field = substr($token[1], 1);
286
							self::$fields[$fqn][$field] = $comment;
287
							$comment = false;
288
						}
289
						break;
290 43
291 43
					case T_FUNCTION:
292 43
						if ($comment !== false && $class)
293
						{
294 43
							$function = $this->getString($tokens, $i, $max);
295
							self::$methods[$fqn][$function] = $comment;
296 43
							$comment = false;
297
						}
298
299
						break;
300 43
301
					// ignore
302
					case T_WHITESPACE:
303
					case T_PUBLIC:
304 43
					case T_PROTECTED:
305
					case T_PRIVATE:
306 43
					case T_ABSTRACT:
307
					case T_FINAL:
308
					case T_VAR:
309
						break;
310
311
					default:
312
						$comment = false;
313
						break;
314
				}
315
			}
316
			else
317
			{
318
				$comment = false;
319
			}
320
			$i++;
321
		}
322
		return $fqn;
323
	}
324
325
	private function getString($tokens, &$i, $max)
326
	{
327
		do
328
		{
329
			/**
330
			 * TODO Workaround for problem desribed near T_CLASS token
331
			 */
332
			if (!isset($tokens[$i]))
333
			{
334
				$i++;
335
				continue;
336
			}
337
			$token = $tokens[$i];
338
			$i++;
339
			if (is_array($token))
340
			{
341
				if ($token[0] == T_STRING)
342
				{
343
					return $token[1];
344
				}
345
			}
346
		}
347
		while ($i <= $max);
348
		return false;
349
	}
350
351
	private function getTokens($file)
352
	{
353
		return token_get_all(file_get_contents($file));
354
	}
355
356
}
357