ArtifactRepository.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Repository;
  12. use Composer\IO\IOInterface;
  13. use Composer\Json\JsonFile;
  14. use Composer\Package\Loader\ArrayLoader;
  15. use Composer\Package\Loader\LoaderInterface;
  16. /**
  17. * @author Serge Smertin <serg.smertin@gmail.com>
  18. */
  19. class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface
  20. {
  21. /** @var LoaderInterface */
  22. protected $loader;
  23. protected $lookup;
  24. protected $repoConfig;
  25. public function __construct(array $repoConfig, IOInterface $io)
  26. {
  27. parent::__construct();
  28. if (!extension_loaded('zip')) {
  29. throw new \RuntimeException('The artifact repository requires PHP\'s zip extension');
  30. }
  31. $this->loader = new ArrayLoader();
  32. $this->lookup = $repoConfig['url'];
  33. $this->io = $io;
  34. $this->repoConfig = $repoConfig;
  35. }
  36. public function getRepoConfig()
  37. {
  38. return $this->repoConfig;
  39. }
  40. protected function initialize()
  41. {
  42. parent::initialize();
  43. $this->scanDirectory($this->lookup);
  44. }
  45. private function scanDirectory($path)
  46. {
  47. $io = $this->io;
  48. $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
  49. $iterator = new \RecursiveIteratorIterator($directory);
  50. $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i');
  51. foreach ($regex as $file) {
  52. /* @var $file \SplFileInfo */
  53. if (!$file->isFile()) {
  54. continue;
  55. }
  56. $package = $this->getComposerInformation($file);
  57. if (!$package) {
  58. $io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
  59. continue;
  60. }
  61. $template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
  62. $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);
  63. $this->addPackage($package);
  64. }
  65. }
  66. /**
  67. * Find a file by name, returning the one that has the shortest path.
  68. *
  69. * @param \ZipArchive $zip
  70. * @param $filename
  71. * @return bool|int
  72. */
  73. private function locateFile(\ZipArchive $zip, $filename)
  74. {
  75. $indexOfShortestMatch = false;
  76. $lengthOfShortestMatch = -1;
  77. for ($i = 0; $i < $zip->numFiles; $i++) {
  78. $stat = $zip->statIndex($i);
  79. if (strcmp(basename($stat['name']), $filename) === 0) {
  80. $directoryName = dirname($stat['name']);
  81. if ($directoryName == '.') {
  82. //if composer.json is in root directory
  83. //it has to be the one to use.
  84. return $i;
  85. }
  86. if (strpos($directoryName, '\\') !== false ||
  87. strpos($directoryName, '/') !== false) {
  88. //composer.json files below first directory are rejected
  89. continue;
  90. }
  91. $length = strlen($stat['name']);
  92. if ($indexOfShortestMatch == false || $length < $lengthOfShortestMatch) {
  93. //Check it's not a directory.
  94. $contents = $zip->getFromIndex($i);
  95. if ($contents !== false) {
  96. $indexOfShortestMatch = $i;
  97. $lengthOfShortestMatch = $length;
  98. }
  99. }
  100. }
  101. }
  102. return $indexOfShortestMatch;
  103. }
  104. private function getComposerInformation(\SplFileInfo $file)
  105. {
  106. $zip = new \ZipArchive();
  107. $zip->open($file->getPathname());
  108. if (0 == $zip->numFiles) {
  109. return false;
  110. }
  111. $foundFileIndex = $this->locateFile($zip, 'composer.json');
  112. if (false === $foundFileIndex) {
  113. return false;
  114. }
  115. $configurationFileName = $zip->getNameIndex($foundFileIndex);
  116. $composerFile = "zip://{$file->getPathname()}#$configurationFileName";
  117. $json = file_get_contents($composerFile);
  118. $package = JsonFile::parseJson($json, $composerFile);
  119. $package['dist'] = array(
  120. 'type' => 'zip',
  121. 'url' => strtr($file->getPathname(), '\\', '/'),
  122. 'shasum' => sha1_file($file->getRealPath()),
  123. );
  124. $package = $this->loader->load($package);
  125. return $package;
  126. }
  127. }