Composer.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <?php
  2. namespace Illuminate\Support;
  3. use Closure;
  4. use Illuminate\Filesystem\Filesystem;
  5. use RuntimeException;
  6. use Symfony\Component\Console\Output\OutputInterface;
  7. use Symfony\Component\Process\Process;
  8. class Composer
  9. {
  10. /**
  11. * The filesystem instance.
  12. *
  13. * @var \Illuminate\Filesystem\Filesystem
  14. */
  15. protected $files;
  16. /**
  17. * The working path to regenerate from.
  18. *
  19. * @var string|null
  20. */
  21. protected $workingPath;
  22. /**
  23. * Create a new Composer manager instance.
  24. *
  25. * @param \Illuminate\Filesystem\Filesystem $files
  26. * @param string|null $workingPath
  27. */
  28. public function __construct(Filesystem $files, $workingPath = null)
  29. {
  30. $this->files = $files;
  31. $this->workingPath = $workingPath;
  32. }
  33. /**
  34. * Determine if the given Composer package is installed.
  35. *
  36. * @param string $package
  37. * @return bool
  38. *
  39. * @throws \RuntimeException
  40. */
  41. public function hasPackage($package)
  42. {
  43. $composer = json_decode(file_get_contents($this->findComposerFile()), true);
  44. return array_key_exists($package, $composer['require'] ?? [])
  45. || array_key_exists($package, $composer['require-dev'] ?? []);
  46. }
  47. /**
  48. * Install the given Composer packages into the application.
  49. *
  50. * @param array<int, string> $packages
  51. * @param bool $dev
  52. * @param \Closure|\Symfony\Component\Console\Output\OutputInterface|null $output
  53. * @param string|null $composerBinary
  54. * @return bool
  55. */
  56. public function requirePackages(array $packages, bool $dev = false, Closure|OutputInterface|null $output = null, $composerBinary = null)
  57. {
  58. $command = (new Collection([
  59. ...$this->findComposer($composerBinary),
  60. 'require',
  61. ...$packages,
  62. ]))
  63. ->when($dev, function ($command) {
  64. $command->push('--dev');
  65. })->all();
  66. return 0 === $this->getProcess($command, ['COMPOSER_MEMORY_LIMIT' => '-1'])
  67. ->run(
  68. $output instanceof OutputInterface
  69. ? function ($type, $line) use ($output) {
  70. $output->write(' '.$line);
  71. } : $output
  72. );
  73. }
  74. /**
  75. * Remove the given Composer packages from the application.
  76. *
  77. * @param array<int, string> $packages
  78. * @param bool $dev
  79. * @param \Closure|\Symfony\Component\Console\Output\OutputInterface|null $output
  80. * @param string|null $composerBinary
  81. * @return bool
  82. */
  83. public function removePackages(array $packages, bool $dev = false, Closure|OutputInterface|null $output = null, $composerBinary = null)
  84. {
  85. $command = (new Collection([
  86. ...$this->findComposer($composerBinary),
  87. 'remove',
  88. ...$packages,
  89. ]))
  90. ->when($dev, function ($command) {
  91. $command->push('--dev');
  92. })->all();
  93. return 0 === $this->getProcess($command, ['COMPOSER_MEMORY_LIMIT' => '-1'])
  94. ->run(
  95. $output instanceof OutputInterface
  96. ? function ($type, $line) use ($output) {
  97. $output->write(' '.$line);
  98. } : $output
  99. );
  100. }
  101. /**
  102. * Modify the "composer.json" file contents using the given callback.
  103. *
  104. * @param callable(array):array $callback
  105. * @return void
  106. *
  107. * @throws \RuntimeException
  108. */
  109. public function modify(callable $callback)
  110. {
  111. $composerFile = $this->findComposerFile();
  112. $composer = json_decode(file_get_contents($composerFile), true, 512, JSON_THROW_ON_ERROR);
  113. file_put_contents(
  114. $composerFile,
  115. json_encode(
  116. call_user_func($callback, $composer),
  117. JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
  118. )
  119. );
  120. }
  121. /**
  122. * Regenerate the Composer autoloader files.
  123. *
  124. * @param string|array $extra
  125. * @param string|null $composerBinary
  126. * @return int
  127. */
  128. public function dumpAutoloads($extra = '', $composerBinary = null)
  129. {
  130. $extra = $extra ? (array) $extra : [];
  131. $command = array_merge($this->findComposer($composerBinary), ['dump-autoload'], $extra);
  132. return $this->getProcess($command)->run();
  133. }
  134. /**
  135. * Regenerate the optimized Composer autoloader files.
  136. *
  137. * @param string|null $composerBinary
  138. * @return int
  139. */
  140. public function dumpOptimized($composerBinary = null)
  141. {
  142. return $this->dumpAutoloads('--optimize', $composerBinary);
  143. }
  144. /**
  145. * Get the Composer binary / command for the environment.
  146. *
  147. * @param string|null $composerBinary
  148. * @return array
  149. */
  150. public function findComposer($composerBinary = null)
  151. {
  152. if (! is_null($composerBinary) && $this->files->exists($composerBinary)) {
  153. return [$this->phpBinary(), $composerBinary];
  154. } elseif ($this->files->exists($this->workingPath.'/composer.phar')) {
  155. return [$this->phpBinary(), 'composer.phar'];
  156. }
  157. return ['composer'];
  158. }
  159. /**
  160. * Get the path to the "composer.json" file.
  161. *
  162. * @return string
  163. *
  164. * @throws \RuntimeException
  165. */
  166. protected function findComposerFile()
  167. {
  168. $composerFile = "{$this->workingPath}/composer.json";
  169. if (! file_exists($composerFile)) {
  170. throw new RuntimeException("Unable to locate `composer.json` file at [{$this->workingPath}].");
  171. }
  172. return $composerFile;
  173. }
  174. /**
  175. * Get the PHP binary.
  176. *
  177. * @return string
  178. */
  179. protected function phpBinary()
  180. {
  181. return php_binary();
  182. }
  183. /**
  184. * Get a new Symfony process instance.
  185. *
  186. * @param array $command
  187. * @param array $env
  188. * @return \Symfony\Component\Process\Process
  189. */
  190. protected function getProcess(array $command, array $env = [])
  191. {
  192. return (new Process($command, $this->workingPath, $env))->setTimeout(null);
  193. }
  194. /**
  195. * Set the working path used by the class.
  196. *
  197. * @param string $path
  198. * @return $this
  199. */
  200. public function setWorkingPath($path)
  201. {
  202. $this->workingPath = realpath($path);
  203. return $this;
  204. }
  205. /**
  206. * Get the version of Composer.
  207. *
  208. * @return string|null
  209. */
  210. public function getVersion()
  211. {
  212. $command = array_merge($this->findComposer(), ['-V', '--no-ansi']);
  213. $process = $this->getProcess($command);
  214. $process->run();
  215. $output = $process->getOutput();
  216. if (preg_match('/(\d+(\.\d+){2})/', $output, $version)) {
  217. return $version[1];
  218. }
  219. return explode(' ', $output)[2] ?? null;
  220. }
  221. }