1<?php 2/* 3 * This file is part of the PHP_CodeCoverage 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 11/** 12 * Factory for PHP_CodeCoverage_Report_Node_* object graphs. 13 * 14 * @since Class available since Release 1.1.0 15 */ 16class PHP_CodeCoverage_Report_Factory 17{ 18 /** 19 * @param PHP_CodeCoverage $coverage 20 * @return PHP_CodeCoverage_Report_Node_Directory 21 */ 22 public function create(PHP_CodeCoverage $coverage) 23 { 24 $files = $coverage->getData(); 25 $commonPath = $this->reducePaths($files); 26 $root = new PHP_CodeCoverage_Report_Node_Directory( 27 $commonPath, 28 null 29 ); 30 31 $this->addItems( 32 $root, 33 $this->buildDirectoryStructure($files), 34 $coverage->getTests(), 35 $coverage->getCacheTokens() 36 ); 37 38 return $root; 39 } 40 41 /** 42 * @param PHP_CodeCoverage_Report_Node_Directory $root 43 * @param array $items 44 * @param array $tests 45 * @param bool $cacheTokens 46 */ 47 private function addItems(PHP_CodeCoverage_Report_Node_Directory $root, array $items, array $tests, $cacheTokens) 48 { 49 foreach ($items as $key => $value) { 50 if (substr($key, -2) == '/f') { 51 $key = substr($key, 0, -2); 52 53 if (file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) { 54 $root->addFile($key, $value, $tests, $cacheTokens); 55 } 56 } else { 57 $child = $root->addDirectory($key); 58 $this->addItems($child, $value, $tests, $cacheTokens); 59 } 60 } 61 } 62 63 /** 64 * Builds an array representation of the directory structure. 65 * 66 * For instance, 67 * 68 * <code> 69 * Array 70 * ( 71 * [Money.php] => Array 72 * ( 73 * ... 74 * ) 75 * 76 * [MoneyBag.php] => Array 77 * ( 78 * ... 79 * ) 80 * ) 81 * </code> 82 * 83 * is transformed into 84 * 85 * <code> 86 * Array 87 * ( 88 * [.] => Array 89 * ( 90 * [Money.php] => Array 91 * ( 92 * ... 93 * ) 94 * 95 * [MoneyBag.php] => Array 96 * ( 97 * ... 98 * ) 99 * ) 100 * ) 101 * </code> 102 * 103 * @param array $files 104 * @return array 105 */ 106 private function buildDirectoryStructure($files) 107 { 108 $result = array(); 109 110 foreach ($files as $path => $file) { 111 $path = explode('/', $path); 112 $pointer = &$result; 113 $max = count($path); 114 115 for ($i = 0; $i < $max; $i++) { 116 if ($i == ($max - 1)) { 117 $type = '/f'; 118 } else { 119 $type = ''; 120 } 121 122 $pointer = &$pointer[$path[$i] . $type]; 123 } 124 125 $pointer = $file; 126 } 127 128 return $result; 129 } 130 131 /** 132 * Reduces the paths by cutting the longest common start path. 133 * 134 * For instance, 135 * 136 * <code> 137 * Array 138 * ( 139 * [/home/sb/Money/Money.php] => Array 140 * ( 141 * ... 142 * ) 143 * 144 * [/home/sb/Money/MoneyBag.php] => Array 145 * ( 146 * ... 147 * ) 148 * ) 149 * </code> 150 * 151 * is reduced to 152 * 153 * <code> 154 * Array 155 * ( 156 * [Money.php] => Array 157 * ( 158 * ... 159 * ) 160 * 161 * [MoneyBag.php] => Array 162 * ( 163 * ... 164 * ) 165 * ) 166 * </code> 167 * 168 * @param array $files 169 * @return string 170 */ 171 private function reducePaths(&$files) 172 { 173 if (empty($files)) { 174 return '.'; 175 } 176 177 $commonPath = ''; 178 $paths = array_keys($files); 179 180 if (count($files) == 1) { 181 $commonPath = dirname($paths[0]) . '/'; 182 $files[basename($paths[0])] = $files[$paths[0]]; 183 184 unset($files[$paths[0]]); 185 186 return $commonPath; 187 } 188 189 $max = count($paths); 190 191 for ($i = 0; $i < $max; $i++) { 192 // strip phar:// prefixes 193 if (strpos($paths[$i], 'phar://') === 0) { 194 $paths[$i] = substr($paths[$i], 7); 195 $paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR); 196 } 197 $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); 198 199 if (empty($paths[$i][0])) { 200 $paths[$i][0] = DIRECTORY_SEPARATOR; 201 } 202 } 203 204 $done = false; 205 $max = count($paths); 206 207 while (!$done) { 208 for ($i = 0; $i < $max - 1; $i++) { 209 if (!isset($paths[$i][0]) || 210 !isset($paths[$i+1][0]) || 211 $paths[$i][0] != $paths[$i+1][0]) { 212 $done = true; 213 break; 214 } 215 } 216 217 if (!$done) { 218 $commonPath .= $paths[0][0]; 219 220 if ($paths[0][0] != DIRECTORY_SEPARATOR) { 221 $commonPath .= DIRECTORY_SEPARATOR; 222 } 223 224 for ($i = 0; $i < $max; $i++) { 225 array_shift($paths[$i]); 226 } 227 } 228 } 229 230 $original = array_keys($files); 231 $max = count($original); 232 233 for ($i = 0; $i < $max; $i++) { 234 $files[implode('/', $paths[$i])] = $files[$original[$i]]; 235 unset($files[$original[$i]]); 236 } 237 238 ksort($files); 239 240 return substr($commonPath, 0, -1); 241 } 242} 243