diff --git a/doc/articles/troubleshooting.md b/doc/articles/troubleshooting.md index 9582fd500..f08ef6da0 100644 --- a/doc/articles/troubleshooting.md +++ b/doc/articles/troubleshooting.md @@ -346,13 +346,9 @@ composer update See also https://github.com/composer/composer/issues/4180 for more information. -## Zip archives are being reported as corrupted or not unpacked correctly. +## Zip archives are not unpacked correctly. Composer can unpack zipballs using either a system-provided `unzip` utility or PHP's -native `ZipArchiver` class, preferring the first. The `ZipArchiver` class however is -known to occassionally report valid zip files as corrupted, and does not support certain -advanced features with permissions and symlinks. - -If you have issues with zip files you should install a native implementation of unzip -and verify whether the problem persists. If so it is likely a real issue in the file -itself and you should contact the provider. +native `ZipArchiver` class. The `ZipArchiver` class is preferred on Windows. On other +OSes where ZIP files can contain permissions and symlinks, the `unzip` utility is +preferred. You're advised to install it if you need these features. diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 5278be91b..73a1de48b 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -48,7 +48,18 @@ class ZipDownloader extends ArchiveDownloader } if (!class_exists('ZipArchive') && !self::$hasSystemUnzip) { - throw new \RuntimeException('The zip extension and unzip command are both missing, skipping'); + // php.ini path is added to the error message to help users find the correct file + $iniPath = php_ini_loaded_file(); + + if ($iniPath) { + $iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath; + } else { + $iniMessage = 'A php.ini file does not exist. You will have to create one.'; + } + + $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage; + + throw new \RuntimeException($error); } return parent::download($package, $path); @@ -58,7 +69,7 @@ class ZipDownloader extends ArchiveDownloader { $processError = null; - if (self::$hasSystemUnzip) { + if (self::$hasSystemUnzip && !(class_exists('ZipArchive') && Platform::isWindows())) { $command = 'unzip '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path); if (!Platform::isWindows()) { $command .= ' && chmod -R u+w ' . ProcessExecutor::escape($path); @@ -73,22 +84,10 @@ class ZipDownloader extends ArchiveDownloader } catch (\Exception $e) { $processError = 'Failed to execute ' . $command . "\n\n" . $e->getMessage(); } - } - if (!class_exists('ZipArchive')) { - // php.ini path is added to the error message to help users find the correct file - $iniPath = php_ini_loaded_file(); - - if ($iniPath) { - $iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath; - } else { - $iniMessage = 'A php.ini file does not exist. You will have to create one.'; + if (!class_exists('ZipArchive')) { + throw new \RuntimeException($processError); } - - $error = "Could not decompress the archive, enable the PHP zip extension or install unzip.\n" - . $iniMessage . ($processError ? "\n" . $processError : ''); - - throw new \RuntimeException($error); } $zipArchive = new ZipArchive(); @@ -98,10 +97,7 @@ class ZipDownloader extends ArchiveDownloader } if (true !== $zipArchive->extractTo($path)) { - $this->io->writeError("As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension."); - $this->io->writeError("This may cause invalid reports of corrupted archives. Installing 'unzip' may remediate them."); - - throw new \RuntimeException("There was an error extracting the ZIP file, it is either corrupted or using an invalid format"); + throw new \RuntimeException(rtrim("There was an error extracting the ZIP file, it is either corrupted or using an invalid format.\n".$processError)); } $zipArchive->close();