diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index b470925b1..b1b7c445a 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' - name: Install dependencies run: composer update --no-progress --no-interaction --prefer-dist diff --git a/.gitignore b/.gitignore index 25a58fe9c..83cb61992 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ package.xml /vendor .idea -.php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache docs/_build tests/clover.xml diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 82% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 0a411e3e2..8f4027a98 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,19 +1,15 @@ setRules([ - '@PSR2' => true, + '@PHP71Migration' => true, '@Symfony' => true, '@Symfony:risky' => true, - 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], 'ordered_imports' => [ 'imports_order' => ['class', 'function', 'const'], ], 'declare_strict_types' => true, - 'psr0' => true, - 'psr4' => true, - 'random_api_migration' => true, 'yoda_style' => true, 'self_accessor' => false, 'phpdoc_no_useless_inheritdoc' => false, diff --git a/composer.json b/composer.json index afc45d39b..efc50b676 100644 --- a/composer.json +++ b/composer.json @@ -39,17 +39,17 @@ "symfony/polyfill-uuid": "^1.13.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", + "friendsofphp/php-cs-fixer": "^2.19|^3.4", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.3|^2.0", "nikic/php-parser": "^4.10.3", "php-http/mock-client": "^1.3", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^8.5.14|^9.4", "symfony/phpunit-bridge": "^5.2|^6.0", - "vimeo/psalm": "^4.2" + "vimeo/psalm": "^4.17" }, "suggest": { "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 24aa34355..e9c9d7eab 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,10 @@ parameters: ignoreErrors: + - + message: "#^Constructor of class Sentry\\\\Client has an unused parameter \\$serializer\\.$#" + count: 1 + path: src/Client.php + - message: "#^Method Sentry\\\\Client\\:\\:getIntegration\\(\\) should return T of Sentry\\\\Integration\\\\IntegrationInterface\\|null but returns T of Sentry\\\\Integration\\\\IntegrationInterface\\|null\\.$#" count: 1 @@ -11,25 +16,30 @@ parameters: path: src/ClientInterface.php - - message: "#^Offset 'host' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'host' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'path' does not exist on array\\(\\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string, 'scheme' \\=\\> 'http'\\|'https'\\)\\.$#" + message: "#^Offset 'path' does not exist on array\\{host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string, scheme\\: 'http'\\|'https'\\}\\.$#" count: 4 path: src/Dsn.php - - message: "#^Offset 'scheme' does not exist on array\\(\\?'scheme' \\=\\> string, \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'scheme' does not exist on array\\{scheme\\?\\: string, host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php - - message: "#^Offset 'user' does not exist on array\\('scheme' \\=\\> 'http'\\|'https', \\?'host' \\=\\> string, \\?'port' \\=\\> int, \\?'user' \\=\\> string, \\?'pass' \\=\\> string, \\?'path' \\=\\> string, \\?'query' \\=\\> string, \\?'fragment' \\=\\> string\\)\\.$#" + message: "#^Offset 'user' does not exist on array\\{scheme\\: 'http'\\|'https', host\\?\\: string, port\\?\\: int, user\\?\\: string, pass\\?\\: string, path\\?\\: string, query\\?\\: string, fragment\\?\\: string\\}\\.$#" count: 1 path: src/Dsn.php + - + message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\ErrorHandler\\:\\:cleanBacktraceFromErrorHandlerFrames\\(\\) expects array\\, array\\\\> given\\.$#" + count: 1 + path: src/ErrorHandler.php + - message: "#^Result of && is always false\\.$#" count: 2 @@ -55,11 +65,181 @@ parameters: count: 1 path: src/HttpClient/HttpClientFactory.php + - + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$responseFactory\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Constructor of class Sentry\\\\HttpClient\\\\HttpClientFactory has an unused parameter \\$uriFactory\\.$#" + count: 1 + path: src/HttpClient/HttpClientFactory.php + + - + message: "#^Property Sentry\\\\Integration\\\\IgnoreErrorsIntegration\\:\\:\\$options \\(array\\{ignore_exceptions\\: array\\\\>, ignore_tags\\: array\\\\}\\) does not accept array\\.$#" + count: 1 + path: src/Integration/IgnoreErrorsIntegration.php + + - + message: "#^Property Sentry\\\\Integration\\\\RequestIntegration\\:\\:\\$options \\(array\\{pii_sanitize_headers\\: array\\\\}\\) does not accept array\\.$#" + count: 1 + path: src/Integration/RequestIntegration.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: Sentry\\\\Breadcrumb\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getBeforeSendCallback\\(\\) should return callable\\(Sentry\\\\Event, Sentry\\\\EventHint\\|null\\)\\: Sentry\\\\Event\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getClassSerializers\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getContextLines\\(\\) should return int\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getDsn\\(\\) should return Sentry\\\\Dsn\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getEnvironment\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getErrorTypes\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpProxy\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getInAppIncludedPaths\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getIntegrations\\(\\) should return array\\\\|\\(callable\\(array\\\\)\\: array\\\\) but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getLogger\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxBreadcrumbs\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxRequestBodySize\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getMaxValueLength\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getPrefixes\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getRelease\\(\\) should return string\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getSampleRate\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getSendAttempts\\(\\) should return int but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getServerName\\(\\) should return string but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTags\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampleRate\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getTracesSampler\\(\\) should return \\(callable\\(\\)\\: mixed\\)\\|null but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:hasDefaultIntegrations\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:isCompressionEnabled\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldAttachStacktrace\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldCaptureSilencedErrors\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:shouldSendDefaultPii\\(\\) should return bool but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Argument of an invalid type object supplied for foreach, only iterables are supported\\.$#" count: 1 path: src/Serializer/AbstractSerializer.php + - + message: "#^Cannot cast mixed to string\\.$#" + count: 1 + path: src/Serializer/AbstractSerializer.php + + - + message: "#^Parameter \\#1 \\$backtrace of method Sentry\\\\StacktraceBuilder\\:\\:buildFromBacktrace\\(\\) expects array\\, array\\\\> given\\.$#" + count: 1 + path: src/StacktraceBuilder.php + - message: "#^Method Sentry\\\\ClientInterface\\:\\:captureException\\(\\) invoked with 3 parameters, 1\\-2 required\\.$#" count: 1 @@ -105,6 +285,36 @@ parameters: count: 3 path: src/State/HubInterface.php + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 1 + path: src/Tracing/SpanContext.php + + - + message: "#^Parameter \\#1 \\$email of method Sentry\\\\UserDataBag\\:\\:setEmail\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$id of method Sentry\\\\UserDataBag\\:\\:setId\\(\\) expects int\\|string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$ipAddress of method Sentry\\\\UserDataBag\\:\\:setIpAddress\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Parameter \\#1 \\$username of method Sentry\\\\UserDataBag\\:\\:setUsername\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/UserDataBag.php + + - + message: "#^Method Sentry\\\\Util\\\\JSON\\:\\:encode\\(\\) should return string but returns string\\|false\\.$#" + count: 1 + path: src/Util/JSON.php + - message: "#^Method Sentry\\\\State\\\\HubInterface\\:\\:captureException\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index f180d1007..43c4ca673 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,9 +4,9 @@ includes: parameters: tipsOfTheDay: false treatPhpDocTypesAsCertain: false - level: 8 + level: 9 paths: - src - excludes_analyse: + excludePaths: - tests/resources - tests/Fixtures diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 000000000..fe746dad3 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,73 @@ + + + + + $parsedDsn['host'] + $parsedDsn['path'] + $parsedDsn['scheme'] + $parsedDsn['user'] + + + + + GuzzleHttpClientOptions + GuzzleHttpClientOptions + GuzzleHttpClientOptions + SymfonyHttpClient + + + + + $userIntegration + $userIntegrations + + + + + JSON::encode($result) + + + string + + + $envelopeHeader + $itemHeader + + + + + $value + + + representationSerialize + + + + + captureException + captureLastError + captureMessage + + + + + captureException + captureLastError + captureMessage + startTransaction + + + + + new static() + + + + + captureException + captureLastError + captureMessage + startTransaction + + + diff --git a/psalm.xml.dist b/psalm.xml.dist index e1cbbfde2..c60ac2108 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -5,6 +5,7 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" memoizeMethodCallResults="true" + errorBaseline="psalm-baseline.xml" > diff --git a/src/Client.php b/src/Client.php index d159a5793..37a6838ac 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,7 +12,6 @@ use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface; @@ -56,11 +55,6 @@ final class Client implements ClientInterface */ private $integrations; - /** - * @var SerializerInterface The serializer of the client - */ - private $serializer; - /** * @var RepresentationSerializerInterface The representation serializer of the client */ @@ -105,7 +99,6 @@ public function __construct( $this->transport = $transport; $this->logger = $logger ?? new NullLogger(); $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); - $this->serializer = $serializer ?? new Serializer($this->options); $this->representationSerializer = $representationSerializer ?? new RepresentationSerializer($this->options); $this->stacktraceBuilder = new StacktraceBuilder($options, $this->representationSerializer); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 0108ad5b3..13c887760 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -4,13 +4,11 @@ namespace Sentry; -use Http\Client\HttpAsyncClient; use Http\Discovery\Psr17FactoryDiscovery; use Jean85\PrettyVersions; use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientFactory; use Sentry\Serializer\RepresentationSerializerInterface; -use Sentry\Serializer\Serializer; use Sentry\Serializer\SerializerInterface; use Sentry\Transport\DefaultTransportFactory; use Sentry\Transport\TransportFactoryInterface; @@ -38,11 +36,6 @@ final class ClientBuilder implements ClientBuilderInterface */ private $transport; - /** - * @var HttpAsyncClient|null The HTTP client - */ - private $httpClient; - /** * @var SerializerInterface|null The serializer to be injected in the client */ @@ -84,7 +77,7 @@ public function __construct(Options $options = null) */ public static function create(array $options = []): ClientBuilderInterface { - return new static(new Options($options)); + return new self(new Options($options)); } /** @@ -189,7 +182,7 @@ private function createDefaultTransportFactory(): DefaultTransportFactory Psr17FactoryDiscovery::findUrlFactory(), Psr17FactoryDiscovery::findResponseFactory(), $streamFactory, - $this->httpClient, + null, $this->sdkIdentifier, $this->sdkVersion ); diff --git a/src/Context/OsContext.php b/src/Context/OsContext.php index 7e6928f77..04e9387bd 100644 --- a/src/Context/OsContext.php +++ b/src/Context/OsContext.php @@ -41,7 +41,7 @@ final class OsContext */ public function __construct(string $name, ?string $version = null, ?string $build = null, ?string $kernelVersion = null) { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -66,7 +66,7 @@ public function getName(): string */ public function setName(string $name): void { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Context/RuntimeContext.php b/src/Context/RuntimeContext.php index 9801e9ee3..d0a114e48 100644 --- a/src/Context/RuntimeContext.php +++ b/src/Context/RuntimeContext.php @@ -29,7 +29,7 @@ final class RuntimeContext */ public function __construct(string $name, ?string $version = null) { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } @@ -52,7 +52,7 @@ public function getName(): string */ public function setName(string $name): void { - if (0 === \strlen(trim($name))) { + if ('' === trim($name)) { throw new \InvalidArgumentException('The $name argument cannot be an empty string.'); } diff --git a/src/Dsn.php b/src/Dsn.php index 04109e16d..e96d4484a 100644 --- a/src/Dsn.php +++ b/src/Dsn.php @@ -92,12 +92,10 @@ public static function createFromString(string $value): self throw new \InvalidArgumentException(sprintf('The "%s" DSN must contain a valid secret key.', $value)); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ if (!\in_array($parsedDsn['scheme'], ['http', 'https'], true)) { throw new \InvalidArgumentException(sprintf('The scheme of the "%s" DSN must be either "http" or "https".', $value)); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ $segmentPaths = explode('/', $parsedDsn['path']); $projectId = array_pop($segmentPaths); @@ -112,7 +110,6 @@ public static function createFromString(string $value): self $path = substr($parsedDsn['path'], 0, $lastSlashPosition); } - /** @psalm-suppress PossiblyUndefinedArrayOffset */ return new self( $parsedDsn['scheme'], $parsedDsn['host'], diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index ac2b63804..1fee7d01c 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -12,6 +12,8 @@ * error types and relays them to all configured listeners. Registering this * error handler more than once is not supported and will lead to nasty * problems. The code is based on the Symfony ErrorHandler component. + * + * @psalm-import-type StacktraceFrame from FrameBuilder */ final class ErrorHandler { @@ -93,7 +95,7 @@ final class ErrorHandler private static $reservedMemory; /** - * @var array List of error levels and their description + * @var string[] List of error levels and their description */ private const ERROR_LEVELS_DESCRIPTION = [ \E_DEPRECATED => 'Deprecated', @@ -379,11 +381,13 @@ private function handleException(\Throwable $exception): void * Cleans and returns the backtrace without the first frames that belong to * this error handler. * - * @param array $backtrace The backtrace to clear - * @param string $file The filename the backtrace was raised in - * @param int $line The line number the backtrace was raised at + * @param array> $backtrace The backtrace to clear + * @param string $file The filename the backtrace was raised in + * @param int $line The line number the backtrace was raised at * * @return array + * + * @psalm-param list $backtrace */ private function cleanBacktraceFromErrorHandlerFrames(array $backtrace, string $file, int $line): array { diff --git a/src/Event.php b/src/Event.php index 6f0e582d4..4c77aae91 100644 --- a/src/Event.php +++ b/src/Event.php @@ -71,7 +71,7 @@ final class Event private $messageFormatted; /** - * @var mixed[] The parameters to use to format the message + * @var string[] The parameters to use to format the message */ private $messageParams = []; @@ -376,7 +376,7 @@ public function getMessageParams(): array * Sets the error message. * * @param string $message The message - * @param mixed[] $params The parameters to use to format the message + * @param string[] $params The parameters to use to format the message * @param string|null $formatted The formatted message */ public function setMessage(string $message, array $params = [], ?string $formatted = null): void diff --git a/src/Frame.php b/src/Frame.php index ade6ac811..4d6a067af 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -67,7 +67,7 @@ final class Frame /** * @var array A mapping of variables which were available within - * this frame (usually context-locals) + * this frame (usually context-locals) */ private $vars = []; diff --git a/src/FrameBuilder.php b/src/FrameBuilder.php index 21659f585..393c1b946 100644 --- a/src/FrameBuilder.php +++ b/src/FrameBuilder.php @@ -10,6 +10,15 @@ * This class builds a {@see Frame} object out of a backtrace's raw frame. * * @internal + * + * @psalm-type StacktraceFrame array{ + * function?: string, + * line?: int, + * file?: string, + * class?: class-string, + * type?: string, + * args?: mixed[] + * } */ final class FrameBuilder { @@ -42,14 +51,7 @@ public function __construct(Options $options, RepresentationSerializerInterface * @param int $line The line at which the frame originated * @param array $backtraceFrame The raw frame * - * @psalm-param array{ - * function?: callable-string, - * line?: integer, - * file?: string, - * class?: class-string, - * type?: string, - * args?: array - * } $backtraceFrame + * @psalm-param StacktraceFrame $backtraceFrame */ public function buildFromBacktraceFrame(string $file, int $line, array $backtraceFrame): Frame { @@ -153,14 +155,7 @@ private function isFrameInApp(string $file, ?string $functionName): bool * * @return array * - * @throws \ReflectionException - * - * @psalm-param array{ - * function?: callable-string, - * class?: class-string, - * type?: string, - * args?: array - * } $backtraceFrame + * @psalm-param StacktraceFrame $backtraceFrame */ private function getFunctionArguments(array $backtraceFrame): array { @@ -179,7 +174,7 @@ private function getFunctionArguments(array $backtraceFrame): array } else { $reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call'); } - } elseif (isset($backtraceFrame['function']) && !\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { + } elseif (!\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) { $reflectionFunction = new \ReflectionFunction($backtraceFrame['function']); } } catch (\ReflectionException $e) { diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index 977d75850..0eb231acd 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -41,16 +41,6 @@ final class HttpClientFactory implements HttpClientFactoryInterface */ private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; - /** - * @var UriFactoryInterface The PSR-7 URI factory - */ - private $uriFactory; - - /** - * @var ResponseFactoryInterface The PSR-7 response factory - */ - private $responseFactory; - /** * @var StreamFactoryInterface The PSR-17 stream factory */ @@ -89,8 +79,6 @@ public function __construct( string $sdkIdentifier, string $sdkVersion ) { - $this->uriFactory = $uriFactory; - $this->responseFactory = $responseFactory; $this->streamFactory = $streamFactory; $this->httpClient = $httpClient; $this->sdkIdentifier = $sdkIdentifier; @@ -122,23 +110,19 @@ public function create(Options $options): HttpAsyncClientInterface $symfonyConfig['proxy'] = $options->getHttpProxy(); } - /** @psalm-suppress UndefinedClass */ $httpClient = new SymfonyHttplugClient( SymfonyHttpClient::create($symfonyConfig) ); } elseif (class_exists(GuzzleHttpClient::class)) { - /** @psalm-suppress UndefinedClass */ $guzzleConfig = [ GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, ]; if (null !== $options->getHttpProxy()) { - /** @psalm-suppress UndefinedClass */ $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ $httpClient = GuzzleHttpClient::createWithConfig($guzzleConfig); } elseif (class_exists(CurlHttpClient::class)) { $curlConfig = [ @@ -150,7 +134,6 @@ public function create(Options $options): HttpAsyncClientInterface $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ $httpClient = new CurlHttpClient(null, null, $curlConfig); } elseif (null !== $options->getHttpProxy()) { throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index 4cc5961a0..aae22e10b 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -14,11 +14,18 @@ * to a series of options that must match with its data. * * @author Stefano Arlandini + * + * @psalm-type IntegrationOptions array{ + * ignore_exceptions: list>, + * ignore_tags: array + * } */ final class IgnoreErrorsIntegration implements IntegrationInterface { /** * @var array The options + * + * @psalm-var IntegrationOptions */ private $options; @@ -29,7 +36,7 @@ final class IgnoreErrorsIntegration implements IntegrationInterface * @param array $options The options * * @psalm-param array{ - * ignore_exceptions?: list> + * ignore_exceptions?: list>, * ignore_tags?: array * } $options */ @@ -69,6 +76,8 @@ public function setupOnce(): void * * @param Event $event The event to check * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function shouldDropEvent(Event $event, array $options): bool { @@ -89,6 +98,8 @@ private function shouldDropEvent(Event $event, array $options): bool * * @param Event $event The event instance * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function isIgnoredException(Event $event, array $options): bool { @@ -113,6 +124,8 @@ private function isIgnoredException(Event $event, array $options): bool * * @param Event $event The event instance * @param array $options The options of the integration + * + * @psalm-param IntegrationOptions $options */ private function isIgnoredTag(Event $event, array $options): bool { diff --git a/src/Integration/IntegrationRegistry.php b/src/Integration/IntegrationRegistry.php index 731c4469c..ff15b2b60 100644 --- a/src/Integration/IntegrationRegistry.php +++ b/src/Integration/IntegrationRegistry.php @@ -86,7 +86,6 @@ private function getIntegrationsToSetup(Options $options): array $userIntegrations = $options->getIntegrations(); if (\is_array($userIntegrations)) { - /** @psalm-suppress PossiblyInvalidArgument */ $userIntegrationsClasses = array_map('get_class', $userIntegrations); $pickedIntegrationsClasses = []; @@ -100,7 +99,6 @@ private function getIntegrationsToSetup(Options $options): array } foreach ($userIntegrations as $userIntegration) { - /** @psalm-suppress PossiblyInvalidArgument */ $integrationClassName = \get_class($userIntegration); if (!isset($pickedIntegrationsClasses[$integrationClassName])) { diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index b67c39a06..b3b515387 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -69,6 +69,10 @@ final class RequestIntegration implements IntegrationInterface /** * @var array The options + * + * @psalm-var array{ + * pii_sanitize_headers: string[] + * } */ private $options; diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index 7f24be550..d7c775fce 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -36,7 +36,7 @@ public function setupOnce(): void return $event; } - if (isset($hint->extra['transaction'])) { + if (isset($hint->extra['transaction']) && \is_string($hint->extra['transaction'])) { $event->setTransaction($hint->extra['transaction']); } elseif (isset($_SERVER['PATH_INFO'])) { $event->setTransaction($_SERVER['PATH_INFO']); diff --git a/src/Serializer/RepresentationSerializer.php b/src/Serializer/RepresentationSerializer.php index 2d91446f6..f8b9e07e2 100644 --- a/src/Serializer/RepresentationSerializer.php +++ b/src/Serializer/RepresentationSerializer.php @@ -12,9 +12,6 @@ class RepresentationSerializer extends AbstractSerializer implements Representat { /** * {@inheritdoc} - * - * @psalm-suppress InvalidReturnType - * @psalm-suppress InvalidReturnStatement */ public function representationSerialize($value) { diff --git a/src/StacktraceBuilder.php b/src/StacktraceBuilder.php index d48b2cdf5..bf998cb61 100644 --- a/src/StacktraceBuilder.php +++ b/src/StacktraceBuilder.php @@ -11,14 +11,11 @@ * or from a backtrace. * * @internal + * + * @psalm-import-type StacktraceFrame from FrameBuilder */ final class StacktraceBuilder { - /** - * @var Options The SDK client options - */ - private $options; - /** * @var FrameBuilder An instance of the builder of {@see Frame} objects */ @@ -32,7 +29,6 @@ final class StacktraceBuilder */ public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer) { - $this->options = $options; $this->frameBuilder = new FrameBuilder($options, $representationSerializer); } @@ -53,10 +49,7 @@ public function buildFromException(\Throwable $exception): Stacktrace * @param string $file The file where the backtrace originated from * @param int $line The line from which the backtrace originated from * - * @phpstan-param list $backtrace + * @psalm-param list $backtrace */ public function buildFromBacktrace(array $backtrace, string $file, int $line): Stacktrace { diff --git a/src/State/Hub.php b/src/State/Hub.php index beb2b97be..4d8bf36a3 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -121,7 +121,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureMessage($message, $level, $this->getScope(), $hint); } @@ -136,7 +135,6 @@ public function captureException(\Throwable $exception, ?EventHint $hint = null) $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureException($exception, $this->getScope(), $hint); } @@ -165,7 +163,6 @@ public function captureLastError(?EventHint $hint = null): ?EventId $client = $this->getClient(); if (null !== $client) { - /** @psalm-suppress TooManyArguments */ return $this->lastEventId = $client->captureLastError($this->getScope(), $hint); } @@ -267,9 +264,6 @@ public function startTransaction(TransactionContext $context, array $customSampl /** * {@inheritdoc} - * - * @psalm-suppress MoreSpecificReturnType - * @psalm-suppress LessSpecificReturnStatement */ public function getTransaction(): ?Transaction { diff --git a/src/State/HubAdapter.php b/src/State/HubAdapter.php index a047a9dac..4782c8b81 100644 --- a/src/State/HubAdapter.php +++ b/src/State/HubAdapter.php @@ -108,7 +108,6 @@ public function bindClient(ClientInterface $client): void */ public function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } @@ -117,7 +116,6 @@ public function captureMessage(string $message, ?Severity $level = null, ?EventH */ public function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureException($exception, $hint); } @@ -134,7 +132,6 @@ public function captureEvent(Event $event, ?EventHint $hint = null): ?EventId */ public function captureLastError(?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureLastError($hint); } @@ -161,7 +158,6 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/src/Tracing/SpanContext.php b/src/Tracing/SpanContext.php index 6b98416ce..1fcaaaf51 100644 --- a/src/Tracing/SpanContext.php +++ b/src/Tracing/SpanContext.php @@ -201,7 +201,6 @@ public static function fromTraceparent(string $header) { @trigger_error(sprintf('The %s() method is deprecated since version 3.1 and will be removed in 4.0. Use TransactionContext::fromSentryTrace() instead.', __METHOD__), \E_USER_DEPRECATED); - /** @phpstan-ignore-next-line */ /** @psalm-suppress UnsafeInstantiation */ $context = new static(); if (!preg_match(self::TRACEPARENT_HEADER_REGEX, $header, $matches)) { diff --git a/src/Util/JSON.php b/src/Util/JSON.php index c56025246..3cd092980 100644 --- a/src/Util/JSON.php +++ b/src/Util/JSON.php @@ -22,12 +22,14 @@ final class JSON * @param int $options Bitmask consisting of JSON_* constants * @param int $maxDepth The maximum depth allowed for serializing $data * - * @return mixed - * * @throws JsonException If the encoding failed */ - public static function encode($data, int $options = 0, int $maxDepth = 512) + public static function encode($data, int $options = 0, int $maxDepth = 512): string { + if ($maxDepth < 1) { + throw new \InvalidArgumentException('The $maxDepth argument must be an integer greater than 0.'); + } + $options |= \JSON_UNESCAPED_UNICODE | \JSON_INVALID_UTF8_SUBSTITUTE; $encodedData = json_encode($data, $options, $maxDepth); diff --git a/src/functions.php b/src/functions.php index 7e88a5434..24ef3265b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -28,7 +28,6 @@ function init(array $options = []): void */ function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureMessage($message, $level, $hint); } @@ -40,7 +39,6 @@ function captureMessage(string $message, ?Severity $level = null, ?EventHint $hi */ function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureException($exception, $hint); } @@ -62,7 +60,6 @@ function captureEvent(Event $event, ?EventHint $hint = null): ?EventId */ function captureLastError(?EventHint $hint = null): ?EventId { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->captureLastError($hint); } @@ -120,6 +117,5 @@ function withScope(callable $callback): void */ function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - /** @psalm-suppress TooManyArguments */ return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } diff --git a/tests/Util/JSONTest.php b/tests/Util/JSONTest.php index d2a4ebf1e..ecc086a52 100644 --- a/tests/Util/JSONTest.php +++ b/tests/Util/JSONTest.php @@ -113,6 +113,23 @@ public function testEncodeThrowsIfValueIsResource(): void JSON::encode($resource); } + /** + * @dataProvider encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider + */ + public function testEncodeThrowsExceptionIfMaxDepthArgumentIsInvalid(int $maxDepth): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('The $maxDepth argument must be an integer greater than 0.'); + + JSON::encode('foo', 0, $maxDepth); + } + + public function encodeThrowsExceptionIfMaxDepthArgumentIsInvalidDataProvider(): \Generator + { + yield [0]; + yield [-1]; + } + public function testEncodeRespectsOptionsArgument(): void { $this->assertSame('{}', JSON::encode([], \JSON_FORCE_OBJECT));