diff --git a/phpstan/config.neon b/phpstan/config.neon index 6458aac46..d1a078e1e 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -35,6 +35,10 @@ parameters: # Mock errors - '~^Call to an undefined method (PHPUnit\\Framework\\MockObject\\MockObject|Prophecy\\Prophecy\\ObjectProphecy)::.*$~' + # Level 6 TODO + #- '~parameter .*? with no return typehint specified.$~' + #- '~has no return typehint specified.$~' + bootstrapFiles: - ../tests/bootstrap.php diff --git a/src/Composer/Util/AuthHelper.php b/src/Composer/Util/AuthHelper.php index abbc76cf7..deb0a4c39 100644 --- a/src/Composer/Util/AuthHelper.php +++ b/src/Composer/Util/AuthHelper.php @@ -75,6 +75,7 @@ class AuthHelper * @param string[] $headers * @return array|null containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be * retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json + * @phpstan-return ?array{retry: bool, storeAuth: string|bool} */ public function promptAuthIfNeeded($url, $origin, $statusCode, $reason = null, $headers = array()) { diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index e412f52e6..227449e7a 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -26,11 +26,16 @@ use React\Promise\Promise; * @internal * @author Jordi Boggiano * @author Nicolas Grekas + * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int, storeAuth: bool} + * @phpstan-type Job array{url: string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: resource, filename: string|false, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable} */ class CurlDownloader { + /** @var ?resource */ private $multiHandle; + /** @var ?resource */ private $shareHandle; + /** @var Job[] */ private $jobs = array(); /** @var IOInterface */ private $io; @@ -38,11 +43,15 @@ class CurlDownloader private $config; /** @var AuthHelper */ private $authHelper; + /** @var float */ private $selectTimeout = 5.0; + /** @var int */ private $maxRedirects = 20; /** @var ProxyManager */ private $proxyManager; + /** @var bool */ private $supportsSecureProxy; + /** @var array */ protected $multiErrors = array( CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'), CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."), @@ -50,6 +59,7 @@ class CurlDownloader CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!'), ); + /** @var mixed[] */ private static $options = array( 'http' => array( 'method' => CURLOPT_CUSTOMREQUEST, @@ -68,6 +78,7 @@ class CurlDownloader ), ); + /** @var array */ private static $timeInfo = array( 'total_time' => true, 'namelookup_time' => true, @@ -77,6 +88,10 @@ class CurlDownloader 'redirect_time' => true, ); + /** + * @param mixed[] $options + * @param bool $disableTls + */ public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false) { $this->io = $io; @@ -106,6 +121,13 @@ class CurlDownloader } /** + * @param callable $resolve + * @param callable $reject + * @param string $origin + * @param string $url + * @param mixed[] $options + * @param ?string $copyTo + * * @return int internal job id */ public function download($resolve, $reject, $origin, $url, $options, $copyTo = null) @@ -120,6 +142,15 @@ class CurlDownloader } /** + * @param callable $resolve + * @param callable $reject + * @param string $origin + * @param string $url + * @param mixed[] $options + * @param ?string $copyTo + * + * @param array{retryAuthFailure?: bool, redirects?: int, storeAuth?: bool} $attributes + * * @return int internal job id */ private function initDownload($resolve, $reject, $origin, $url, $options, $copyTo = null, array $attributes = array()) @@ -245,6 +276,10 @@ class CurlDownloader return (int) $curlHandle; } + /** + * @param int $id + * @return void + */ public function abortRequest($id) { if (isset($this->jobs[$id], $this->jobs[$id]['handle'])) { @@ -264,6 +299,9 @@ class CurlDownloader } } + /** + * @return void + */ public function tick() { if (!$this->jobs) { @@ -409,6 +447,10 @@ class CurlDownloader } } + /** + * @param Job $job + * @return string + */ private function handleRedirect(array $job, Response $response) { if ($locationHeader = $response->getHeader('location')) { @@ -440,6 +482,10 @@ class CurlDownloader throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')'); } + /** + * @param Job $job + * @return array{retry: bool, storeAuth: string|bool} + */ private function isAuthenticatedRetryNeeded(array $job, Response $response) { if (in_array($response->getStatusCode(), array(401, 403)) && $job['attributes']['retryAuthFailure']) { @@ -487,6 +533,14 @@ class CurlDownloader return array('retry' => false, 'storeAuth' => false); } + /** + * @param Job $job + * @param string $url + * + * @param array{retryAuthFailure?: bool, redirects?: int, storeAuth?: bool} $attributes + * + * @return void + */ private function restartJob(array $job, $url, array $attributes = array()) { if ($job['filename']) { @@ -499,6 +553,11 @@ class CurlDownloader $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes); } + /** + * @param Job $job + * @param string $errorMessage + * @return TransportException + */ private function failResponse(array $job, Response $response, $errorMessage) { if ($job['filename']) { @@ -513,6 +572,10 @@ class CurlDownloader return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode()); } + /** + * @param Job $job + * @return void + */ private function rejectJob(array $job, \Exception $e) { if (is_resource($job['headerHandle'])) { @@ -527,6 +590,10 @@ class CurlDownloader call_user_func($job['reject'], $e); } + /** + * @param int $code + * @return void + */ private function checkCurlResult($code) { if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) { diff --git a/src/Composer/Util/Http/CurlResponse.php b/src/Composer/Util/Http/CurlResponse.php index 82f725266..495a73b85 100644 --- a/src/Composer/Util/Http/CurlResponse.php +++ b/src/Composer/Util/Http/CurlResponse.php @@ -12,10 +12,20 @@ namespace Composer\Util\Http; +/** + * @phpstan-type CurlInfo array{url: mixed, content_type: mixed, http_code: mixed, header_size: mixed, request_size: mixed, filetime: mixed, ssl_verify_result: mixed, redirect_count: mixed, total_time: mixed, namelookup_time: mixed, connect_time: mixed, pretransfer_time: mixed, size_upload: mixed, size_download: mixed, speed_download: mixed, speed_upload: mixed, download_content_length: mixed, upload_content_length: mixed, starttransfer_time: mixed, redirect_time: mixed, certinfo: mixed, primary_ip: mixed, primary_port: mixed, local_ip: mixed, local_port: mixed, redirect_url: mixed} + */ class CurlResponse extends Response { + /** + * @see https://www.php.net/curl_getinfo + * @var CurlInfo + */ private $curlInfo; + /** + * @param CurlInfo $curlInfo + */ public function __construct(array $request, $code, array $headers, $body, array $curlInfo) { parent::__construct($request, $code, $headers, $body); @@ -23,7 +33,7 @@ class CurlResponse extends Response } /** - * @return array + * @return CurlInfo */ public function getCurlInfo() { diff --git a/src/Composer/Util/Http/RequestProxy.php b/src/Composer/Util/Http/RequestProxy.php index 67555c9b1..5a832706f 100644 --- a/src/Composer/Util/Http/RequestProxy.php +++ b/src/Composer/Util/Http/RequestProxy.php @@ -20,15 +20,19 @@ use Composer\Util\Url; */ class RequestProxy { + /** @var mixed[] */ private $contextOptions; + /** @var bool */ private $isSecure; + /** @var string */ private $formattedUrl; + /** @var string */ private $url; /** - * @param string $url - * @param array $contextOptions - * @param string $formattedUrl + * @param string $url + * @param mixed[] $contextOptions + * @param string $formattedUrl */ public function __construct($url, array $contextOptions, $formattedUrl) { @@ -41,7 +45,7 @@ class RequestProxy /** * Returns an array of context options * - * @return array + * @return mixed[] */ public function getContextOptions() { diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php index 29dd934d5..5f3cf73e5 100644 --- a/src/Composer/Util/Http/Response.php +++ b/src/Composer/Util/Http/Response.php @@ -13,14 +13,28 @@ namespace Composer\Util\Http; use Composer\Json\JsonFile; +use Composer\Util\HttpDownloader; +/** + * @phpstan-import-type Request from HttpDownloader + */ class Response { + /** @var Request */ private $request; + /** @var int */ private $code; + /** @var string[] */ private $headers; + /** @var ?string */ private $body; + /** + * @param Request $request + * @param int $code + * @param string[] $headers + * @param ?string $body + */ public function __construct(array $request, $code, array $headers, $body) { if (!isset($request['url'])) { @@ -32,6 +46,9 @@ class Response $this->body = $body; } + /** + * @return int + */ public function getStatusCode() { return $this->code; @@ -54,33 +71,51 @@ class Response return $value; } + /** + * @return string[] + */ public function getHeaders() { return $this->headers; } + /** + * @param string $name + * @return ?string + */ public function getHeader($name) { return self::findHeaderValue($this->headers, $name); } + /** + * @return ?string + */ public function getBody() { return $this->body; } + /** + * @return mixed + */ public function decodeJson() { return JsonFile::parseJson($this->body, $this->request['url']); } + /** + * @return void + * @phpstan-impure + */ public function collect() { + /** @phpstan-ignore-next-line */ $this->request = $this->code = $this->headers = $this->body = null; } /** - * @param array $headers array of returned headers like from getLastHeaders() + * @param string[] $headers array of returned headers like from getLastHeaders() * @param string $name header name (case insensitive) * @return string|null */ diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 809ac2b1a..1706dab9f 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -16,14 +16,18 @@ use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\Util\Http\Response; +use Composer\Util\Http\CurlDownloader; use Composer\Composer; use Composer\Package\Version\VersionParser; use Composer\Semver\Constraint\Constraint; use Composer\Exception\IrrecoverableDownloadException; use React\Promise\Promise; +use React\Promise\PromiseInterface; /** * @author Jordi Boggiano + * @phpstan-type Request array{url: string, options?: mixed[], copyTo?: ?string} + * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: TransportException} */ class HttpDownloader { @@ -33,22 +37,33 @@ class HttpDownloader const STATUS_FAILED = 4; const STATUS_ABORTED = 5; + /** @var IOInterface */ private $io; + /** @var Config */ private $config; + /** @var array */ private $jobs = array(); + /** @var mixed[] */ private $options = array(); + /** @var int */ private $runningJobs = 0; + /** @var int */ private $maxJobs = 12; + /** @var ?CurlDownloader */ private $curl; + /** @var ?RemoteFilesystem */ private $rfs; + /** @var int */ private $idGen = 0; + /** @var bool */ private $disabled; + /** @var bool */ private $allowAsync = false; /** * @param IOInterface $io The IO instance * @param Config $config The config - * @param array $options The options + * @param mixed[] $options The options * @param bool $disableTls */ public function __construct(IOInterface $io, Config $config, array $options = array(), $disableTls = false) @@ -68,7 +83,7 @@ class HttpDownloader $this->config = $config; if (self::isCurlEnabled()) { - $this->curl = new Http\CurlDownloader($io, $config, $options, $disableTls); + $this->curl = new CurlDownloader($io, $config, $options, $disableTls); } $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); @@ -82,14 +97,14 @@ class HttpDownloader * Download a file synchronously * * @param string $url URL to download - * @param array $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException * @return Response */ public function get($url, $options = array()) { - list($job) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false), true); + list($job) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => null), true); $this->wait($job['id']); $response = $this->getResponse($job['id']); @@ -106,7 +121,7 @@ class HttpDownloader $this->curl = null; - list($job) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false), true); + list($job) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => null), true); $this->wait($job['id']); $response = $this->getResponse($job['id']); @@ -119,14 +134,14 @@ class HttpDownloader * Create an async download operation * * @param string $url URL to download - * @param array $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException - * @return Promise + * @return PromiseInterface */ public function add($url, $options = array()) { - list(, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => false)); + list(, $promise) = $this->addJob(array('url' => $url, 'options' => $options, 'copyTo' => null)); return $promise; } @@ -136,7 +151,7 @@ class HttpDownloader * * @param string $url URL to download * @param string $to Path to copy to - * @param array $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException * @return Response @@ -154,10 +169,10 @@ class HttpDownloader * * @param string $url URL to download * @param string $to Path to copy to - * @param array $options Stream context options e.g. https://www.php.net/manual/en/context.http.php + * @param mixed[] $options Stream context options e.g. https://www.php.net/manual/en/context.http.php * although not all options are supported when using the default curl downloader * @throws TransportException - * @return Promise + * @return PromiseInterface */ public function addCopy($url, $to, $options = array()) { @@ -169,7 +184,7 @@ class HttpDownloader /** * Retrieve the options set in the constructor * - * @return array Options + * @return mixed[] Options */ public function getOptions() { @@ -179,6 +194,7 @@ class HttpDownloader /** * Merges new options * + * @param mixed[] $options * @return void */ public function setOptions(array $options) @@ -186,10 +202,17 @@ class HttpDownloader $this->options = array_replace_recursive($this->options, $options); } + /** + * @param Request $request + * @param bool $sync + * + * @return array{Job, PromiseInterface} + */ private function addJob($request, $sync = false) { $request['options'] = array_replace_recursive($this->options, $request['options']); + /** @var Job */ $job = array( 'id' => $this->idGen++, 'status' => self::STATUS_QUEUED, @@ -283,6 +306,10 @@ class HttpDownloader return array($job, $promise); } + /** + * @param int $id + * @return void + */ private function startJob($id) { $job = &$this->jobs[$id]; @@ -325,6 +352,7 @@ class HttpDownloader /** * @private + * @return void */ public function markJobDone() { @@ -335,6 +363,8 @@ class HttpDownloader * Wait for current async download jobs to complete * * @param int|null $index For internal use only, the job id + * + * @return void */ public function wait($index = null) { @@ -345,6 +375,8 @@ class HttpDownloader /** * @internal + * + * @return void */ public function enableAsync() { @@ -387,6 +419,10 @@ class HttpDownloader return $active; } + /** + * @param int $index Job id + * @return Response + */ private function getResponse($index) { if (!isset($this->jobs[$index])) { @@ -410,6 +446,10 @@ class HttpDownloader /** * @internal + * + * @param string $url + * @param array{warning?: string, info?: string, warning-versions?: string, info-versions?: string} $data + * @return void */ public static function outputWarnings(IOInterface $io, $url, $data) { @@ -433,11 +473,13 @@ class HttpDownloader /** * @internal + * + * @return ?string[] */ public static function getExceptionHints(\Exception $e) { if (!$e instanceof TransportException) { - return; + return null; } if ( @@ -460,8 +502,14 @@ class HttpDownloader 'The following exception probably indicates you are offline or have misconfigured DNS resolver(s)', ); } + + return null; } + /** + * @param Job $job + * @return bool + */ private function canUseCurl(array $job) { if (!$this->curl) { diff --git a/src/Composer/Util/IniHelper.php b/src/Composer/Util/IniHelper.php index d655419fc..8076f82ed 100644 --- a/src/Composer/Util/IniHelper.php +++ b/src/Composer/Util/IniHelper.php @@ -29,7 +29,7 @@ class IniHelper * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. * The loaded ini location is the first entry and may be empty. * - * @return array + * @return string[] */ public static function getAll() { diff --git a/src/Composer/Util/Loop.php b/src/Composer/Util/Loop.php index 00130a472..b103011a4 100644 --- a/src/Composer/Util/Loop.php +++ b/src/Composer/Util/Loop.php @@ -12,8 +12,9 @@ namespace Composer\Util; -use React\Promise\Promise; +use React\Promise\CancellablePromiseInterface; use Symfony\Component\Console\Helper\ProgressBar; +use React\Promise\PromiseInterface; /** * @author Jordi Boggiano @@ -24,7 +25,7 @@ class Loop private $httpDownloader; /** @var ProcessExecutor|null */ private $processExecutor; - /** @var Promise[][] */ + /** @var PromiseInterface[][] */ private $currentPromises = array(); /** @var int */ private $waitIndex = 0; @@ -56,6 +57,11 @@ class Loop return $this->processExecutor; } + /** + * @param PromiseInterface[] $promises + * @param ?ProgressBar $progress + * @return void + */ public function wait(array $promises, ProgressBar $progress = null) { /** @var \Exception|null */ @@ -113,11 +119,16 @@ class Loop } } + /** + * @return void + */ public function abortJobs() { foreach ($this->currentPromises as $promiseGroup) { foreach ($promiseGroup as $promise) { - $promise->cancel(); + if ($promise instanceof CancellablePromiseInterface) { + $promise->cancel(); + } } } } diff --git a/src/Composer/Util/NoProxyPattern.php b/src/Composer/Util/NoProxyPattern.php index 7e0b5cce0..21f7c4fca 100644 --- a/src/Composer/Util/NoProxyPattern.php +++ b/src/Composer/Util/NoProxyPattern.php @@ -420,6 +420,8 @@ class NoProxyPattern * @param string $int * @param int $min * @param int $max + * + * @return bool */ private function validateInt($int, $min, $max) { diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 86b041ced..7adcf00a6 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -142,6 +142,10 @@ class Platform return \strlen($str); } + /** + * @param ?resource $fd Open file descriptor or null to default to STDOUT + * @return bool + */ public static function isTty($fd = null) { if ($fd === null) { @@ -170,6 +174,9 @@ class Platform return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } + /** + * @return void + */ public static function workaroundFilesystemIssues() { if (self::isVirtualBoxGuest()) { diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index c00e8fd20..70ea40b77 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -31,6 +31,7 @@ class ProcessExecutor const STATUS_FAILED = 4; const STATUS_ABORTED = 5; + /** @var int */ protected static $timeout = 300; /** @var bool */ @@ -44,9 +45,13 @@ class ProcessExecutor * @phpstan-var array> */ private $jobs = array(); + /** @var int */ private $runningJobs = 0; + /** @var int */ private $maxJobs = 10; + /** @var int */ private $idGen = 0; + /** @var bool */ private $allowAsync = false; public function __construct(IOInterface $io = null) @@ -57,11 +62,11 @@ class ProcessExecutor /** * runs a process on the commandline * - * @param string $command the command to execute - * @param mixed $output the output will be written into this var if passed by ref - * if a callable is passed it will be used as output handler - * @param string $cwd the working directory - * @return int statuscode + * @param string $command the command to execute + * @param mixed $output the output will be written into this var if passed by ref + * if a callable is passed it will be used as output handler + * @param ?string $cwd the working directory + * @return int statuscode */ public function execute($command, &$output = null, $cwd = null) { @@ -75,9 +80,9 @@ class ProcessExecutor /** * runs a process on the commandline in TTY mode * - * @param string $command the command to execute - * @param string $cwd the working directory - * @return int statuscode + * @param string $command the command to execute + * @param ?string $cwd the working directory + * @return int statuscode */ public function executeTty($command, $cwd = null) { @@ -88,6 +93,13 @@ class ProcessExecutor return $this->doExecute($command, $cwd, false); } + /** + * @param string $command + * @param ?string $cwd + * @param bool $tty + * @param mixed $output + * @return int + */ private function doExecute($command, $cwd, $tty, &$output = null) { if ($this->io && $this->io->isDebug()) { @@ -120,6 +132,7 @@ class ProcessExecutor if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); } else { + /** @phpstan-ignore-next-line */ $process = new Process($command, $cwd, null, null, static::getTimeout()); } if (!Platform::isWindows() && $tty) { @@ -218,6 +231,10 @@ class ProcessExecutor return $promise; } + /** + * @param int $id + * @return void + */ private function startJob($id) { $job = &$this->jobs[$id]; @@ -286,6 +303,10 @@ class ProcessExecutor } } + /** + * @param ?int $index job id + * @return void + */ public function wait($index = null) { while (true) { @@ -299,6 +320,8 @@ class ProcessExecutor /** * @internal + * + * @return void */ public function enableAsync() { @@ -308,7 +331,8 @@ class ProcessExecutor /** * @internal * - * @return int number of active (queued or started) jobs + * @param ?int $index job id + * @return int number of active (queued or started) jobs */ public function countActiveJobs($index = null) { @@ -345,6 +369,8 @@ class ProcessExecutor /** * @private + * + * @return void */ public function markJobDone() { @@ -352,6 +378,7 @@ class ProcessExecutor } /** + * @param ?string $output * @return string[] */ public function splitLines($output) @@ -373,6 +400,11 @@ class ProcessExecutor /** * @private + * + * @param Process::ERR|Process::OUT $type + * @param string $buffer + * + * @return void */ public function outputHandler($type, $buffer) { @@ -402,7 +434,8 @@ class ProcessExecutor } /** - * @param int $timeout the timeout in seconds + * @param int $timeout the timeout in seconds + * @return void */ public static function setTimeout($timeout) { @@ -466,6 +499,11 @@ class ProcessExecutor return "'".str_replace("'", "'\\''", $argument)."'"; } + /** + * @param string $arg + * @param string $char + * @return bool + */ private static function isSurroundedBy($arg, $char) { return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 8b16bb456..56ec5cadd 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -28,25 +28,45 @@ use Composer\Util\Http\ProxyManager; */ class RemoteFilesystem { + /** @var IOInterface */ private $io; + /** @var Config */ private $config; + /** @var string */ private $scheme; + /** @var int */ private $bytesMax; + /** @var string */ private $originUrl; + /** @var string */ private $fileUrl; + /** @var ?string */ private $fileName; - private $retry; + /** @var bool */ + private $retry = false; + /** @var bool */ private $progress; + /** @var ?int */ private $lastProgress; + /** @var mixed[] */ private $options = array(); + /** @var array */ private $peerCertificateMap = array(); + /** @var bool */ private $disableTls = false; + /** @var string[] */ private $lastHeaders; - private $storeAuth; + /** @var bool */ + private $storeAuth = false; + /** @var AuthHelper */ private $authHelper; + /** @var bool */ private $degradedMode = false; + /** @var int */ private $redirects; + /** @var int */ private $maxRedirects = 20; + /** @var ProxyManager */ private $proxyManager; /** @@ -54,7 +74,7 @@ class RemoteFilesystem * * @param IOInterface $io The IO instance * @param Config $config The config - * @param array $options The options + * @param mixed[] $options The options * @param bool $disableTls * @param AuthHelper $authHelper */ @@ -80,11 +100,11 @@ class RemoteFilesystem /** * Copy the remote file in local. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param string $fileName the local filename - * @param bool $progress Display the progression - * @param array $options Additional context options + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param string $fileName the local filename + * @param bool $progress Display the progression + * @param mixed[] $options Additional context options * * @return bool true */ @@ -96,10 +116,10 @@ class RemoteFilesystem /** * Get the content. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param bool $progress Display the progression - * @param array $options Additional context options + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param bool $progress Display the progression + * @param mixed[] $options Additional context options * * @return bool|string The content */ @@ -111,7 +131,7 @@ class RemoteFilesystem /** * Retrieve the options set in the constructor * - * @return array Options + * @return mixed[] Options */ public function getOptions() { @@ -121,7 +141,8 @@ class RemoteFilesystem /** * Merges new options * - * @param array $options + * @param mixed[] $options + * @return void */ public function setOptions(array $options) { @@ -141,7 +162,7 @@ class RemoteFilesystem /** * Returns the headers of the last request * - * @return array + * @return string[] */ public function getLastHeaders() { @@ -149,7 +170,7 @@ class RemoteFilesystem } /** - * @param array $headers array of returned headers like from getLastHeaders() + * @param string[] $headers array of returned headers like from getLastHeaders() * @return int|null */ public static function findStatusCode(array $headers) @@ -167,7 +188,7 @@ class RemoteFilesystem } /** - * @param array $headers array of returned headers like from getLastHeaders() + * @param string[] $headers array of returned headers like from getLastHeaders() * @return string|null */ public function findStatusMessage(array $headers) @@ -187,11 +208,11 @@ class RemoteFilesystem /** * Get file content or copy action. * - * @param string $originUrl The origin URL - * @param string $fileUrl The file URL - * @param array $additionalOptions context options - * @param string $fileName the local filename - * @param bool $progress Display the progression + * @param string $originUrl The origin URL + * @param string $fileUrl The file URL + * @param mixed[] $additionalOptions context options + * @param string $fileName the local filename + * @param bool $progress Display the progression * * @throws TransportException|\Exception * @throws TransportException When the file could not be downloaded @@ -260,7 +281,7 @@ class RemoteFilesystem unset($origFileUrl, $proxy, $usingProxy); // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 - if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) { + if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist)) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } @@ -375,7 +396,7 @@ class RemoteFilesystem // check for gitlab 404 when downloading archives if ($statusCode === 404 - && $this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true) + && in_array($originUrl, $this->config->get('gitlab-domains'), true) && false !== strpos($fileUrl, 'archive.zip') ) { $result = false; @@ -493,7 +514,7 @@ class RemoteFilesystem $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); - if ($this->storeAuth && $this->config) { + if ($this->storeAuth) { $this->authHelper->storeAuth($this->originUrl, $this->storeAuth); $this->storeAuth = false; } @@ -596,7 +617,7 @@ class RemoteFilesystem case STREAM_NOTIFY_PROGRESS: if ($this->bytesMax > 0 && $this->progress) { - $progression = min(100, round($bytesTransferred / $this->bytesMax * 100)); + $progression = min(100, (int) round($bytesTransferred / $this->bytesMax * 100)); if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) { $this->lastProgress = $progression; @@ -741,6 +762,8 @@ class RemoteFilesystem * Fetch certificate common name and fingerprint for validation of SAN. * * @todo Remove when PHP 5.6 is minimum supported version. + * + * @return ?array{cn: string, fp: string} */ private function getCertificateCnAndFp($url, $options) { @@ -761,7 +784,7 @@ class RemoteFilesystem // Ideally this would just use stream_socket_client() to avoid sending a // HTTP request but that does not capture the certificate. if (false === $handle = @fopen($url, 'rb', false, $context)) { - return; + return null; } // Close non authenticated connection without reading any content. @@ -780,6 +803,8 @@ class RemoteFilesystem ); } } + + return null; } private function getUrlAuthority($url) diff --git a/src/Composer/Util/Silencer.php b/src/Composer/Util/Silencer.php index dcb362b52..361ef41f6 100644 --- a/src/Composer/Util/Silencer.php +++ b/src/Composer/Util/Silencer.php @@ -44,6 +44,8 @@ class Silencer /** * Restores a single state. + * + * @return void */ public static function restore() { diff --git a/src/Composer/Util/StreamContextFactory.php b/src/Composer/Util/StreamContextFactory.php index 4e6997155..9cab3b2fe 100644 --- a/src/Composer/Util/StreamContextFactory.php +++ b/src/Composer/Util/StreamContextFactory.php @@ -32,8 +32,8 @@ final class StreamContextFactory * * @param string $url URL the context is to be used for * @phpstan-param array{http?: array{follow_location?: int, max_redirects?: int, header?: string|array}} $defaultOptions - * @param array $defaultOptions Options to merge with the default - * @param array $defaultParams Parameters to specify on the context + * @param mixed[] $defaultOptions Options to merge with the default + * @param mixed[] $defaultParams Parameters to specify on the context * @throws \RuntimeException if https proxy required and OpenSSL uninstalled * @return resource Default context */ @@ -57,9 +57,9 @@ final class StreamContextFactory } /** - * @param string $url - * @param array $options - * @param bool $forCurl When true, will not add proxy values as these are handled separately + * @param string $url + * @param mixed[] $options + * @param bool $forCurl When true, will not add proxy values as these are handled separately * @phpstan-return array{http: array{header: string[], proxy?: string, request_fulluri: bool}, ssl: array} * @return array formatted as a stream context array */ @@ -130,9 +130,9 @@ final class StreamContextFactory } /** - * @param array $options + * @param mixed[] $options * - * @return array + * @return mixed[] */ public static function getTlsDefaults(array $options, LoggerInterface $logger = null) { @@ -239,8 +239,8 @@ final class StreamContextFactory * This method fixes the array by moving the content-type header to the end * * @link https://bugs.php.net/bug.php?id=61548 - * @param string|array $header - * @return array + * @param string|string[] $header + * @return string[] */ private static function fixHttpHeaderField($header) { diff --git a/src/Composer/Util/Svn.php b/src/Composer/Util/Svn.php index 2f7d070ab..0bc489de6 100644 --- a/src/Composer/Util/Svn.php +++ b/src/Composer/Util/Svn.php @@ -24,7 +24,7 @@ class Svn const MAX_QTY_AUTH_TRIES = 5; /** - * @var array + * @var ?array{username: string, password: string} */ protected $credentials; @@ -82,6 +82,9 @@ class Svn $this->process = $process ?: new ProcessExecutor($io); } + /** + * @return void + */ public static function cleanEnv() { // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 @@ -127,6 +130,15 @@ class Svn return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose); } + /** + * @param string $svnCommand + * @param string $cwd + * @param string $url + * @param string $path + * @param bool $verbose + * + * @return ?string + */ private function executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose) { // Regenerate the command at each try, to use the newly user-provided credentials @@ -136,10 +148,10 @@ class Svn $io = $this->io; $handler = function ($type, $buffer) use (&$output, $io, $verbose) { if ($type !== 'out') { - return; + return null; } if (strpos($buffer, 'Redirecting to URL ') === 0) { - return; + return null; } $output .= $buffer; if ($verbose) { @@ -178,7 +190,8 @@ class Svn } /** - * @param bool $cacheCredentials + * @param bool $cacheCredentials + * @return void */ public function setCacheCredentials($cacheCredentials) { diff --git a/src/Composer/Util/SyncHelper.php b/src/Composer/Util/SyncHelper.php index 9db7f3365..9e8c5d710 100644 --- a/src/Composer/Util/SyncHelper.php +++ b/src/Composer/Util/SyncHelper.php @@ -28,6 +28,8 @@ class SyncHelper * @param string $path the installation path for the package * @param PackageInterface $package the package to install * @param PackageInterface|null $prevPackage the previous package if this is an update and not an initial installation + * + * @return void */ public static function downloadAndInstallPackageSync(Loop $loop, DownloaderInterface $downloader, $path, PackageInterface $package, PackageInterface $prevPackage = null) { @@ -56,6 +58,8 @@ class SyncHelper * * @param Loop $loop Loop instance which you can get from $composer->getLoop() * @param PromiseInterface|null $promise + * + * @return void */ public static function await(Loop $loop, PromiseInterface $promise = null) { diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index 6fa70f0cc..01166d7b3 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -57,7 +57,7 @@ final class TlsHelper * * @param mixed $certificate X.509 certificate * - * @return array|null + * @return array{cn: string, san: string[]}|null */ public static function getCertificateNames($certificate) { @@ -130,6 +130,9 @@ final class TlsHelper * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @param string $certificate + * @return string */ public static function getCertificateFingerprint($certificate) { diff --git a/src/Composer/Util/Url.php b/src/Composer/Util/Url.php index e09754e01..7a281c924 100644 --- a/src/Composer/Util/Url.php +++ b/src/Composer/Util/Url.php @@ -103,6 +103,10 @@ class Url return $origin; } + /** + * @param string $url + * @return string + */ public static function sanitize($url) { // GitHub repository rename result in redirect locations containing the access_token as GET parameter diff --git a/src/bootstrap.php b/src/bootstrap.php index a3832ce1d..9b33ec1b0 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -10,9 +10,13 @@ * file that was distributed with this source code. */ +/** + * @param string $file + * @return ?\Composer\Autoload\ClassLoader + */ function includeIfExists($file) { - return file_exists($file) ? include $file : false; + return file_exists($file) ? include $file : null; } if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) {