diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index fbe0112..780da6b 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -42,8 +42,8 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - #- name: Run type checker - # run: ./vendor/bin/psalm + - name: Run type checker + run: ./vendor/bin/psalm - name: Run unit tests run: ./vendor/bin/phpunit --testdox --no-coverage diff --git a/composer.json b/composer.json index 468f8bd..cb3817e 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "require-dev": { "guzzlehttp/psr7": "^2.4", "mockery/mockery": "^1.4.1", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0", + "vimeo/psalm": "^5.7.7" }, "conflict": { "phpunit/phpunit": "<8.0 || >= 11.0" diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..bb222f1 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/Constraint/BodyMatchesConstraint.php b/src/Constraint/BodyMatchesConstraint.php index 9a724fe..82d32e9 100644 --- a/src/Constraint/BodyMatchesConstraint.php +++ b/src/Constraint/BodyMatchesConstraint.php @@ -9,7 +9,7 @@ class BodyMatchesConstraint extends Constraint { /** @var Constraint */ - private $constraint; + private Constraint $constraint; public function __construct(Constraint $constraint) { @@ -23,10 +23,11 @@ public function __construct(Constraint $constraint) */ public function toString(): string { + /** @psalm-suppress InternalMethod */ return 'message body matches ' . $this->constraint->toString(); } - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!$other instanceof MessageInterface) { return false; @@ -34,6 +35,6 @@ protected function matches($other): bool $other->getBody()->rewind(); $body = $other->getBody()->getContents(); - return $this->constraint->evaluate($body, '', true); + return (bool)$this->constraint->evaluate($body, '', true); } } diff --git a/src/Constraint/HasHeaderConstraint.php b/src/Constraint/HasHeaderConstraint.php index 0205388..74521a1 100644 --- a/src/Constraint/HasHeaderConstraint.php +++ b/src/Constraint/HasHeaderConstraint.php @@ -9,13 +9,10 @@ class HasHeaderConstraint extends Constraint { - /** @var string */ - private $name; + private string $name; + private Constraint $constraint; - /** @var Constraint */ - private $constraint; - - public function __construct(string $name, $constraint = null) + public function __construct(string $name, Constraint|string|int $constraint = null) { if ($constraint === null) { $constraint = Assert::logicalNot(Assert::isEmpty()); @@ -34,10 +31,11 @@ public function __construct(string $name, $constraint = null) */ public function toString(): string { + /** @psalm-suppress InternalMethod */ return "has header '{$this->name}' that {$this->constraint->toString()}"; } - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!$other instanceof MessageInterface) { return false; diff --git a/src/Constraint/HasMethodConstraint.php b/src/Constraint/HasMethodConstraint.php index 77c736c..00a1653 100644 --- a/src/Constraint/HasMethodConstraint.php +++ b/src/Constraint/HasMethodConstraint.php @@ -8,8 +8,7 @@ class HasMethodConstraint extends Constraint { - /** @var string */ - private $method; + private string $method; public function __construct(string $method) { @@ -26,7 +25,7 @@ public function toString(): string return "has request method {$this->method}"; } - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!$other instanceof RequestInterface) { return false; diff --git a/src/Constraint/HasQueryParameterConstraint.php b/src/Constraint/HasQueryParameterConstraint.php index f4485a7..31a6459 100644 --- a/src/Constraint/HasQueryParameterConstraint.php +++ b/src/Constraint/HasQueryParameterConstraint.php @@ -8,15 +8,14 @@ class HasQueryParameterConstraint extends Constraint { - /** @var UrlEncodedMatches */ - private $inner; + private UrlEncodedMatches $inner; - public function __construct($nameMatcher, $valueMatcher = null) + public function __construct(Constraint|string $nameMatcher, Constraint|string|null $valueMatcher = null) { $this->inner = new UrlEncodedMatches($nameMatcher, $valueMatcher); } - protected function matches($other): bool + protected function matches(mixed $other): bool { if (is_string($other)) { return $this->matchesString($other); @@ -51,7 +50,7 @@ private function matchesString(string $other): bool private function matchesQueryString(string $query): bool { - return $this->inner->evaluate($query, "", true); + return (bool)$this->inner->evaluate($query, "", true); } diff --git a/src/Constraint/HasQueryParametersConstraint.php b/src/Constraint/HasQueryParametersConstraint.php index b177fad..32c1a19 100644 --- a/src/Constraint/HasQueryParametersConstraint.php +++ b/src/Constraint/HasQueryParametersConstraint.php @@ -6,8 +6,11 @@ class HasQueryParametersConstraint extends Constraint { /** @var HasQueryParameterConstraint[] */ - private $constraints = []; + private array $constraints = []; + /** + * @param array $constraints + */ public function __construct(array $constraints) { foreach ($constraints as $key => $value) { @@ -15,7 +18,7 @@ public function __construct(array $constraints) } } - public function matches($other): bool + public function matches(mixed $other): bool { foreach ($this->constraints as $constraint) { if (!$constraint->evaluate($other, "", true)) { diff --git a/src/Constraint/HasStatusConstraint.php b/src/Constraint/HasStatusConstraint.php index 7848173..f1da7b9 100644 --- a/src/Constraint/HasStatusConstraint.php +++ b/src/Constraint/HasStatusConstraint.php @@ -9,10 +9,9 @@ class HasStatusConstraint extends Constraint { - /** @var Constraint */ - private $status; + private Constraint $status; - public function __construct($status) + public function __construct(mixed $status) { if (!$status instanceof Constraint) { $status = Assert::equalTo($status); @@ -28,19 +27,20 @@ public function __construct($status) */ public function toString(): string { + /** @psalm-suppress InternalMethod */ return "response status {$this->status->toString()}"; } - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!$other instanceof ResponseInterface) { return false; } - return $this->status->evaluate($other->getStatusCode(), '', true); + return (bool)$this->status->evaluate($other->getStatusCode(), '', true); } - protected function additionalFailureDescription($other): string + protected function additionalFailureDescription(mixed $other): string { if ($other instanceof ResponseInterface) { return 'Actual status is ' . $other->getStatusCode() . ' and the body contains: ' . $other->getBody(); diff --git a/src/Constraint/HasUriConstraint.php b/src/Constraint/HasUriConstraint.php index 28d09ea..7c3e14f 100644 --- a/src/Constraint/HasUriConstraint.php +++ b/src/Constraint/HasUriConstraint.php @@ -8,8 +8,7 @@ class HasUriConstraint extends Constraint { - /** @var string */ - private $uri; + private string $uri; public function __construct(string $uri) { @@ -26,7 +25,7 @@ public function toString(): string return "has request URI '{$this->uri}'"; } - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!$other instanceof RequestInterface) { return false; diff --git a/src/Constraint/IsAbsoluteUriConstraint.php b/src/Constraint/IsAbsoluteUriConstraint.php index f095a0b..9407012 100644 --- a/src/Constraint/IsAbsoluteUriConstraint.php +++ b/src/Constraint/IsAbsoluteUriConstraint.php @@ -11,8 +11,12 @@ public function toString(): string return "is valid URI"; } - protected function matches($other): bool + protected function matches(mixed $other): bool { + if (!is_string($other)) { + return false; + } + $parts = parse_url($other); if ($parts === false) { return false; @@ -26,6 +30,6 @@ protected function matches($other): bool return false; } - return $parts !== false; + return true; } } \ No newline at end of file diff --git a/src/Constraint/UrlEncodedMatches.php b/src/Constraint/UrlEncodedMatches.php index 4cf1403..0ce4eb6 100644 --- a/src/Constraint/UrlEncodedMatches.php +++ b/src/Constraint/UrlEncodedMatches.php @@ -7,10 +7,10 @@ class UrlEncodedMatches extends Constraint { - private $nameMatcher; - private $valueMatcher; + private Constraint $nameMatcher; + private Constraint $valueMatcher; - public function __construct($nameMatcher, $valueMatcher = null) + public function __construct(Constraint|string $nameMatcher, Constraint|string|null $valueMatcher = null) { if (!($nameMatcher instanceof Constraint)) { $nameMatcher = new IsEqual($nameMatcher); @@ -26,10 +26,15 @@ public function __construct($nameMatcher, $valueMatcher = null) $this->valueMatcher = $valueMatcher; } - protected function matches($other): bool + protected function matches(mixed $other): bool { + if (!is_string($other)) { + return false; + } + parse_str($other, $parsedQuery); + /** @var array $parsedQuery */ foreach ($parsedQuery as $key => $value) { $nameMatches = $this->nameMatcher->evaluate($key, "", true); $valueMatches = $this->valueMatcher->evaluate($value, "", true); @@ -44,6 +49,7 @@ protected function matches($other): bool public function toString(): string { + /** @psalm-suppress InternalMethod */ return 'contains a name matching ' . $this->nameMatcher->toString() . ' and value matching ' . $this->valueMatcher->toString(); } diff --git a/src/Constraint/UrlEncodedMatchesMany.php b/src/Constraint/UrlEncodedMatchesMany.php index 9fef96c..54b0c60 100644 --- a/src/Constraint/UrlEncodedMatchesMany.php +++ b/src/Constraint/UrlEncodedMatchesMany.php @@ -6,8 +6,11 @@ class UrlEncodedMatchesMany extends Constraint { /** @var UrlEncodedMatches[] */ - private $constraints = []; + private array $constraints = []; + /** + * @param array $constraints + */ public function __construct(array $constraints) { foreach ($constraints as $key => $value) { @@ -15,7 +18,7 @@ public function __construct(array $constraints) } } - protected function matches($other): bool + protected function matches(mixed $other): bool { foreach ($this->constraints as $constraint) { if (!$constraint->evaluate($other, "", true)) { @@ -29,7 +32,7 @@ protected function matches($other): bool public function toString(): string { - return join(" and ", array_map(function(HasQueryParameterConstraint $c) { + return join(" and ", array_map(function(UrlEncodedMatches $c) { return $c->toString(); }, $this->constraints)); } diff --git a/src/Functions.php b/src/Functions.php index c033b59..d2e8cbd 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -4,6 +4,7 @@ use Helmich\JsonAssert\Constraint\JsonValueMatchesMany; use Helmich\Psr7Assert\Constraint\BodyMatchesConstraint; use Helmich\Psr7Assert\Constraint\HasHeaderConstraint; +use Helmich\Psr7Assert\Constraint\HasQueryParameterConstraint; use Helmich\Psr7Assert\Constraint\HasUriConstraint; use Helmich\Psr7Assert\Constraint\IsAbsoluteUriConstraint; use Helmich\Psr7Assert\Psr7AssertionsClass; @@ -15,26 +16,34 @@ function hasUri(string $uri): HasUriConstraint return new HasUriConstraint($uri); } -function hasHeader(string $name, $constraint = null): HasHeaderConstraint +function hasHeader(string $name, Constraint|string|int $constraint = null): HasHeaderConstraint { return new HasHeaderConstraint($name, $constraint); } +/** + * @param array $constraints + * @return Constraint + */ function hasHeaders(array $constraints): Constraint { return Psr7AssertionsClass::hasHeaders($constraints); } -function hasStatus($status): Constraint +function hasStatus(Constraint|int $status): Constraint { return Psr7AssertionsClass::hasStatus($status); } -function hasQueryParameter($name, $value = null): Constraint +function hasQueryParameter(Constraint|string $name, Constraint|string $value = null): Constraint { return Psr7AssertionsClass::hasQueryParameter($name, $value); } +/** + * @param array $constraints + * @return Constraint + */ function hasQueryParameters(array $constraints): Constraint { return Psr7AssertionsClass::hasQueryParameters($constraints); @@ -93,12 +102,12 @@ function isDelete(): Constraint return Psr7AssertionsClass::isDelete(); } -function bodyMatches($constraint): Constraint +function bodyMatches(Constraint $constraint): Constraint { return new BodyMatchesConstraint($constraint); } -function bodyMatchesJson($constraints): Constraint +function bodyMatchesJson(array $constraints): Constraint { return Assert::logicalAnd( hasContentType('application/json'), @@ -111,6 +120,10 @@ function bodyMatchesJson($constraints): Constraint ); } +/** + * @param array $constraints + * @return Constraint + */ function bodyMatchesForm(array $constraints): Constraint { return Psr7AssertionsClass::bodyMatchesForm($constraints); diff --git a/src/Psr7Assertions.php b/src/Psr7Assertions.php index 43349cb..ba2f267 100644 --- a/src/Psr7Assertions.php +++ b/src/Psr7Assertions.php @@ -28,17 +28,22 @@ public static function assertRequestHasUri(RequestInterface $request, string $ur Assert::assertThat($request, static::hasUri($uri)); } - public static function assertMessageHasHeader(MessageInterface $message, string $headerName, $headerValue = null): void + public static function assertMessageHasHeader(MessageInterface $message, string $headerName, Constraint|string $headerValue = null): void { Assert::assertThat($message, static::hasHeader($headerName, $headerValue)); } + /** + * @param MessageInterface $message + * @param array $constraints + * @return void + */ public static function assertMessageHasHeaders(MessageInterface $message, array $constraints): void { Assert::assertThat($message, static::hasHeaders($constraints)); } - public static function assertMessageBodyMatches(MessageInterface $message, $constraint): void + public static function assertMessageBodyMatches(MessageInterface $message, Constraint $constraint): void { Assert::assertThat($message, static::bodyMatches($constraint)); } @@ -48,6 +53,11 @@ public static function assertMessageBodyMatchesJson(MessageInterface $message, a Assert::assertThat($message, static::bodyMatchesJson($jsonConstraints)); } + /** + * @param MessageInterface $message + * @param array $formConstraints + * @return void + */ public static function assertMessageBodyMatchesForm(MessageInterface $message, array $formConstraints): void { Assert::assertThat($message, static::bodyMatchesForm($formConstraints)); @@ -103,25 +113,22 @@ public static function assertRequestIsDelete(RequestInterface $request): void Assert::assertThat($request, static::isDelete()); } - /** - * @param string $uri - */ public static function assertStringIsAbsoluteUri(string $uri): void { Assert::assertThat($uri, static::isAbsoluteUri()); } - /** - * @param string|UriInterface|RequestInterface $uriOrRequest - * @param string|Constraint $name - * @param string|Constraint|null $value - */ - public static function assertHasQueryParameter($uriOrRequest, $name, $value = null): void + public static function assertHasQueryParameter(string|UriInterface|RequestInterface $uriOrRequest, string|Constraint $name, string|Constraint|null $value = null): void { Assert::assertThat($uriOrRequest, static::hasQueryParameter($name, $value)); } - public static function assertHasQueryParameters($uriOrRequest, array $parameters): void + /** + * @param string|UriInterface|RequestInterface $uriOrRequest + * @param array $parameters + * @return void + */ + public static function assertHasQueryParameters(string|UriInterface|RequestInterface $uriOrRequest, array $parameters): void { Assert::assertThat($uriOrRequest, static::hasQueryParameters($parameters)); } @@ -136,7 +143,7 @@ public static function hasMethod(string $method): Constraint return new HasMethodConstraint($method); } - public static function hasStatus($status): Constraint + public static function hasStatus(Constraint|int $status): Constraint { return new HasStatusConstraint($status); } @@ -181,11 +188,15 @@ public static function isDelete(): Constraint return static::hasMethod('DELETE'); } - public static function hasHeader(string $name, $constraint = null): Constraint + public static function hasHeader(string $name, Constraint|string|int $constraint = null): Constraint { return new HasHeaderConstraint($name, $constraint); } + /** + * @param array $constraints + * @return Constraint + */ public static function hasHeaders(array $constraints): Constraint { $headerConstraints = []; @@ -206,16 +217,15 @@ public static function bodyMatches(Constraint $constraint): Constraint return new BodyMatchesConstraint($constraint); } - /** - * @param string|Constraint $name - * @param string|Constraint|null $value - * @return Constraint - */ - public static function hasQueryParameter($name, $value = null): Constraint + public static function hasQueryParameter(string|Constraint $name, string|Constraint|null $value = null): Constraint { return new HasQueryParameterConstraint($name, $value); } + /** + * @param array $parameters + * @return Constraint + */ public static function hasQueryParameters(array $parameters): Constraint { return new HasQueryParametersConstraint($parameters); @@ -234,6 +244,10 @@ public static function bodyMatchesJson(array $constraints): Constraint ); } + /** + * @param array $constraints + * @return Constraint + */ public static function bodyMatchesForm(array $constraints): Constraint { return Assert::logicalAnd( @@ -242,9 +256,6 @@ public static function bodyMatchesForm(array $constraints): Constraint ); } - /** - * @return Constraint - */ public static function isAbsoluteUri(): Constraint { return new IsAbsoluteUriConstraint();