Completed
Push — master ( 75b133...6283a1 )
by Martijn
02:31
created

Parser::tokenToStatements()   C

Complexity

Conditions 13
Paths 54

Size

Total Lines 52
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 35
nc 54
nop 1
dl 0
loc 52
rs 5.9687
c 0
b 0
f 0

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
namespace SwaggerGen\Parser\Php;
4
5
/**
6
 * Parses comments in PHP into a structure of functions, classes and methods,
7
 * resolving inheritance, references and namespaces.
8
 *
9
 * @package    SwaggerGen
10
 * @author     Martijn van der Lee <[email protected]>
11
 * @copyright  2014-2015 Martijn van der Lee
12
 * @license    https://opensource.org/licenses/MIT MIT
13
 */
14
class Parser extends Entity\AbstractEntity implements \SwaggerGen\Parser\IParser
15
{
16
17
	const COMMENT_TAG = 'rest';
18
19
// transient
20
21
	private $current_file = null;
22
	private $files_queued = array();
23
	private $files_done = array();
24
	private $dirs = array();
25
// States
26
27
	public $Statements = array();
28
	
29
	/**
30
	 * @var \SwaggerGen\Statement[]|null
31
	 */
32
	private $lastStatements = array();
33
34
	/**
35
	 * @var Entity\ParserClass[]
36
	 */
37
	public $Classes = array();
38
39
	/**
40
	 * @var Entity\ParserFunction[]
41
	 */
42
	public $Functions = array();
43
44
	/**
45
	 * @var \SwaggerGen\Parser\AbstractPreprocessor
46
	 */
47
	private $Preprocessor;
48
49
	/**
50
	 * Directories available to all parse calls
51
	 *
52
	 * @var string[]
53
	 */
54
	protected $common_dirs = array();
55
56
	public function __construct(Array $dirs = array())
57
	{
58
		foreach ($dirs as $dir) {
59
			$this->common_dirs[] = realpath($dir);
60
		}
61
62
		$this->Preprocessor = new Preprocessor(self::COMMENT_TAG);
63
	}
64
65
	public function addDirs(Array $dirs)
66
	{
67
		foreach ($dirs as $dir) {
68
			$this->common_dirs[] = realpath($dir);
69
		}
70
	}
71
	
72
	private function extractStatements() {
73
		// Core comments
74
		$Statements = $this->Statements;
75
76
		// Functions
77
		foreach ($this->Functions as $Function) {
78
			if ($Function->hasCommand('method')) {
79
				$Statements = array_merge($Statements, $Function->Statements);
80
			}
81
		}
82
83
		// Classes
84
		foreach ($this->Classes as $Class) {
85
			$Statements = array_merge($Statements, $Class->Statements);
86
			foreach ($Class->Methods as $Method) {
87
				if ($Method->hasCommand('method')) {
88
					$Statements = array_merge($Statements, $Method->Statements);
89
				}
90
			}
91
		}
92
93
		return $Statements;
94
	}
95
96
	public function parse($file, Array $dirs = array(), Array $defines = array())
97
	{
98
		$this->dirs = $this->common_dirs;
99
		foreach ($dirs as $dir) {
100
			$this->dirs[] = realpath($dir);
101
		}
102
103
		$this->parseFiles(array($file), $defines);
104
105
		// Inherit classes
106
		foreach ($this->Classes as $Class) {
107
			$this->inherit($Class);
108
		}
109
110
		// Expand functions with used and seen functions/methods.
111
		foreach ($this->Classes as $Class) {
112
			foreach ($Class->Methods as $Method) {
113
				$Method->Statements = $this->expand($Method->Statements, $Class);
114
			}
115
		}
116
117
		return $this->extractStatements();
118
	}
119
120
	/**
121
	 * Convert a T_*_COMMENT string to an array of Statements
122
	 * @param array $token
123
	 * @return \SwaggerGen\Statement[]
124
	 */
125
	public function tokenToStatements($token)
126
	{
127
		$comment = $token[1];
128
		$commentLineNumber = $token[2];
129
		$commentLines = array();
130
131
		$match = array();
132
		if (preg_match('~^/\*\*?\s*(.*)\s*\*\/$~sm', $comment, $match) === 1) {
133
			$lines = preg_split('~\n~', $match[0]);
134
			foreach ($lines as $line) {
135
				if (preg_match('~^\s*\*?\s*(.*?)\s*$~', $line, $match) === 1) {
136
					if (!empty($match[1])) {
137
						$commentLines[] = trim($match[1]);
138
					}
139
				}
140
			}
141
		} elseif (preg_match('~^//\s*(.*)$~', $comment, $match) === 1) {
142
			$commentLines[] = trim($match[1]);
143
		}
144
		// to commands
145
		$match = array();
146
		$command = null;
147
		$data = '';
148
		$commandLineNumber = 0;
149
		$Statements = array();
150
		foreach ($commentLines as $lineNumber => $line) {
151
			// If new @-command, store any old and start new
152
			if ($command !== null && chr(ord($line)) === '@') {
153
				$Statements[] = new Statement($command, $data, $this->current_file, $commentLineNumber + $commandLineNumber);
154
				$command = null;
155
				$data = '';
156
			}
157
158
			if (preg_match('~^@' . preg_quote(self::COMMENT_TAG) . '\\\\([a-z][-a-z]*\\??)\\s*(.*)$~', $line, $match) === 1) {
159
				$command = $match[1];
160
				$data = $match[2];
161
				$commandLineNumber = $lineNumber;
162
			} elseif ($command !== null) {
163
				if ($lineNumber < count($commentLines) - 1) {
164
					$data.= ' ' . $line;
165
				} else {
166
					$data.= preg_replace('~\s*\**\/\s*$~', '', $line);
167
				}
168
			}
169
		}
170
171
		if ($command !== null) {
172
			$Statements[] = new Statement($command, $data, $this->current_file, $commentLineNumber + $commandLineNumber);
173
		}
174
175
		return $Statements;
176
	}
177
178
	public function queueClass($classname)
179
	{
180
		foreach ($this->dirs as $dir) {
181
			$paths = array(
182
				$dir . DIRECTORY_SEPARATOR . $classname . '.php',
183
				$dir . DIRECTORY_SEPARATOR . $classname . '.class.php',
184
			);
185
186
			foreach ($paths as $path) {
187
				$realpath = realpath($path);
188
				if (in_array($realpath, $this->files_done)) {
189
					return;
190
				} elseif (is_file($realpath)) {
191
					$this->files_queued[] = $realpath;
192
					return;
193
				}
194
			}
195
		}
196
197
		// assume it's a class;
198
	}
199
200
	/**
201
	 * Add to the queue any classes based on the commands.
202
	 * @param \SwaggerGen\Statement[] $Statements
203
	 */
204
	public function queueClassesFromComments(Array $Statements)
205
	{
206
		foreach ($Statements as $Statement) {
207
			if ($Statement->command === 'uses' || $Statement->command === 'see') {
208
				$match = array();
209
				if (preg_match('~^(\w+)(::|->)?(\w+)?(?:\(\))?$~', $Statement->data, $match) === 1) {
210
					if (!in_array($match[1], array('self', '$this'))) {
211
						$this->queueClass($match[1]);
212
					}
213
				}
214
			}
215
		}
216
	}
217
218
	private function parseTokens($source) {
219
		$mode = null;
220
		$namespace = '';
221
222
		$tokens = token_get_all($source);
223
		$token = reset($tokens);
224
		while ($token) {
225
			switch ($token[0]) {
226
				case T_NAMESPACE:
227
					$mode = T_NAMESPACE;
228
					break;
229
230
				case T_NS_SEPARATOR:
231
				case T_STRING:
232
					if ($mode === T_NAMESPACE) {
233
						$namespace .= $token[1];
234
					}
235
					break;
236
237
				case ';':
238
					$mode = null;
239
					break;
240
241
				case T_CLASS:
242
				case T_INTERFACE:
243
					$Class = new Entity\ParserClass($this, $tokens, $this->lastStatements);
244
					$this->Classes[strtolower($Class->name)] = $Class;
245
					$this->lastStatements = null;
246
					break;
247
248 View Code Duplication
				case T_FUNCTION:
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...
249
					$Function = new Entity\ParserFunction($this, $tokens, $this->lastStatements);
250
					$this->Functions[strtolower($Function->name)] = $Function;
251
					$this->lastStatements = null;
252
					break;
253
254
				case T_COMMENT:
255
					if ($this->lastStatements !== null) {
256
						$this->Statements = array_merge($this->Statements, $this->lastStatements);
257
						$this->lastStatements = null;
258
					}
259
					$Statements = $this->tokenToStatements($token);
260
					$this->queueClassesFromComments($Statements);
261
					$this->Statements = array_merge($this->Statements, $Statements);
262
					break;
263
264
				case T_DOC_COMMENT:
265
					if ($this->lastStatements !== null) {
266
						$this->Statements = array_merge($this->Statements, $this->lastStatements);
267
					}
268
					$Statements = $this->tokenToStatements($token);
269
					$this->queueClassesFromComments($Statements);
270
					$this->lastStatements = $Statements;
271
					break;
272
			}
273
274
			$token = next($tokens);
275
		}
276
	}
277
	
278
	private function parseFiles(Array $files, Array $defines = array())
279
	{
280
		$this->files_queued = $files;
281
282
		$index = 0;
283
		while (($file = array_shift($this->files_queued)) !== null) {
284
			$file = realpath($file);
285
286
			// @todo Test if this works
287
			if (in_array($file, $this->files_done)) {
288
				continue;
289
			}
290
291
			$this->current_file = $file;
292
			$this->files_done[] = $file;
293
			++$index;
294
295
			$this->Preprocessor->resetDefines();
296
			$this->Preprocessor->addDefines($defines);
297
			$source = $this->Preprocessor->preprocessFile($file);
298
299
			$this->parseTokens($source);
300
301
			if ($this->lastStatements !== null) {
302
				$this->Statements = array_merge($this->Statements, $this->lastStatements);
303
				$this->lastStatements = null;
304
			}
305
		}
306
307
		$this->current_file = null;
308
	}
309
310
	/**
311
	 * Inherit the statements
312
	 * @param \SwaggerGen\Parser\Php\Entity\ParserClass $Class
313
	 */
314
	private function inherit(Entity\ParserClass $Class)
315
	{
316
		$inherits = array_merge(array($Class->extends), $Class->implements);
317
		while (($inherit = array_shift($inherits)) !== null) {
318
			if (isset($this->Classes[strtolower($inherit)])) {
319
				$inheritedClass = $this->Classes[strtolower($inherit)];
320
				$this->inherit($inheritedClass);
321
322
				foreach ($inheritedClass->Methods as $name => $Method) {
323
					if (!isset($Class->Methods[$name])) {
324
						$Class->Methods[$name] = $Method;
325
					}
326
				}
327
			}
328
		}
329
	}
330
331
	/**
332
	 * Expands a set of comments with comments of methods referred to by
333
	 * rest\uses statements.
334
	 * @param \SwaggerGen\Statement[] $Statements
335
	 * @return \SwaggerGen\Statement[]
336
	 */
337
	private function expand(Array $Statements, Entity\ParserClass $Self = null)
338
	{
339
		$output = array();
340
341
		$match = null;
342
		foreach ($Statements as $Statement) {
343
			if ($Statement->command === 'uses' || $Statement->command === 'see') { //@todo either one, not both?
344
				if (preg_match('/^((?:\\w+)|\$this)(?:(::|->)(\\w+))?(?:\\(\\))?$/', strtolower($Statement->data), $match) === 1) {
345
					if (count($match) >= 3) {
346
						$Class = null;
347
						if (in_array($match[1], array('$this', 'self', 'static'))) {
348
							$Class = $Self;
349
						} elseif (isset($this->Classes[$match[1]])) {
350
							$Class = $this->Classes[$match[1]];
351
						}
352
353
						if ($Class) {
354
							if (isset($Class->Methods[$match[3]])) {
355
								$Method = $Class->Methods[$match[3]];
356
								$Method->Statements = $this->expand($Method->Statements, $Class);
357
								$output = array_merge($output, $Method->Statements);
358
							} else {
359
								throw new \SwaggerGen\Exception("Method '{$match[3]}' for class '{$match[1]}' not found");
360
							}
361
						} else {
362
							throw new \SwaggerGen\Exception("Class '{$match[1]}' not found");
363
						}
364
					} elseif (isset($this->Functions[$match[1]])) {
365
						$Function = $this->Functions[$match[1]];
366
						$Function->Statements = $this->expand($Function->Statements, null);
367
						$output = array_merge($output, $Function->Statements);
368
					} else {
369
						throw new \SwaggerGen\Exception("Function '{$match[1]}' not found");
370
					}
371
				}
372
			} else {
373
				$output[] = $Statement;
374
			}
375
		}
376
377
		return $output;
378
	}
379
380
}
381