Completed
Push — prado-3.3 ( e90646...0b76d5 )
by Fabio
23:37 queued 03:01
created

ClassTreeBuilder   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 231
rs 3.44
c 0
b 0
f 0
wmc 62
lcom 1
cbo 5

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A buildTree() 0 14 4
A saveToFile() 0 4 1
A displayTree() 0 4 1
A displayTreeInternal() 0 9 2
A parseFile() 0 33 5
B parseMethodComments() 0 41 9
A isValidPath() 0 7 4
B getSourceFiles() 0 20 6
C saveAsDWExtension() 0 44 14
A processObjectType() 0 12 6
B checkType() 0 12 7

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
$basePath=dirname(__FILE__);
4
$frameworkPath=realpath($basePath.'/../../framework');
5
require_once($frameworkPath.'/prado.php');
6
require_once($basePath.'/DWExtension.php');
7
8
//the manager class sets up some dependency paths
9
Prado::using('System.Data.SqlMap.TSqlMapManager');
10
11
$exclusions=array(
12
	'pradolite.php',
13
	'prado-cli.php',
14
	'JSMin.php',
15
	'/I18N/core',
16
	'/3rdParty',
17
	'/Testing',
18
	'/Web/UI/WebControls/assets',
19
	);
20
$a=new ClassTreeBuilder($frameworkPath,$exclusions);
21
$a->buildTree();
22
$a->saveToFile($basePath.'/classes.data');
23
$a->saveAsDWExtension($basePath);
24
25
class ClassTreeBuilder
26
{
27
	const REGEX_RULES='/^\s*(abstract\s+)?class\s+(\w+)(\s+extends\s+(\w+)\s*|\s*)/msS';
28
	private $_frameworkPath;
29
	private $_exclusions;
30
	private $_classes=array();
31
32
	public function __construct($frameworkPath,$exclusions)
33
	{
34
		$this->_frameworkPath=realpath($frameworkPath);
35
		$this->_exclusions=array();
36
		foreach($exclusions as $exclusion)
37
		{
38
			if($exclusion[0]==='/')
39
				$this->_exclusions[realpath($frameworkPath.'/'.$exclusion)]=true;
40
			else
41
				$this->_exclusions[$exclusion]=true;
42
		}
43
	}
44
45
	public function buildTree()
46
	{
47
		$sourceFiles=$this->getSourceFiles($this->_frameworkPath);
48
		foreach($sourceFiles as $sourceFile)
49
			$this->parseFile($sourceFile);
50
		ksort($this->_classes);
51
		foreach(array_keys($this->_classes) as $className)
52
		{
53
			$parentClass=$this->_classes[$className]['ParentClass'];
54
			if(isset($this->_classes[$parentClass]))
55
				$this->_classes[$parentClass]['ChildClasses'][]=$className;
56
		}
57
		echo "\nClass tree built successfully. Total ".count($this->_classes)." classes found.\n";
58
	}
59
60
	public function saveToFile($fileName)
61
	{
62
		file_put_contents($fileName,serialize($this->_classes));
63
	}
64
65
	public function displayTree()
66
	{
67
		$this->displayTreeInternal(array_keys($this->_baseClasses),0);
0 ignored issues
show
Bug introduced by
The property _baseClasses does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
68
	}
69
70
	public function displayTreeInternal($classNames,$level)
71
	{
72
		foreach($classNames as $className)
73
		{
74
			echo str_repeat(' ',$level*4);
75
			echo $className.':'.$this->_classes[$className]->Package."\n";
76
			$this->displayTreeInternal(array_keys($this->_classes[$className]->ChildClasses),$level+1);
77
		}
78
	}
79
80
	protected function parseFile($sourceFile)
81
	{
82
		include_once($sourceFile);
83
		$classFile=strtr(substr($sourceFile,strlen($this->_frameworkPath)),'\\','/');
84
		echo "Parsing $classFile...\n";
85
		$content=file_get_contents($sourceFile);
86
		if(preg_match('/@package\s+([\w\.]+)\s*/msS',$content,$matches)>0)
87
			$package=$matches[1];
88
		else
89
			$package='';
90
		$n=preg_match_all(self::REGEX_RULES,$content,$matches,PREG_SET_ORDER);
91
		for($i=0;$i<$n;++$i)
92
		{
93
			$className=$matches[$i][2];
94
			if(isset($this->_classes[$className]))
95
				throw new Exception("Class $className is defined in both $sourceFile and ".$this->_classes[$className]->ClassFile);
96
			$c=new TComponentReflection($className);
97
			$properties=$c->getProperties();
98
			$this->parseMethodComments($properties);
99
			$events=$c->getEvents();
100
			$this->parseMethodComments($events);
101
			$methods=$c->getMethods();
102
			$this->parseMethodComments($methods);
103
			$this->_classes[$className]=array(
104
				'ClassFile'=>$classFile,
105
				'Package'=>$package,
106
				'ParentClass'=>isset($matches[$i][4])?$matches[$i][4]:'',
107
				'ChildClasses'=>array(),
108
				'Properties'=>$properties,
109
				'Events'=>$events,
110
				'Methods'=>$methods);
111
		}
112
	}
113
114
	protected function parseMethodComments(&$methods)
115
	{
116
		foreach(array_keys($methods) as $key)
117
		{
118
			$method=&$methods[$key];
119
			$comments=$method['comments'];
120
			$s='';
121
			foreach(explode("\n",$comments) as $line)
122
			{
123
				$line=trim($line);
124
				$line=trim($line,'/*');
125
				$s.=' '.$line;
126
			}
127
			$s=trim($s);
128
			$s=preg_replace('/\{@link.*?([\w\(\)]+)\}/i','$1',$s);
129
			$pos1=strpos($s,'@');
130
			$pos2=strpos($s,'.');
131
			if($pos1===false)
132
			{
133
				if($pos2!==false)
134
					$method['comments']=substr($s,0,$pos2);
135
				else
136
					$method['comments']=$s;
137
			}
138
			else if($pos1>0)
139
			{
140
				if($pos2 && $pos2<$pos1)	// use the first line as comment
141
					$method['comments']=substr($s,0,$pos2);
142
				else
143
					$method['comments']=substr($s,0,$pos1);
144
			}
145
			else
146
			{
147
				$matches=array();
148
				if(preg_match('/@return\s+[\w\|]+\s+([^\.]*)/',$s,$matches)>0)
149
					$method['comments']=$matches[1];
150
				else
151
					$method['comments']='';
152
			}
153
		}
154
	}
155
156
	protected function isValidPath($path)
157
	{
158
		if(is_dir($path))
159
			return !isset($this->_exclusions[basename($path)]) && !isset($this->_exclusions[$path]);
160
		else
161
			return basename($path)!==basename($path,'.php') && !isset($this->_exclusions[basename($path)]);
162
	}
163
164
	public function getSourceFiles($path)
165
	{
166
		$files=array();
167
		$folder=opendir($path);
168
		while($file=readdir($folder))
169
		{
170
			if($file==='.' || $file==='..')
171
				continue;
172
			$fullPath=realpath($path.'/'.$file);
173
			if($this->isValidPath($fullPath))
174
			{
175
				if(is_file($fullPath))
176
					$files[]=$fullPath;
177
				else
178
					$files=array_merge($files,$this->getSourceFiles($fullPath));
179
			}
180
		}
181
		closedir($folder);
182
		return $files;
183
	}
184
185
	public function saveAsDWExtension($basePath)
186
	{
187
		$tagPath=$basePath.'/Configuration/TagLibraries/PRADO';
188
189
		// prepare the directory to save tag lib
190
		@mkdir($basePath.'/Configuration');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
191
		@mkdir($basePath.'/Configuration/TagLibraries');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
192
		@mkdir($basePath.'/Configuration/TagLibraries/PRADO');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
193
194
		$docMXI = new PradoMXIDocument(Prado::getVersion());
195
		$tagChooser = new PradoTagChooser;
196
197
		$controlClass = new ReflectionClass('TControl');
198
199
		foreach($this->_classes as $className=>$classInfo)
200
		{
201
			$class = new ReflectionClass($className);
202
			if($class->isInstantiable() && ($className==='TControl' || $class->isSubclassOf($controlClass)))
203
			{
204
				$docMXI->addTag($className);
205
				$tagChooser->addElement($className);
206
				$docVTM = new PradoVTMDocument($className);
207
				foreach($classInfo['Properties'] as $name=>$property)
208
				{
209
					$type=$property['type'];
210
					if(isset($this->_classes[$type]) && ($type==='TFont' || strrpos($type,'Style')===strlen($type)-5 && $type!=='TStyle'))
211
						$this->processObjectType($type,$this->_classes[$type],$name,$docVTM);
212
					if($property['readonly'] || $property['protected'])
213
						continue;
214
					if(($type=$this->checkType($className,$name,$property['type']))!=='')
215
						$docVTM->addAttribute($name,$type);
216
				}
217
				foreach($classInfo['Events'] as $name=>$event)
218
				{
219
					$docVTM->addEvent($name);
220
				}
221
				file_put_contents($tagPath.'/'.$className.'.vtm',$docVTM->getXML());
222
			}
223
		}
224
225
		file_put_contents($basePath.'/PRADO.mxi',$docMXI->getXML());
226
		file_put_contents($tagPath.'/TagChooser.xml',$tagChooser->getXML());
227
228
	}
229
230
	private	function processObjectType($objectType,$objectInfo,$prefix,$doc)
231
	{
232
		foreach($objectInfo['Properties'] as $name=>$property)
233
		{
234
			if($property['type']==='TFont')
235
				$this->processObjectType('TFont',$this->_classes['TFont'],$prefix.'.'.$name,$doc);
236
			if($property['readonly'] || $property['protected'])
237
				continue;
238
			if(($type=$this->checkType($objectType,$name,$property['type']))!=='')
239
				$doc->addAttribute($prefix.'.'.$name,$type);
240
		}
241
	}
242
243
	private	function checkType($className,$propertyName,$type)
244
	{
245
		if(strrpos($propertyName,'Color')===strlen($propertyName)-5)
246
			return 'color';
247
		if($propertyName==='Style')
248
			return 'style';
249
		if($type==='boolean')
250
			return array('true','false');
251
		if($type==='string' || $type==='integer' || $type==='ITemplate')
252
			return 'text';
253
		return '';
254
	}
255
}
256