Commit 52758cae authored by catch's avatar catch
Browse files

Issue #3528139 by phenaproxima, tim.plunkett, catch: Package Manager should...

Issue #3528139 by phenaproxima, tim.plunkett, catch: Package Manager should use a copy of Composer that is local to the current project, if available

(cherry picked from commit 42209999)
parent c8235219
Loading
Loading
Loading
Loading
Loading
+20 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

namespace Drupal\package_manager;

use Composer\InstalledVersions;
use Drupal\Core\Config\ConfigFactoryInterface;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;

@@ -17,10 +18,19 @@
 */
final class ExecutableFinder implements ExecutableFinderInterface {

  /**
   * The path where Composer is installed in the project, or FALSE if it's not.
   */
  private string|false|null $composerPath = NULL;

  public function __construct(
    private readonly ExecutableFinderInterface $decorated,
    private readonly ConfigFactoryInterface $configFactory,
  ) {}
  ) {
    $this->composerPath = InstalledVersions::isInstalled('composer/composer')
      ? InstalledVersions::getInstallPath('composer/composer') . '/bin/composer'
      : FALSE;
  }

  /**
   * {@inheritdoc}
@@ -29,7 +39,15 @@ public function find(string $name): string {
    $executables = $this->configFactory->get('package_manager.settings')
      ->get('executables');

    return $executables[$name] ?? $this->decorated->find($name);
    if (isset($executables[$name])) {
      return $executables[$name];
    }

    // If we're looking for Composer, use the project's local copy if available.
    if ($name === 'composer' && $this->composerPath && file_exists($this->composerPath)) {
      return $this->composerPath;
    }
    return $this->decorated->find($name);
  }

}
+38 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@

use Drupal\package_manager\ExecutableFinder;
use Drupal\Tests\UnitTestCase;
use org\bovigo\vfs\vfsStream;
use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;

/**
@@ -36,4 +37,41 @@ public function testCheckConfigurationForExecutablePath(): void {
    $finder->find('rsync');
  }

  /**
   * Tests that the executable finder tries to use a local copy of Composer.
   */
  public function testComposerInstalledInProject(): void {
    vfsStream::setup('root', NULL, [
      'composer-path' => [
        'bin' => [],
      ],
    ]);
    $composer_path = 'vfs://root/composer-path/bin/composer';
    touch($composer_path);
    $this->assertFileExists($composer_path);

    $decorated = $this->prophesize(ExecutableFinderInterface::class);
    $decorated->find('composer')->willReturn('the real Composer');

    $finder = new ExecutableFinder(
      $decorated->reveal(),
      $this->getConfigFactoryStub([
        'package_manager.settings' => [
          'executables' => [],
        ],
      ]),
    );
    $reflector = new \ReflectionProperty($finder, 'composerPath');
    $reflector->setValue($finder, $composer_path);
    $this->assertSame($composer_path, $finder->find('composer'));

    // If the executable disappears, or Composer isn't locally installed, the
    // decorated executable finder should be called.
    unlink($composer_path);
    $this->assertSame('the real Composer', $finder->find('composer'));

    $reflector->setValue($finder, FALSE);
    $this->assertSame('the real Composer', $finder->find('composer'));
  }

}