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\Node; 12 13use SebastianBergmann\CodeCoverage\CodeCoverage; 14 15class Builder 16{ 17 /** 18 * @param CodeCoverage $coverage 19 * 20 * @return Directory 21 */ 22 public function build(CodeCoverage $coverage) 23 { 24 $files = $coverage->getData(); 25 $commonPath = $this->reducePaths($files); 26 $root = new 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 Directory $root 43 * @param array $items 44 * @param array $tests 45 * @param bool $cacheTokens 46 */ 47 private function addItems(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 * 105 * @return array 106 */ 107 private function buildDirectoryStructure($files) 108 { 109 $result = []; 110 111 foreach ($files as $path => $file) { 112 $path = explode('/', $path); 113 $pointer = &$result; 114 $max = count($path); 115 116 for ($i = 0; $i < $max; $i++) { 117 if ($i == ($max - 1)) { 118 $type = '/f'; 119 } else { 120 $type = ''; 121 } 122 123 $pointer = &$pointer[$path[$i] . $type]; 124 } 125 126 $pointer = $file; 127 } 128 129 return $result; 130 } 131 132 /** 133 * Reduces the paths by cutting the longest common start path. 134 * 135 * For instance, 136 * 137 * <code> 138 * Array 139 * ( 140 * [/home/sb/Money/Money.php] => Array 141 * ( 142 * ... 143 * ) 144 * 145 * [/home/sb/Money/MoneyBag.php] => Array 146 * ( 147 * ... 148 * ) 149 * ) 150 * </code> 151 * 152 * is reduced to 153 * 154 * <code> 155 * Array 156 * ( 157 * [Money.php] => Array 158 * ( 159 * ... 160 * ) 161 * 162 * [MoneyBag.php] => Array 163 * ( 164 * ... 165 * ) 166 * ) 167 * </code> 168 * 169 * @param array $files 170 * 171 * @return string 172 */ 173 private function reducePaths(&$files) 174 { 175 if (empty($files)) { 176 return '.'; 177 } 178 179 $commonPath = ''; 180 $paths = array_keys($files); 181 182 if (count($files) == 1) { 183 $commonPath = dirname($paths[0]) . '/'; 184 $files[basename($paths[0])] = $files[$paths[0]]; 185 186 unset($files[$paths[0]]); 187 188 return $commonPath; 189 } 190 191 $max = count($paths); 192 193 for ($i = 0; $i < $max; $i++) { 194 // strip phar:// prefixes 195 if (strpos($paths[$i], 'phar://') === 0) { 196 $paths[$i] = substr($paths[$i], 7); 197 $paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR); 198 } 199 $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]); 200 201 if (empty($paths[$i][0])) { 202 $paths[$i][0] = DIRECTORY_SEPARATOR; 203 } 204 } 205 206 $done = false; 207 $max = count($paths); 208 209 while (!$done) { 210 for ($i = 0; $i < $max - 1; $i++) { 211 if (!isset($paths[$i][0]) || 212 !isset($paths[$i+1][0]) || 213 $paths[$i][0] != $paths[$i+1][0]) { 214 $done = true; 215 break; 216 } 217 } 218 219 if (!$done) { 220 $commonPath .= $paths[0][0]; 221 222 if ($paths[0][0] != DIRECTORY_SEPARATOR) { 223 $commonPath .= DIRECTORY_SEPARATOR; 224 } 225 226 for ($i = 0; $i < $max; $i++) { 227 array_shift($paths[$i]); 228 } 229 } 230 } 231 232 $original = array_keys($files); 233 $max = count($original); 234 235 for ($i = 0; $i < $max; $i++) { 236 $files[implode('/', $paths[$i])] = $files[$original[$i]]; 237 unset($files[$original[$i]]); 238 } 239 240 ksort($files); 241 242 return substr($commonPath, 0, -1); 243 } 244} 245