1<?php 2/* 3 * This file is part of the php-code-coverage package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace SebastianBergmann\CodeCoverage\Report\Html; 12 13use SebastianBergmann\CodeCoverage\Node\AbstractNode; 14use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; 15 16/** 17 * Renders the dashboard for a directory node. 18 */ 19class Dashboard extends Renderer 20{ 21 /** 22 * @param DirectoryNode $node 23 * @param string $file 24 */ 25 public function render(DirectoryNode $node, $file) 26 { 27 $classes = $node->getClassesAndTraits(); 28 $template = new \Text_Template( 29 $this->templatePath . 'dashboard.html', 30 '{{', 31 '}}' 32 ); 33 34 $this->setCommonTemplateVariables($template, $node); 35 36 $baseLink = $node->getId() . '/'; 37 $complexity = $this->complexity($classes, $baseLink); 38 $coverageDistribution = $this->coverageDistribution($classes); 39 $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink); 40 $projectRisks = $this->projectRisks($classes, $baseLink); 41 42 $template->setVar( 43 [ 44 'insufficient_coverage_classes' => $insufficientCoverage['class'], 45 'insufficient_coverage_methods' => $insufficientCoverage['method'], 46 'project_risks_classes' => $projectRisks['class'], 47 'project_risks_methods' => $projectRisks['method'], 48 'complexity_class' => $complexity['class'], 49 'complexity_method' => $complexity['method'], 50 'class_coverage_distribution' => $coverageDistribution['class'], 51 'method_coverage_distribution' => $coverageDistribution['method'] 52 ] 53 ); 54 55 $template->renderTo($file); 56 } 57 58 /** 59 * Returns the data for the Class/Method Complexity charts. 60 * 61 * @param array $classes 62 * @param string $baseLink 63 * 64 * @return array 65 */ 66 protected function complexity(array $classes, $baseLink) 67 { 68 $result = ['class' => [], 'method' => []]; 69 70 foreach ($classes as $className => $class) { 71 foreach ($class['methods'] as $methodName => $method) { 72 if ($className != '*') { 73 $methodName = $className . '::' . $methodName; 74 } 75 76 $result['method'][] = [ 77 $method['coverage'], 78 $method['ccn'], 79 sprintf( 80 '<a href="%s">%s</a>', 81 str_replace($baseLink, '', $method['link']), 82 $methodName 83 ) 84 ]; 85 } 86 87 $result['class'][] = [ 88 $class['coverage'], 89 $class['ccn'], 90 sprintf( 91 '<a href="%s">%s</a>', 92 str_replace($baseLink, '', $class['link']), 93 $className 94 ) 95 ]; 96 } 97 98 return [ 99 'class' => json_encode($result['class']), 100 'method' => json_encode($result['method']) 101 ]; 102 } 103 104 /** 105 * Returns the data for the Class / Method Coverage Distribution chart. 106 * 107 * @param array $classes 108 * 109 * @return array 110 */ 111 protected function coverageDistribution(array $classes) 112 { 113 $result = [ 114 'class' => [ 115 '0%' => 0, 116 '0-10%' => 0, 117 '10-20%' => 0, 118 '20-30%' => 0, 119 '30-40%' => 0, 120 '40-50%' => 0, 121 '50-60%' => 0, 122 '60-70%' => 0, 123 '70-80%' => 0, 124 '80-90%' => 0, 125 '90-100%' => 0, 126 '100%' => 0 127 ], 128 'method' => [ 129 '0%' => 0, 130 '0-10%' => 0, 131 '10-20%' => 0, 132 '20-30%' => 0, 133 '30-40%' => 0, 134 '40-50%' => 0, 135 '50-60%' => 0, 136 '60-70%' => 0, 137 '70-80%' => 0, 138 '80-90%' => 0, 139 '90-100%' => 0, 140 '100%' => 0 141 ] 142 ]; 143 144 foreach ($classes as $class) { 145 foreach ($class['methods'] as $methodName => $method) { 146 if ($method['coverage'] == 0) { 147 $result['method']['0%']++; 148 } elseif ($method['coverage'] == 100) { 149 $result['method']['100%']++; 150 } else { 151 $key = floor($method['coverage'] / 10) * 10; 152 $key = $key . '-' . ($key + 10) . '%'; 153 $result['method'][$key]++; 154 } 155 } 156 157 if ($class['coverage'] == 0) { 158 $result['class']['0%']++; 159 } elseif ($class['coverage'] == 100) { 160 $result['class']['100%']++; 161 } else { 162 $key = floor($class['coverage'] / 10) * 10; 163 $key = $key . '-' . ($key + 10) . '%'; 164 $result['class'][$key]++; 165 } 166 } 167 168 return [ 169 'class' => json_encode(array_values($result['class'])), 170 'method' => json_encode(array_values($result['method'])) 171 ]; 172 } 173 174 /** 175 * Returns the classes / methods with insufficient coverage. 176 * 177 * @param array $classes 178 * @param string $baseLink 179 * 180 * @return array 181 */ 182 protected function insufficientCoverage(array $classes, $baseLink) 183 { 184 $leastTestedClasses = []; 185 $leastTestedMethods = []; 186 $result = ['class' => '', 'method' => '']; 187 188 foreach ($classes as $className => $class) { 189 foreach ($class['methods'] as $methodName => $method) { 190 if ($method['coverage'] < $this->highLowerBound) { 191 if ($className != '*') { 192 $key = $className . '::' . $methodName; 193 } else { 194 $key = $methodName; 195 } 196 197 $leastTestedMethods[$key] = $method['coverage']; 198 } 199 } 200 201 if ($class['coverage'] < $this->highLowerBound) { 202 $leastTestedClasses[$className] = $class['coverage']; 203 } 204 } 205 206 asort($leastTestedClasses); 207 asort($leastTestedMethods); 208 209 foreach ($leastTestedClasses as $className => $coverage) { 210 $result['class'] .= sprintf( 211 ' <tr><td><a href="%s">%s</a></td><td class="text-right">%d%%</td></tr>' . "\n", 212 str_replace($baseLink, '', $classes[$className]['link']), 213 $className, 214 $coverage 215 ); 216 } 217 218 foreach ($leastTestedMethods as $methodName => $coverage) { 219 list($class, $method) = explode('::', $methodName); 220 221 $result['method'] .= sprintf( 222 ' <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d%%</td></tr>' . "\n", 223 str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), 224 $methodName, 225 $method, 226 $coverage 227 ); 228 } 229 230 return $result; 231 } 232 233 /** 234 * Returns the project risks according to the CRAP index. 235 * 236 * @param array $classes 237 * @param string $baseLink 238 * 239 * @return array 240 */ 241 protected function projectRisks(array $classes, $baseLink) 242 { 243 $classRisks = []; 244 $methodRisks = []; 245 $result = ['class' => '', 'method' => '']; 246 247 foreach ($classes as $className => $class) { 248 foreach ($class['methods'] as $methodName => $method) { 249 if ($method['coverage'] < $this->highLowerBound && 250 $method['ccn'] > 1) { 251 if ($className != '*') { 252 $key = $className . '::' . $methodName; 253 } else { 254 $key = $methodName; 255 } 256 257 $methodRisks[$key] = $method['crap']; 258 } 259 } 260 261 if ($class['coverage'] < $this->highLowerBound && 262 $class['ccn'] > count($class['methods'])) { 263 $classRisks[$className] = $class['crap']; 264 } 265 } 266 267 arsort($classRisks); 268 arsort($methodRisks); 269 270 foreach ($classRisks as $className => $crap) { 271 $result['class'] .= sprintf( 272 ' <tr><td><a href="%s">%s</a></td><td class="text-right">%d</td></tr>' . "\n", 273 str_replace($baseLink, '', $classes[$className]['link']), 274 $className, 275 $crap 276 ); 277 } 278 279 foreach ($methodRisks as $methodName => $crap) { 280 list($class, $method) = explode('::', $methodName); 281 282 $result['method'] .= sprintf( 283 ' <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d</td></tr>' . "\n", 284 str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']), 285 $methodName, 286 $method, 287 $crap 288 ); 289 } 290 291 return $result; 292 } 293 294 protected function getActiveBreadcrumb(AbstractNode $node) 295 { 296 return sprintf( 297 ' <li><a href="index.html">%s</a></li>' . "\n" . 298 ' <li class="active">(Dashboard)</li>' . "\n", 299 $node->getName() 300 ); 301 } 302} 303