5 * Copyright (c) 2009-2011, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
38 * @package CodeCoverage
39 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
40 * @copyright 2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
42 * @link http://github.com/sebastianbergmann/php-code-coverage
43 * @since File available since Release 1.0.0
46 require_once 'PHP/CodeCoverage.php';
47 require_once 'PHP/CodeCoverage/Report/HTML/Node.php';
48 require_once 'Text/Template.php';
51 * Generates an HTML report from an PHP_CodeCoverage object.
54 * @package CodeCoverage
55 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
56 * @copyright 2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
57 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
58 * @version Release: 1.0.4
59 * @link http://github.com/sebastianbergmann/php-code-coverage
60 * @since Class available since Release 1.0.0
62 class PHP_CodeCoverage_Report_HTML
67 public static $templatePath;
77 * @param array $options
79 public function __construct(array $options = array())
81 if (!isset($options['title'])) {
82 $options['title'] = '';
85 if (!isset($options['charset'])) {
86 $options['charset'] = 'UTF-8';
89 if (!isset($options['yui'])) {
90 $options['yui'] = TRUE;
93 if (!isset($options['highlight'])) {
94 $options['highlight'] = FALSE;
97 if (!isset($options['lowUpperBound'])) {
98 $options['lowUpperBound'] = 35;
101 if (!isset($options['highLowerBound'])) {
102 $options['highLowerBound'] = 70;
105 if (!isset($options['generator'])) {
106 $options['generator'] = '';
109 $this->options = $options;
111 self::$templatePath = sprintf(
112 '%s%sHTML%sTemplate%s',
122 * @param PHP_CodeCoverage $coverage
123 * @param string $target
125 public function process(PHP_CodeCoverage $coverage, $target)
127 $target = PHP_CodeCoverage_Util::getDirectory($target);
128 $files = $coverage->getSummary();
129 $commonPath = PHP_CodeCoverage_Util::reducePaths($files);
130 $items = PHP_CodeCoverage_Util::buildDirectoryStructure($files);
131 $root = new PHP_CodeCoverage_Report_HTML_Node_Directory(
135 $this->addItems($root, $items);
137 $this->renderDashboard(
138 $root, $target . 'index.dashboard.html', $this->options['title']
141 foreach ($root as $node) {
142 if ($node instanceof PHP_CodeCoverage_Report_HTML_Node_Directory) {
143 $this->renderDashboard(
145 $target . PHP_CodeCoverage_Util::getSafeFilename(
147 ) . '.dashboard.html',
155 $this->options['title'],
156 $this->options['charset'],
157 $this->options['lowUpperBound'],
158 $this->options['highLowerBound'],
159 $this->options['generator']
162 $this->copyFiles($target);
166 * @param PHP_CodeCoverage_Report_HTML_Node_Directory $root
167 * @param string $file
168 * @param string $title
170 protected function renderDashboard(PHP_CodeCoverage_Report_HTML_Node_Directory $root, $file, $title)
172 $classes = $this->classes($root);
173 $template = new Text_Template(
174 PHP_CodeCoverage_Report_HTML::$templatePath . 'dashboard.html'
180 'charset' => $this->options['charset'],
183 $_SERVER['REQUEST_TIME']
185 'version' => '1.0.4',
186 'php_version' => PHP_VERSION,
187 'generator' => $this->options['generator'],
188 'least_tested_methods' => $this->leastTestedMethods($classes),
189 'top_project_risks' => $this->topProjectRisks($classes),
190 'cc_values' => $this->classComplexity($classes),
191 'ccd_values' => $this->classCoverageDistribution($classes),
192 'backlink' => basename(str_replace('.dashboard', '', $file))
196 $template->renderTo($file);
200 * @param PHP_CodeCoverage_Report_HTML_Node_Directory $root
201 * @param array $items
203 protected function addItems(PHP_CodeCoverage_Report_HTML_Node_Directory $root, array $items)
205 foreach ($items as $key => $value) {
206 if (substr($key, -2) == '/f') {
211 $this->options['yui'],
212 $this->options['highlight']
216 catch (RuntimeException $e) {
220 $child = $root->addDirectory($key);
221 $this->addItems($child, $value);
227 * Returns the classes.
229 * @param PHP_CodeCoverage_Report_HTML_Node_Directory $root
232 protected function classes(PHP_CodeCoverage_Report_HTML_Node_Directory $root)
236 foreach ($root as $node) {
237 if ($node instanceof PHP_CodeCoverage_Report_HTML_Node_File) {
238 $classes = array_merge($classes, $node->getClasses());
242 if (isset($classes['*'])) {
243 unset($classes['*']);
250 * Returns the data for the Class Complexity chart.
252 * @param array $classes
255 protected function classComplexity(array $classes)
259 foreach ($classes as $name => $class) {
260 $data[] = array($class['coverage'], $class['ccn'], 'blue', $name);
263 return json_encode($data);
267 * Returns the data for the Class Coverage Distribution chart.
269 * @param array $classes
272 protected function classCoverageDistribution(array $classes)
289 foreach ($classes as $class) {
290 if ($class['coverage'] == 0) {
294 else if ($class['coverage'] == 100) {
299 $key = floor($class['coverage']/10)*10;
300 $key = $key . '-' . ($key + 10) . '%';
305 return json_encode(array_values($data));
309 * @param string $target
311 protected function copyFiles($target)
320 'excanvas.compressed.js',
324 'RGraph.common.core.js',
325 'RGraph.common.tooltips.js',
333 foreach ($files as $file) {
334 copy(self::$templatePath . $file, $target . $file);
339 * Returns the least tested methods.
341 * @param array $classes
342 * @param integer $max
345 protected function leastTestedMethods(array $classes, $max = 10)
349 foreach ($classes as $className => $class) {
350 foreach ($class['methods'] as $methodName => $method) {
351 if ($method['coverage'] < 100) {
352 if ($className != '*') {
353 $key = $className . '::' . $methodName;
358 $methods[$key] = $method['coverage'];
365 $methods = array_slice($methods, 0, min($max, count($methods)));
368 foreach ($methods as $name => $coverage) {
369 list($class, $method) = explode('::', $name);
372 ' <li><a href="%s">%s</a> (%d%%)</li>' . "\n",
373 $classes[$class]['methods'][$method]['file'],
383 * Returns the top project risks according to the CRAP index.
385 * @param array $classes
386 * @param integer $max
389 protected function topProjectRisks(array $classes, $max = 10)
393 foreach ($classes as $className => $class) {
394 if ($class['coverage'] < 100 &&
395 $class['ccn'] > count($class['methods'])) {
396 $risks[$className] = $class['crap'];
402 $risks = array_reverse(
403 array_slice($risks, 0, min($max, count($risks)))
408 foreach ($risks as $name => $crap) {
410 ' <li><a href="%s">%s</a> (%d)</li>' . "\n",
411 $classes[$name]['file'],