ArtifactRepository.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. if (!extension_loaded('zip')) {
  28. throw new \RuntimeException('The artifact repository requires PHP\'s zip extension');
  29. }
  30. $this->loader = new ArrayLoader();
  31. $this->lookup = $repoConfig['url'];
  32. $this->io = $io;
  33. $this->repoConfig = $repoConfig;
  34. }
  35. public function getRepoConfig()
  36. {
  37. return $this->repoConfig;
  38. }
  39. protected function initialize()
  40. {
  41. parent::initialize();
  42. $this->scanDirectory($this->lookup);
  43. }
  44. private function scanDirectory($path)
  45. {
  46. $io = $this->io;
  47. $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
  48. $iterator = new \RecursiveIteratorIterator($directory);
  49. $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i');
  50. foreach ($regex as $file) {
  51. /* @var $file \SplFileInfo */
  52. if (!$file->isFile()) {
  53. continue;
  54. }
  55. $package = $this->getComposerInformation($file);
  56. if (!$package) {
  57. $io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
  58. continue;
  59. }
  60. $template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
  61. $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);
  62. $this->addPackage($package);
  63. }
  64. }
  65. /**
  66. * Find a file by name, returning the one that has the shortest path.
  67. *
  68. * @param \ZipArchive $zip
  69. * @param $filename
  70. * @return bool|int
  71. */
  72. private function locateFile(\ZipArchive $zip, $filename)
  73. {
  74. $indexOfShortestMatch = false;
  75. $lengthOfShortestMatch = -1;
  76. for ($i = 0; $i < $zip->numFiles; $i++) {
  77. $stat = $zip->statIndex($i);
  78. if (strcmp(basename($stat['name']), $filename) === 0) {
  79. $directoryName = dirname($stat['name']);
  80. if ($directoryName == '.') {
  81. //if composer.json is in root directory
  82. //it has to be the one to use.
  83. return $i;
  84. }
  85. if (strpos($directoryName, '\\') !== false ||
  86. strpos($directoryName, '/') !== false) {
  87. //composer.json files below first directory are rejected
  88. continue;
  89. }
  90. $length = strlen($stat['name']);
  91. if ($indexOfShortestMatch == false || $length < $lengthOfShortestMatch) {
  92. //Check it's not a directory.
  93. $contents = $zip->getFromIndex($i);
  94. if ($contents !== false) {
  95. $indexOfShortestMatch = $i;
  96. $lengthOfShortestMatch = $length;
  97. }
  98. }
  99. }
  100. }
  101. return $indexOfShortestMatch;
  102. }
  103. private function getComposerInformation(\SplFileInfo $file)
  104. {
  105. $zip = new \ZipArchive();
  106. $zip->open($file->getPathname());
  107. if (0 == $zip->numFiles) {
  108. return false;
  109. }
  110. $foundFileIndex = $this->locateFile($zip, 'composer.json');
  111. if (false === $foundFileIndex) {
  112. return false;
  113. }
  114. $configurationFileName = $zip->getNameIndex($foundFileIndex);
  115. $composerFile = "zip://{$file->getPathname()}#$configurationFileName";
  116. $json = file_get_contents($composerFile);
  117. $package = JsonFile::parseJson($json, $composerFile);
  118. $package['dist'] = array(
  119. 'type' => 'zip',
  120. 'url' => strtr($file->getPathname(), '\\', '/'),
  121. 'shasum' => sha1_file($file->getRealPath()),
  122. );
  123. $package = $this->loader->load($package);
  124. return $package;
  125. }
  126. }