Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Command/BisectCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Override;
use PHPStan\Command\Bisect\BinarySearch;
use PHPStan\File\FileReader;
use PHPStan\Internal\HttpClientFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
Expand Down Expand Up @@ -106,7 +107,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 1;
}

$client = new Client([
$client = HttpClientFactory::createClient([
RequestOptions::TIMEOUT => 30,
RequestOptions::CONNECT_TIMEOUT => 10,
'headers' => [
Expand Down
4 changes: 2 additions & 2 deletions src/Command/FixerApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use DateTime;
use DateTimeImmutable;
use DateTimeZone;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\RequestOptions;
use Nette\Utils\Json;
Expand All @@ -23,6 +22,7 @@
use PHPStan\Internal\ComposerHelper;
use PHPStan\Internal\DirectoryCreator;
use PHPStan\Internal\DirectoryCreatorException;
use PHPStan\Internal\HttpClientFactory;
use PHPStan\PhpDoc\StubFilesProvider;
use PHPStan\Process\ProcessCanceledException;
use PHPStan\Process\ProcessCrashedException;
Expand Down Expand Up @@ -325,7 +325,7 @@ private function downloadPhar(
$output->writeln('<fg=green>Checking if there\'s a new PHPStan Pro release...</>');
}

$client = new Client([
$client = HttpClientFactory::createClient([
RequestOptions::TIMEOUT => 30,
RequestOptions::CONNECT_TIMEOUT => 5,
]);
Expand Down
28 changes: 28 additions & 0 deletions src/Internal/HttpClientFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace PHPStan\Internal;

use GuzzleHttp\Client;
use function extension_loaded;

final class HttpClientFactory
{

/**
* @param array<mixed> $config
*
* @see \GuzzleHttp\RequestOptions
*/
public static function createClient(array $config): Client
Comment on lines +11 to +16
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PHPDoc type array<mixed> implies an int-keyed list; this config is an associative options array (RequestOptions keys). Consider changing to array<string, mixed> (or a more specific shape) so static analysis keeps key type information.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a service, not a static function. So we can pass options from config parameters

{
if (
!isset($config['headers']['Accept-Encoding'])
&& extension_loaded('zlib')
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This factory forces Accept-Encoding whenever zlib is available, even if the caller explicitly disables decompression via RequestOptions::DECODE_CONTENT => false. In that case the response may be compressed but not decoded. Consider only advertising gzip/deflate when decode_content is not set to false (or when it’s true).

Suggested change
&& extension_loaded('zlib')
&& extension_loaded('zlib')
&& (
!\array_key_exists(\GuzzleHttp\RequestOptions::DECODE_CONTENT, $config)
|| $config[\GuzzleHttp\RequestOptions::DECODE_CONTENT] !== false
)

Copilot uses AI. Check for mistakes.
) {
Comment on lines +18 to +21
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isset($config['headers']['Accept-Encoding']) assumes headers is an array and only checks one exact casing. If a caller passes headers as a non-array (or uses a different header key casing), this can throw or result in duplicate Accept-Encoding values. Consider validating headers is an array and performing a case-insensitive presence check before setting the default.

Suggested change
if (
!isset($config['headers']['Accept-Encoding'])
&& extension_loaded('zlib')
) {
$hasAcceptEncodingHeader = false;
if (isset($config['headers']) && is_array($config['headers'])) {
foreach (array_keys($config['headers']) as $headerName) {
if (is_string($headerName) && strcasecmp($headerName, 'Accept-Encoding') === 0) {
$hasAcceptEncodingHeader = true;
break;
}
}
}
if (
!$hasAcceptEncodingHeader
&& extension_loaded('zlib')
) {
if (!isset($config['headers']) || !is_array($config['headers'])) {
$config['headers'] = [];
}

Copilot uses AI. Check for mistakes.
$config['headers']['Accept-Encoding'] = 'gzip,deflate';
}

return new Client($config);
}

}
Loading