Context   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 315
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 92.62%

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 10
dl 0
loc 315
ccs 138
cts 149
cp 0.9262
rs 5.5199
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A setTracker() 0 3 1
A addListener() 0 3 1
A removeListener() 0 3 1
A visitToken() 0 10 1
A isBlockContextDetected() 0 3 1
A inStructBody() 0 3 1
A inRoutineBody() 0 3 1
A isFunctionInvocation() 0 4 2
A resetLineContext() 0 3 1
A getLineContext() 0 3 1
A getCurrentContext() 0 7 2
A getBlockContext() 0 3 1
A getGroupContext() 0 3 1
A peekBlock() 0 6 2
A peekGroup() 0 6 2
A reset() 0 14 3
B detectBlockContext() 0 15 7
B enterBlockContext() 0 36 7
A findBlockStart() 0 11 2
B leaveBlockContext() 0 32 8
B detectGroupContext() 0 31 7
A detectLineContext() 0 5 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
namespace gossi\formatter\parser;
3
4
use gossi\formatter\entities\Block;
5
use gossi\formatter\entities\Group;
6
use gossi\formatter\events\BlockEvent;
7
use gossi\formatter\events\GroupEvent;
8
use phootwork\collection\Stack;
9
use phootwork\tokenizer\Token;
10
use phootwork\tokenizer\TokenVisitorInterface;
11
use Symfony\Component\EventDispatcher\EventDispatcher;
12
13
class Context implements TokenVisitorInterface {
14
15
	// context constants
16
	const CONTEXT_FILE = 'file';
17
	const CONTEXT_STRUCT = 'struct';
18
	const CONTEXT_ROUTINE = 'routine';
19
	const CONTEXT_BLOCK = 'block';
20
	const CONTEXT_GROUP = 'group';
21
22
	// event constants
23
	const EVENT_BLOCK_ENTER = 'context.block_enter';
24
	const EVENT_BLOCK_LEAVE = 'context.block_leave';
25
	const EVENT_STRUCT_ENTER = 'context.struct_enter';
26
	const EVENT_STRUCT_LEAVE = 'context.struct_leave';
27
	const EVENT_ROUTINE_ENTER = 'context.routine_enter';
28
	const EVENT_ROUTINE_LEAVE = 'context.routine_leave';
29
	const EVENT_GROUP_ENTER = 'context.group_enter';
30
	const EVENT_GROUP_LEAVE = 'context.group_leave';
31
32
	// current contexts
33
	private $block;
34
	private $group;
35
	private $inStructBody = false;
36
	private $inRoutineBody = false;
37
38
	// stacks
39
	private $blockStack;
40
	private $groupStack;
41
	private $contextStack;
42
	private $line;
43
44
	// helpers
45
	private $blockDetected;
46
	private $tracker;
47
	private $dispatcher;
48
	private $parser;
49
	private $matcher;
50
	private $events = [
51
		self::EVENT_BLOCK_ENTER, self::EVENT_BLOCK_LEAVE,
52
		self::EVENT_GROUP_ENTER, self::EVENT_GROUP_LEAVE,
53
		self::EVENT_ROUTINE_ENTER, self::EVENT_ROUTINE_LEAVE,
54
		self::EVENT_STRUCT_ENTER, self::EVENT_STRUCT_LEAVE
55
	];
56
57
	private static $PROPERTIES = [T_PRIVATE, T_PUBLIC, T_PROTECTED, T_STATIC, T_VAR];
58
59 10
	public function __construct(Parser $parser) {
60 10
		$this->parser = $parser;
61 10
		$this->matcher = $parser->getMatcher();
62 10
		$this->blockStack = new Stack();
63 10
		$this->groupStack = new Stack();
64 10
		$this->contextStack = new Stack();
65 10
		$this->dispatcher = new EventDispatcher();
66 10
	}
67
68 10
	public function reset() {
69
		// remove listeners
70 10
		foreach ($this->events as $event) {
71 10
			$listeners = $this->dispatcher->getListeners($event);
72 10
			foreach ($listeners as $listener) {
73 10
				$this->dispatcher->removeListener($event, $listener);
74
			}
75
		}
76
77
		// reset data objects
78 10
		$this->blockStack->clear();
79 10
		$this->groupStack->clear();
80 10
		$this->contextStack->clear();
81 10
	}
82
83 10
	public function setTracker(TokenTracker $tracker) {
84 10
		$this->tracker = $tracker;
85 10
	}
86
87 10
	public function addListener($name, $listener) {
88 10
		$this->dispatcher->addListener($name, $listener);
89 10
	}
90
91
	public function removeListener($name, $listener) {
92
		$this->dispatcher->removeListener($name, $listener);
93
	}
94
95 10
	public function visitToken(Token $token) {
96
		// load current contexts
97 10
		$this->block = $this->peekBlock();
98 10
		$this->group = $this->peekGroup();
99
100
		// detect new contexts
101 10
		$this->detectBlockContext($token);
102 10
		$this->detectGroupContext($token);
103 10
		$this->detectLineContext($token);
104 10
	}
105
106 10
	private function detectBlockContext(Token $token) {
107 10
		if ($this->matcher->isBlock($token)) {
108 10
			$this->blockDetected = $token;
109
		}
110
111 10
		$this->enterBlockContext($token);
112 10
		$this->leaveBlockContext($token);
113
114
		// neglect block detection
115 10
		if ($this->blockDetected !== null && $token->contents == ';'
116 10
				&& ($this->group !== null ? !($this->group->type == Group::BLOCK
117 10
					|| $this->group->type == Group::GROUP) : true)) {
118 10
			$this->blockDetected = null;
119
		}
120 10
	}
121
122 10
	private function enterBlockContext(Token $token) {
123 10
		if ($token->contents == '{' && $this->blockDetected !== null) {
124 10
			$type = Block::getType($this->blockDetected);
125 10
			if ($type == Block::TYPE_FUNCTION && $this->getCurrentContext() == self::CONTEXT_STRUCT) {
126 5
				$type = Block::TYPE_METHOD;
127
			}
128 10
			$block = new Block($type);
129 10
			$block->start = $this->findBlockStart($this->blockDetected);
130 10
			$block->open = $token;
131 10
			$this->blockStack->push($block);
132 10
			$this->block = $block;
133 10
			$this->blockDetected = null;
134
135 10
			$event = new BlockEvent($token, $this->block);
136 10
			$this->dispatcher->dispatch(self::EVENT_BLOCK_ENTER, $event);
137
138
			// enter struct context
139 10
			if ($block->isStruct()) {
140 5
				$this->inStructBody = true;
141 5
				$this->dispatcher->dispatch(self::EVENT_STRUCT_ENTER, $event);
142 5
				$this->contextStack->push(self::CONTEXT_STRUCT);
143
			}
144
145
			// enter routine context
146 10
			else if ($block->isRoutine()) {
147 5
				$this->inRoutineBody = true;
148 5
				$this->dispatcher->dispatch(self::EVENT_ROUTINE_ENTER, $event);
149 5
				$this->contextStack->push(self::CONTEXT_ROUTINE);
150
			}
151
152
			// enter block context
153
			else {
154 8
				$this->contextStack->push(self::CONTEXT_BLOCK);
155
			}
156
		}
157 10
	}
158
159 10
	private function findBlockStart(Token $token) {
160 10
		$startToken = $token;
161 10
		$prevToken = $this->tracker->prevToken($token);
162
163 10
		while ($this->matcher->isModifier($prevToken)) {
164 5
			$startToken = $prevToken;
165 5
			$prevToken = $this->tracker->prevToken($prevToken);
166
		}
167
168 10
		return $startToken;
169
	}
170
171 10
	private function leaveBlockContext(Token $token) {
172 10
		if ($token->contents == '}') {
173 10
			$this->block = $this->blockStack->pop();
174
175
			// find block end
176 10
			if ($this->block->type == Block::TYPE_DO) {
177 5
				$nextToken = $token;
178
				do {
179 5
					$nextToken = $this->tracker->nextToken($nextToken);
180 5
				} while ($nextToken->contents != ';');
181 5
				$this->block->end = $nextToken;
182
			} else {
183 10
				$this->block->end = $token;
184
			}
185
186 10
			$event = new BlockEvent($token, $this->block);
187 10
			$this->dispatcher->dispatch(self::EVENT_BLOCK_LEAVE, $event);
188 10
			$this->contextStack->pop();
189
190
			// leave struct context
191 10
			if ($this->inStructBody && $this->block->isStruct()) {
192 5
				$this->inStructBody = false;
193 5
				$this->dispatcher->dispatch(self::EVENT_STRUCT_LEAVE, $event);
194
			}
195
196
			// leave routine context
197 10
			else if ($this->inRoutineBody && $this->block->isRoutine()) {
198 5
				$this->inRoutineBody = false;
199 5
				$this->dispatcher->dispatch(self::EVENT_ROUTINE_LEAVE, $event);
200
			}
201
		}
202 10
	}
203
204
	public function isBlockContextDetected() {
205
		return $this->blockDetected !== null;
206
	}
207
208
	/**
209
	 * Tells you, whenever being in a struct, this is also true when inside 
210
	 * a method or inside a function, which is inside a method
211
	 * 
212
	 * @return bool
213
	 */
214
	public function inStructBody() {
215
		return $this->inStructBody;
216
	}
217
218
	/**
219
	 * Tells you, whenever being in a function or method
220
	 * 
221
	 * @return bool
222
	 */
223
	public function inRoutineBody() {
224
		return $this->inRoutineBody;
225
	}
226
227 10
	private function detectGroupContext(Token $token) {
228 10
		$prevToken = $this->tracker->getPrevToken();
229 10
		if ($token->contents == '(') {
230 10
			$group = new Group();
231 10
			$group->start = $token;
232
// 			if (in_array($prevToken->type, Tokenizer::$BLOCKS)
233
// 					|| in_array($prevToken->type, Tokenizer::$OPERATORS)) {
234 10
			if (($this->matcher->isBlock($prevToken) || $this->matcher->isOperator($prevToken))
235 10
					&& !$this->group->isBlock()) {
236 8
				$group->type = Group::BLOCK;
237 8
				$group->token = $prevToken;
238 10
			} else if ($this->isFunctionInvocation($token)) {
239 10
				$group->type = Group::CALL;
240 10
				$group->token = $prevToken;
241
			} else {
242 5
				$group->type = Group::GROUP;
243
			}
244
245 10
			$this->groupStack->push($group);
246 10
			$this->group = $group;
247
248 10
			$event = new GroupEvent($token, $group);
249 10
			$this->dispatcher->dispatch(self::EVENT_GROUP_ENTER, $event);
250 10
		} else if ($token->contents == ')') {
251 10
			$this->group = $this->groupStack->pop();
252 10
			$this->group->end = $token;
253
254 10
			$event = new GroupEvent($token, $this->group);
255 10
			$this->dispatcher->dispatch(self::EVENT_GROUP_LEAVE, $event);
256
		}
257 10
	}
258
259 10
	private function isFunctionInvocation($token) {
260 10
		$prevToken = $this->tracker->getPrevToken();
261 10
		return $token->contents == '(' && $prevToken->type == T_STRING;
262
	}
263
264 10
	private function detectLineContext(Token $token) {
265 10
		if ($this->matcher->isLineContext($token)) {
266 2
			$this->line = $token->contents;
267
		}
268 10
	}
269
270 2
	public function resetLineContext() {
271 2
		$this->line = null;
272 2
	}
273
274
	/**
275
	 * Returns the line context or null if not present
276
	 * 
277
	 * @return string|null
278
	 */
279 2
	public function getLineContext() {
280 2
		return $this->line;
281
	}
282
283
	/**
284
	 * Returns the current context. Context is one of the Context::CONTEXT_* constants.
285
	 * 
286
	 * @return string
287
	 */
288 5
	public function getCurrentContext() {
289 5
		if ($this->contextStack->size() > 0) {
290 5
			return $this->contextStack->peek();
291
		}
292
293 3
		return self::CONTEXT_FILE;
294
	}
295
296
	/**
297
	 * Returns the current block context
298
	 * 
299
	 * @return Block
300
	 */
301
	public function getBlockContext() {
302
		return $this->block;
303
	}
304
305
	/**
306
	 * Returns the current group context
307
	 * 
308
	 * @return Group|null
309
	 */
310 2
	public function getGroupContext() {
311 2
		return $this->group;
312
	}
313
314 10
	private function peekBlock() {
315 10
		if ($this->blockStack->size() > 0) {
316 10
			return $this->blockStack->peek();
317
		}
318 10
		return new Block(null);
319
	}
320
321 10
	private function peekGroup() {
322 10
		if ($this->groupStack->size() > 0) {
323 10
			return $this->groupStack->peek();
324
		}
325 10
		return new Group();
326
	}
327
}
328