Builder.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. namespace SebastianBergmann\CodeCoverage\Node;
  11. use SebastianBergmann\CodeCoverage\CodeCoverage;
  12. final class Builder
  13. {
  14. public function build(CodeCoverage $coverage): Directory
  15. {
  16. $files = $coverage->getData();
  17. $commonPath = $this->reducePaths($files);
  18. $root = new Directory(
  19. $commonPath,
  20. null
  21. );
  22. $this->addItems(
  23. $root,
  24. $this->buildDirectoryStructure($files),
  25. $coverage->getTests(),
  26. $coverage->getCacheTokens()
  27. );
  28. return $root;
  29. }
  30. private function addItems(Directory $root, array $items, array $tests, bool $cacheTokens): void
  31. {
  32. foreach ($items as $key => $value) {
  33. if (\substr($key, -2) == '/f') {
  34. $key = \substr($key, 0, -2);
  35. if (\file_exists($root->getPath() . \DIRECTORY_SEPARATOR . $key)) {
  36. $root->addFile($key, $value, $tests, $cacheTokens);
  37. }
  38. } else {
  39. $child = $root->addDirectory($key);
  40. $this->addItems($child, $value, $tests, $cacheTokens);
  41. }
  42. }
  43. }
  44. /**
  45. * Builds an array representation of the directory structure.
  46. *
  47. * For instance,
  48. *
  49. * <code>
  50. * Array
  51. * (
  52. * [Money.php] => Array
  53. * (
  54. * ...
  55. * )
  56. *
  57. * [MoneyBag.php] => Array
  58. * (
  59. * ...
  60. * )
  61. * )
  62. * </code>
  63. *
  64. * is transformed into
  65. *
  66. * <code>
  67. * Array
  68. * (
  69. * [.] => Array
  70. * (
  71. * [Money.php] => Array
  72. * (
  73. * ...
  74. * )
  75. *
  76. * [MoneyBag.php] => Array
  77. * (
  78. * ...
  79. * )
  80. * )
  81. * )
  82. * </code>
  83. */
  84. private function buildDirectoryStructure(array $files): array
  85. {
  86. $result = [];
  87. foreach ($files as $path => $file) {
  88. $path = \explode(\DIRECTORY_SEPARATOR, $path);
  89. $pointer = &$result;
  90. $max = \count($path);
  91. for ($i = 0; $i < $max; $i++) {
  92. $type = '';
  93. if ($i == ($max - 1)) {
  94. $type = '/f';
  95. }
  96. $pointer = &$pointer[$path[$i] . $type];
  97. }
  98. $pointer = $file;
  99. }
  100. return $result;
  101. }
  102. /**
  103. * Reduces the paths by cutting the longest common start path.
  104. *
  105. * For instance,
  106. *
  107. * <code>
  108. * Array
  109. * (
  110. * [/home/sb/Money/Money.php] => Array
  111. * (
  112. * ...
  113. * )
  114. *
  115. * [/home/sb/Money/MoneyBag.php] => Array
  116. * (
  117. * ...
  118. * )
  119. * )
  120. * </code>
  121. *
  122. * is reduced to
  123. *
  124. * <code>
  125. * Array
  126. * (
  127. * [Money.php] => Array
  128. * (
  129. * ...
  130. * )
  131. *
  132. * [MoneyBag.php] => Array
  133. * (
  134. * ...
  135. * )
  136. * )
  137. * </code>
  138. */
  139. private function reducePaths(array &$files): string
  140. {
  141. if (empty($files)) {
  142. return '.';
  143. }
  144. $commonPath = '';
  145. $paths = \array_keys($files);
  146. if (\count($files) === 1) {
  147. $commonPath = \dirname($paths[0]) . \DIRECTORY_SEPARATOR;
  148. $files[\basename($paths[0])] = $files[$paths[0]];
  149. unset($files[$paths[0]]);
  150. return $commonPath;
  151. }
  152. $max = \count($paths);
  153. for ($i = 0; $i < $max; $i++) {
  154. // strip phar:// prefixes
  155. if (\strpos($paths[$i], 'phar://') === 0) {
  156. $paths[$i] = \substr($paths[$i], 7);
  157. $paths[$i] = \str_replace('/', \DIRECTORY_SEPARATOR, $paths[$i]);
  158. }
  159. $paths[$i] = \explode(\DIRECTORY_SEPARATOR, $paths[$i]);
  160. if (empty($paths[$i][0])) {
  161. $paths[$i][0] = \DIRECTORY_SEPARATOR;
  162. }
  163. }
  164. $done = false;
  165. $max = \count($paths);
  166. while (!$done) {
  167. for ($i = 0; $i < $max - 1; $i++) {
  168. if (!isset($paths[$i][0]) ||
  169. !isset($paths[$i + 1][0]) ||
  170. $paths[$i][0] != $paths[$i + 1][0]) {
  171. $done = true;
  172. break;
  173. }
  174. }
  175. if (!$done) {
  176. $commonPath .= $paths[0][0];
  177. if ($paths[0][0] != \DIRECTORY_SEPARATOR) {
  178. $commonPath .= \DIRECTORY_SEPARATOR;
  179. }
  180. for ($i = 0; $i < $max; $i++) {
  181. \array_shift($paths[$i]);
  182. }
  183. }
  184. }
  185. $original = \array_keys($files);
  186. $max = \count($original);
  187. for ($i = 0; $i < $max; $i++) {
  188. $files[\implode(\DIRECTORY_SEPARATOR, $paths[$i])] = $files[$original[$i]];
  189. unset($files[$original[$i]]);
  190. }
  191. \ksort($files);
  192. return \substr($commonPath, 0, -1);
  193. }
  194. }