Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ArchiveDownloader.php

function ArchiveDownloader::install

@inheritDoc

Throws

\RuntimeException

\UnexpectedValueException

Overrides FileDownloader::install

File

vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php, line 55

Class

ArchiveDownloader
Base downloader for archives

Namespace

Composer\Downloader

Code

public function install(PackageInterface $package, string $path, bool $output = true) : PromiseInterface {
    if ($output) {
        $this->io
            ->writeError("  - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path));
    }
    $vendorDir = $this->config
        ->get('vendor-dir');
    // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains
    // the archive to be extracted. This is the case when installing with create-project in the current directory
    // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here.
    if (false === strpos($this->filesystem
        ->normalizePath($vendorDir), $this->filesystem
        ->normalizePath($path . DIRECTORY_SEPARATOR))) {
        $this->filesystem
            ->emptyDirectory($path);
    }
    do {
        $temporaryDir = $vendorDir . '/composer/' . bin2hex(random_bytes(4));
    } while (is_dir($temporaryDir));
    $this->addCleanupPath($package, $temporaryDir);
    // avoid cleaning up $path if installing in "." for eg create-project as we can not
    // delete the directory we are currently in on windows
    if (!is_dir($path) || realpath($path) !== Platform::getCwd()) {
        $this->addCleanupPath($package, $path);
    }
    $this->filesystem
        ->ensureDirectoryExists($temporaryDir);
    $fileName = $this->getFileName($package, $path);
    $filesystem = $this->filesystem;
    $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) {
        // remove cache if the file was corrupted
        $this->clearLastCacheWrite($package);
        // clean up
        $filesystem->removeDirectory($temporaryDir);
        if (is_dir($path) && realpath($path) !== Platform::getCwd()) {
            $filesystem->removeDirectory($path);
        }
        $this->removeCleanupPath($package, $temporaryDir);
        $realpath = realpath($path);
        if ($realpath !== false) {
            $this->removeCleanupPath($package, $realpath);
        }
    };
    try {
        $promise = $this->extract($package, $fileName, $temporaryDir);
    } catch (\Exception $e) {
        $cleanup();
        throw $e;
    }
    return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path) : \React\Promise\PromiseInterface {
        if (file_exists($fileName)) {
            $filesystem->unlink($fileName);
        }
        
        /**
         * Returns the folder content, excluding .DS_Store
         *
         * @param  string         $dir Directory
         * @return \SplFileInfo[]
         */
        $getFolderContent = static function ($dir) : array {
            $finder = Finder::create()->ignoreVCS(false)
                ->ignoreDotFiles(false)
                ->notName('.DS_Store')
                ->depth(0)
                ->in($dir);
            return iterator_to_array($finder);
        };
        $renameRecursively = null;
        
        /**
         * Renames (and recursively merges if needed) a folder into another one
         *
         * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure
         * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would
         * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/
         *
         * @param  string $from Directory
         * @param  string $to   Directory
         * @return void
         */
        $renameRecursively = static function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) {
            $contentDir = $getFolderContent($from);
            // move files back out of the temp dir
            foreach ($contentDir as $file) {
                $file = (string) $file;
                if (is_dir($to . '/' . basename($file))) {
                    if (!is_dir($file)) {
                        throw new \RuntimeException('Installing ' . $package . ' would lead to overwriting the ' . $to . '/' . basename($file) . ' directory with a file from the package, invalid operation.');
                    }
                    $renameRecursively($file, $to . '/' . basename($file));
                }
                else {
                    $filesystem->rename($file, $to . '/' . basename($file));
                }
            }
        };
        $renameAsOne = false;
        if (!file_exists($path)) {
            $renameAsOne = true;
        }
        elseif ($filesystem->isDirEmpty($path)) {
            try {
                if ($filesystem->removeDirectoryPhp($path)) {
                    $renameAsOne = true;
                }
            } catch (\RuntimeException $e) {
                // ignore error, and simply do not renameAsOne
            }
        }
        $contentDir = $getFolderContent($temporaryDir);
        $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir));
        if ($renameAsOne) {
            // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
            if ($singleDirAtTopLevel) {
                $extractedDir = (string) reset($contentDir);
            }
            else {
                $extractedDir = $temporaryDir;
            }
            $filesystem->rename($extractedDir, $path);
        }
        else {
            // only one dir in the archive, extract its contents out of it
            $from = $temporaryDir;
            if ($singleDirAtTopLevel) {
                $from = (string) reset($contentDir);
            }
            $renameRecursively($from, $path);
        }
        $promise = $filesystem->removeDirectoryAsync($temporaryDir);
        return $promise->then(function () use ($package, $path, $temporaryDir) {
            $this->removeCleanupPath($package, $temporaryDir);
            $this->removeCleanupPath($package, $path);
        });
    }, static function ($e) use ($cleanup) {
        $cleanup();
        throw $e;
    });
}

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal