Completed
Push — master ( 28256c...92a7d3 )
by Peter
20:38
created

DocComment::forFile()   D

Complexity

Conditions 9
Paths 256

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 9.1918

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 22
ccs 13
cts 15
cp 0.8667
rs 4.7088
cc 9
eloc 13
nc 256
nop 2
crap 9.1918
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 31
	public function get($reflection)
44
	{
45 31
		if ($reflection instanceof ReflectionClass)
46 31
		{
47 29
			return $this->forClass($reflection);
48
		}
49 17
		elseif ($reflection instanceof ReflectionMethod)
50
		{
51 3
			return $this->forMethod($reflection);
52
		}
53 16
		elseif ($reflection instanceof ReflectionProperty)
54
		{
55 16
			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 2
	public function forFile($name, $className = null)
67
	{
68 2
		$fqn = $this->process($name);
69 2
		if (null !== $className)
70 2
		{
71
			$fqn = $className;
72
		}
73
		/**
74
		 * TODO Use some container here with ArrayAccess interface. Array-like access is *required* here.
75
		 */
76
		$result = [
77 2
			'namespace' => isset(self::$namespaces[$fqn]) ? self::$namespaces[$fqn] : [],
78 2
			'use' => isset(self::$use[$fqn]) ? self::$use[$fqn] : [],
79 2
			'useAliases' => isset(self::$useAliases[$fqn]) ? self::$useAliases[$fqn] : [],
80 2
			'className' => isset(self::$classNames[$fqn]) ? self::$classNames[$fqn] : [],
81 2
			'class' => isset(self::$classes[$fqn]) ? self::$classes[$fqn] : '',
82 2
			'methods' => isset(self::$methods[$fqn]) ? self::$methods[$fqn] : [],
83 2
			'fields' => isset(self::$fields[$fqn]) ? self::$fields[$fqn] : []
84 2
		];
85
86 2
		return $result;
87
	}
88
89 35
	public function forClass($reflection)
90
	{
91 35
		$this->process($reflection->getFileName());
92 35
		$fqn = $reflection->getName();
93
		$result = [
94 35
			'namespace' => isset(self::$namespaces[$fqn]) ? self::$namespaces[$fqn] : [],
95 35
			'use' => isset(self::$use[$fqn]) ? self::$use[$fqn] : [],
96 35
			'useAliases' => isset(self::$useAliases[$fqn]) ? self::$useAliases[$fqn] : [],
97 35
			'className' => isset(self::$classNames[$fqn]) ? self::$classNames[$fqn] : [],
98 35
			'class' => isset(self::$classes[$fqn]) ? self::$classes[$fqn] : '',
99 35
			'methods' => isset(self::$methods[$fqn]) ? self::$methods[$fqn] : [],
100 35
			'fields' => isset(self::$fields[$fqn]) ? self::$fields[$fqn] : []
101 35
		];
102 35
		return $result;
103
	}
104
105 3
	public function forMethod($reflection)
106
	{
107 3
		$this->process($reflection->getDeclaringClass()->getFileName());
108 3
		$class = $reflection->getDeclaringClass()->getName();
109 3
		$method = $reflection->getName();
110 3
		return isset(self::$methods[$class][$method]) ? self::$methods[$class][$method] : false;
111
	}
112
113 16
	public function forProperty($reflection)
114
	{
115 16
		$this->process($reflection->getDeclaringClass()->getFileName());
116 16
		$class = $reflection->getDeclaringClass()->getName();
117 16
		$field = $reflection->getName();
118 16
		return isset(self::$fields[$class][$field]) ? self::$fields[$class][$field] : false;
119
	}
120
121 37
	private function process($file)
122
	{
123 37
		if (!isset(self::$parsedFiles[$file]))
124 37
		{
125 34
			$fqn = $this->parse($file);
126 34
			self::$parsedFiles[$file] = $fqn;
127 34
		}
128 37
		return self::$parsedFiles[$file];
129
	}
130
131 34
	protected function parse($file)
132
	{
133 34
		$use = [];
134 34
		$aliases = [];
135 34
		$namespace = '\\';
136 34
		$tokens = $this->getTokens($file);
137 34
		$class = false;
138 34
		$fqn = false;
139 34
		$comment = null;
140 34
		$max = count($tokens);
141 34
		$i = 0;
142 34
		while ($i < $max)
143
		{
144 34
			$token = $tokens[$i];
145 34
			if (is_array($token))
146 34
			{
147 34
				list($code, $value) = $token;
148
//				$tokenName = token_name($code);
149
150
				switch ($code)
151
				{
152 34
					case T_DOC_COMMENT:
153 34
						$comment = $value;
154 34
						break;
155
156 34
					case T_NAMESPACE:
157 34
						$comment = false;
158 34
						$tokensCount = count($tokens);
159 34
						for ($j = $i + 1; $j < $tokensCount; $j++)
160
						{
161 34
							if ($tokens[$j][0] === T_STRING)
162 34
							{
163 34
								$namespace .= '\\' . $tokens[$j][1];
164 34
							}
165 34
							elseif ($tokens[$j] === '{' || $tokens[$j] === ';')
166
							{
167 34
								break;
168
							}
169 34
						}
170
171 34
						$namespace = preg_replace('~^\\\\+~', '', $namespace);
172 34
						break;
173 34
					case T_USE:
174
						// After class declaration, this should ignore `use` trait
175
						if ($class)
176 33
						{
177 4
							break;
178
						}
179 33
						$comment = false;
180 33
						$useNs = '';
181 33
						$tokensCount = count($tokens);
182 33
						$as = false;
183 33
						for ($j = $i + 1; $j < $tokensCount; $j++)
184
						{
185 33
							$tokenName = $tokens[$j][0];
186 33
							if (isset($tokens[$j][1]) && $tokens[$j][1] == 'IMatcher')
187 33
							{
188 1
								echo 's';
189 1
							}
190 33
							if ($tokenName === T_STRING && !$as)
191 33
							{
192 33
								$useNs .= '\\' . $tokens[$j][1];
193 33
							}
194 33
							if ($tokenName === T_STRING && $as)
195 33
							{
196 2
								$alias = $tokens[$j][1];
197 2
								break;
198
							}
199 33
							if ($tokenName === T_AS)
200 33
							{
201 2
								$as = true;
202 2
							}
203 33
							if ($tokens[$j] === '{' || $tokens[$j] === ';')
204 33
							{
205 33
								break;
206
							}
207 33
						}
208 33
						$use[] = preg_replace('~^\\\\+~', '', $useNs);
209
						if ($as)
210 33
						{
211 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...
212 2
						}
213 33
						break;
214 34
					case T_TRAIT:
215 34
					case T_CLASS:
216 34
					case T_INTERFACE:
217
						// Ignore magic constant `::class`
218 34
						if ($tokens[$i - 1][0] == T_DOUBLE_COLON)
219 34
						{
220 4
							break;
221
						}
222 34
						$class = $this->getString($tokens, $i, $max);
223 34
						$fqn = sprintf('%s\%s', $namespace, $class);
224 34
						if ($comment !== false)
225 34
						{
226 27
							self::$classes[$fqn] = $comment;
227 27
							$comment = false;
228 27
						}
229 34
						self::$namespaces[$fqn] = $namespace;
230 34
						self::$classNames[$fqn] = $class;
231 34
						self::$use[$fqn] = $use;
232 34
						self::$useAliases[$fqn] = $aliases;
233 34
						break;
234
235 34
					case T_VARIABLE:
236 33
						if ($comment !== false && $class)
237 33
						{
238 24
							$field = substr($token[1], 1);
239 24
							self::$fields[$fqn][$field] = $comment;
240 24
							$comment = false;
241 24
						}
242 33
						break;
243
244 34
					case T_FUNCTION:
245 21
						if ($comment !== false && $class)
246 21
						{
247 5
							$function = $this->getString($tokens, $i, $max);
248 5
							self::$methods[$fqn][$function] = $comment;
249 5
							$comment = false;
250 5
						}
251
252 21
						break;
253
254
					// ignore
255 34
					case T_WHITESPACE:
256 34
					case T_PUBLIC:
257 34
					case T_PROTECTED:
258 34
					case T_PRIVATE:
259 34
					case T_ABSTRACT:
260 34
					case T_FINAL:
261 34
					case T_VAR:
262 34
						break;
263
264 34
					default:
265 34
						$comment = false;
266 34
						break;
267 34
				}
268 34
			}
269
			else
270
			{
271 34
				$comment = false;
272
			}
273 34
			$i++;
274 34
		}
275 34
		return $fqn;
276
	}
277
278 34
	private function getString($tokens, &$i, $max)
279
	{
280
		do
281
		{
282
			/**
283
			 * TODO Workaround for problem desribed near T_CLASS token
284
			 */
285 34
			if (!isset($tokens[$i]))
286 34
			{
287
				$i++;
288
				continue;
289
			}
290 34
			$token = $tokens[$i];
291 34
			$i++;
292 34
			if (is_array($token))
293 34
			{
294 34
				if ($token[0] == T_STRING)
295 34
				{
296 34
					return $token[1];
297
				}
298 34
			}
299
		}
300 34
		while ($i <= $max);
301
		return false;
302
	}
303
304 34
	private function getTokens($file)
305
	{
306 34
		return token_get_all(file_get_contents($file));
307
	}
308
309
}
310