diff --git a/composer.json b/composer.json
index 960e2f8..90d6fcf 100644
--- a/composer.json
+++ b/composer.json
@@ -10,7 +10,7 @@
],
"type": "library",
"require": {
- "php": ">=7.4 <8.5"
+ "php": ">=7.4 <=8.4.1"
},
"license": "MIT",
"autoload": {
@@ -31,20 +31,45 @@
],
"minimum-stability": "stable",
"require-dev": {
+ "php-mock/php-mock-phpunit": "^2.10",
+ "php-parallel-lint/php-parallel-lint": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-mockery": "^1.1",
+ "phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^9.6",
+ "slevomat/coding-standard": "^8.4",
"squizlabs/php_codesniffer": "^3.11",
- "php-mock/php-mock-phpunit": "^2.10",
- "php-parallel-lint/php-parallel-lint": "^1.4",
- "phpstan/phpstan": "^2.0",
- "vimeo/psalm": "^0.3.14"
+ "vimeo/psalm": "^4.9"
+ },
+ "scripts": {
+ "analyze": [
+ "@phpstan",
+ "@psalm"
+ ],
+ "build:clean": "git clean -fX build/",
+ "lint": "parallel-lint src tests",
+ "lint:paths": "parallel-lint",
+ "phpcs": "phpcs --standard=PSR12 --exclude=Generic.Files.LineLength",
+ "phpstan": [
+ "phpstan analyse --no-progress --memory-limit=1G",
+ "phpstan analyse -c phpstan-tests.neon --no-progress --memory-limit=1G"
+ ],
+ "phpunit": "phpunit --verbose --colors=always",
+ "phpunit-coverage": "phpunit --verbose --colors=always --coverage-html build/coverage",
+ "psalm": "psalm --show-info=false --config=psalm.xml",
+ "test": [
+ "@lint",
+ "@phpstan",
+ "@psalm",
+ "@phpunit"
+ ]
},
"archive": {
- "exclude": ["examples"]
+ "exclude": ["example"]
},
- "scripts": {
- "lint": "parallel-lint",
- "phpstan": "phpstan analyse --memory-limit=512M",
- "psalm": "psalm",
- "phpcs": "phpcs --standard=PSR12"
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
}
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..aac5769
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,476 @@
+
+
+
+
+
+
+
+ ./src
+ ./tests
+
+ A common coding standard for Ramsey's PHP libraries.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/phpstan-tests.neon b/phpstan-tests.neon
new file mode 100644
index 0000000..d55696e
--- /dev/null
+++ b/phpstan-tests.neon
@@ -0,0 +1,6 @@
+parameters:
+ tmpDir: ./build/cache/phpstan
+ level: max
+ paths:
+ - ./tests
+ reportUnmatchedIgnoredErrors: false
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..f478a1e
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,5 @@
+parameters:
+ tmpDir: ./build/cache/phpstan
+ level: max
+ paths:
+ - ./src
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..8dc065a
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Exception/CallbackException.php b/src/Exception/CallbackException.php
index d7f7b2e..b5c3d65 100644
--- a/src/Exception/CallbackException.php
+++ b/src/Exception/CallbackException.php
@@ -13,7 +13,7 @@ public function __construct(
array $errorDetails,
$message = "Callback Exception",
$code = 0,
- Throwable $previous = null
+ ?Throwable $previous = null
) {
$this->errorDetails = $errorDetails;
parent::__construct($message, $code, $previous);
diff --git a/src/Exception/SameSiteCallbackException.php b/src/Exception/SameSiteCallbackException.php
index d36bd16..2930bf6 100644
--- a/src/Exception/SameSiteCallbackException.php
+++ b/src/Exception/SameSiteCallbackException.php
@@ -12,7 +12,7 @@ class SameSiteCallbackException extends Exception
public function __construct(
$message = "Same Site Callback Exception",
$code = 0,
- Throwable $previous = null
+ ?Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
}
diff --git a/src/Handler/Callback.php b/src/Handler/Callback.php
index 9ee6126..da2b85a 100644
--- a/src/Handler/Callback.php
+++ b/src/Handler/Callback.php
@@ -16,6 +16,7 @@
use HelloCoop\Lib\TokenParser;
use Exception;
use HelloCoop\Utils\CurlWrapper;
+use Throwable;
class Callback
{
@@ -260,13 +261,13 @@ public function handleCallback(): ?string
*
* @param array $error Error details including 'target_uri', 'error', and 'error_description'.
* @param string $errorMessage A message describing the error.
- * @param \Throwable|null $previous Previous exception for chaining (optional).
+ * @param Throwable|null $previous Previous exception for chaining (optional).
*
* @return string The error page URL.
*
* @throws CallbackException If no error URI is provided.
*/
- private function sendErrorPage(array $error, string $errorMessage, \Throwable $previous = null): string
+ private function sendErrorPage(array $error, string $errorMessage, ?Throwable $previous = null): string
{
$error_uri = $error['target_uri'] ?? $this->config->getRoutes()['error'] ?? null;
if ($error_uri) {
diff --git a/src/Handler/Logout.php b/src/Handler/Logout.php
index 8dfe956..e78f7ca 100644
--- a/src/Handler/Logout.php
+++ b/src/Handler/Logout.php
@@ -7,12 +7,23 @@
use HelloCoop\Config\ConfigInterface;
use HelloCoop\Lib\Auth as AuthLib;
+/**
+ * Handles user logout functionality, including generating the logout URL and clearing authentication cookies.
+ */
class Logout
{
private HelloResponseInterface $helloResponse;
private HelloRequestInterface $helloRequest;
private ConfigInterface $config;
private ?AuthLib $authLib = null;
+
+ /**
+ * Constructor for the Logout class.
+ *
+ * @param HelloRequestInterface $helloRequest The request object for fetching data.
+ * @param HelloResponseInterface $helloResponse The response object for sending data.
+ * @param ConfigInterface $config The configuration object.
+ */
public function __construct(
HelloRequestInterface $helloRequest,
HelloResponseInterface $helloResponse,
@@ -23,6 +34,11 @@ public function __construct(
$this->config = $config;
}
+ /**
+ * Retrieves the AuthLib instance.
+ *
+ * @return AuthLib The authentication library instance.
+ */
private function getAuthLib(): AuthLib
{
return $this->authLib ??= new AuthLib(
@@ -32,13 +48,18 @@ private function getAuthLib(): AuthLib
);
}
+ /**
+ * Generates the URL to redirect to after logout.
+ *
+ * @return string The logout redirect URL.
+ */
public function generateLogoutUrl(): string
{
$targetUri = $this->helloRequest->fetch('target_uri');
$this->getAuthLib()->clearAuthCookie();
- if ($this->config->getLoginSync()) {
+ if ($this->config->getLogoutSync()) {
// Call the logoutSync callback
- call_user_func($this->config->getLoginSync());
+ call_user_func($this->config->getLogoutSync());
}
return $targetUri ?? $this->config->getRoutes()['loggedOut'];
}
diff --git a/src/HelloClient.php b/src/HelloClient.php
index 35eb821..554d562 100644
--- a/src/HelloClient.php
+++ b/src/HelloClient.php
@@ -31,9 +31,9 @@ class HelloClient
public function __construct(
ConfigInterface $config,
- HelloRequestInterface $helloRequest = null,
- HelloResponseInterface $helloResponse = null,
- PageRendererInterface $pageRenderer = null
+ ?HelloRequestInterface $helloRequest = null,
+ ?HelloResponseInterface $helloResponse = null,
+ ?PageRendererInterface $pageRenderer = null
) {
$this->config = $config;
$this->helloRequest = $helloRequest ??= new HelloRequest();
diff --git a/src/Lib/PKCE.php b/src/Lib/PKCE.php
index 79d5a3a..f73ce8c 100644
--- a/src/Lib/PKCE.php
+++ b/src/Lib/PKCE.php
@@ -4,7 +4,7 @@
class PKCE
{
- const VERIFIER_LENGTH = 43;
+ public const VERIFIER_LENGTH = 43;
/** Generate cryptographically strong random string
* @param int $size The desired length of the string
diff --git a/src/Type/AuthCookie.php b/src/Type/AuthCookie.php
index d24d51f..3dd2356 100644
--- a/src/Type/AuthCookie.php
+++ b/src/Type/AuthCookie.php
@@ -5,7 +5,8 @@
use InvalidArgumentException;
// Authentication cookie class, extending Claims
-class AuthCookie extends Claims {
+class AuthCookie extends Claims
+{
/** @var int */
public $iat;
@@ -15,7 +16,8 @@ class AuthCookie extends Claims {
*/
public $extraProperties = [];
- public function __construct(string $sub, int $iat) {
+ public function __construct(string $sub, int $iat)
+ {
parent::__construct($sub);
$this->iat = $iat;
}
@@ -23,21 +25,24 @@ public function __construct(string $sub, int $iat) {
/**
* Add an extra property.
*/
- public function setExtraProperty(string $key, $value): void {
+ public function setExtraProperty(string $key, $value): void
+ {
$this->extraProperties[$key] = $value;
}
/**
* Get an extra property.
*/
- public function getExtraProperty(string $key) {
+ public function getExtraProperty(string $key)
+ {
return $this->extraProperties[$key] ?? null;
}
/**
* Create an instance from an array of key-value pairs.
*/
- public static function fromArray(array $data): self {
+ public static function fromArray(array $data): self
+ {
if (!isset($data['sub'], $data['iat'])) {
throw new InvalidArgumentException('Missing required keys "sub" or "iat".');
}
@@ -52,11 +57,12 @@ public static function fromArray(array $data): self {
return $instance;
}
-
+
/**
* Convert the instance to an array of key-value pairs.
*/
- public function toArray(): array {
+ public function toArray(): array
+ {
return array_merge(['sub' => $this->sub, 'iat' => $this->iat], $this->extraProperties);
}
-}
\ No newline at end of file
+}
diff --git a/src/Type/AuthUpdates.php b/src/Type/AuthUpdates.php
index 5178dab..cf4586a 100644
--- a/src/Type/AuthUpdates.php
+++ b/src/Type/AuthUpdates.php
@@ -1,49 +1,61 @@
additionalProperties = $updates;
}
// Magic methods for dynamic properties
- public function __set(string $name, $value): void {
+ public function __set(string $name, $value): void
+ {
$this->additionalProperties[$name] = $value;
}
- public function __get(string $name) {
+ public function __get(string $name)
+ {
return $this->additionalProperties[$name] ?? null;
}
- public function __isset(string $name): bool {
+ public function __isset(string $name): bool
+ {
return isset($this->additionalProperties[$name]);
}
- public function __unset(string $name): void {
+ public function __unset(string $name): void
+ {
unset($this->additionalProperties[$name]);
}
// ArrayAccess implementation
- public function offsetExists($offset): bool {
+ public function offsetExists($offset): bool
+ {
return isset($this->additionalProperties[$offset]);
}
- public function offsetGet($offset) {
+ public function offsetGet($offset): ?string
+ {
return $this->additionalProperties[$offset] ?? null;
}
- public function offsetSet($offset, $value): void {
+ public function offsetSet($offset, $value): void
+ {
$this->additionalProperties[$offset] = $value;
}
- public function offsetUnset($offset): void {
+ public function offsetUnset($offset): void
+ {
unset($this->additionalProperties[$offset]);
}
- public function toArray(): array {
+ public function toArray(): array
+ {
return array_merge(get_object_vars($this), $this->additionalProperties);
}
-}
\ No newline at end of file
+}
diff --git a/src/Type/Claims.php b/src/Type/Claims.php
index b544130..5ca36ea 100644
--- a/src/Type/Claims.php
+++ b/src/Type/Claims.php
@@ -6,13 +6,17 @@
use HelloCoop\Type\Common\OptionalAccountClaimsTrait;
use HelloCoop\Type\Common\OptionalOrgClaimTrait;
-class Claims {
- use OptionalStringClaimsTrait, OptionalAccountClaimsTrait, OptionalOrgClaimTrait;
+class Claims
+{
+ use OptionalStringClaimsTrait;
+ use OptionalAccountClaimsTrait;
+ use OptionalOrgClaimTrait;
/** @var string */
public $sub;
- public function __construct(string $sub) {
+ public function __construct(string $sub)
+ {
$this->sub = $sub;
}
-}
\ No newline at end of file
+}
diff --git a/src/Type/Common/OptionalAccountClaimsTrait.php b/src/Type/Common/OptionalAccountClaimsTrait.php
index c05f72d..27c866b 100644
--- a/src/Type/Common/OptionalAccountClaimsTrait.php
+++ b/src/Type/Common/OptionalAccountClaimsTrait.php
@@ -2,10 +2,11 @@
namespace HelloCoop\Type\Common;
-trait OptionalAccountClaimsTrait {
+trait OptionalAccountClaimsTrait
+{
/**
* @var array
* An associative array of account claims (e.g., ['github' => ['id' => '123', 'username' => 'user']]).
*/
public $claims = [];
-}
\ No newline at end of file
+}
diff --git a/src/Type/Common/OptionalOrgClaimTrait.php b/src/Type/Common/OptionalOrgClaimTrait.php
index 4e97232..6bef94c 100644
--- a/src/Type/Common/OptionalOrgClaimTrait.php
+++ b/src/Type/Common/OptionalOrgClaimTrait.php
@@ -2,10 +2,11 @@
namespace HelloCoop\Type\Common;
-trait OptionalOrgClaimTrait {
+trait OptionalOrgClaimTrait
+{
/**
* @var array{id: string, domain: string}|null
* Optional organization claim.
*/
public $org;
-}
\ No newline at end of file
+}
diff --git a/src/Type/Common/OptionalStringClaimsTrait.php b/src/Type/Common/OptionalStringClaimsTrait.php
index 9798c9c..d86c8f9 100644
--- a/src/Type/Common/OptionalStringClaimsTrait.php
+++ b/src/Type/Common/OptionalStringClaimsTrait.php
@@ -1,7 +1,9 @@
codeVerifier = $codeVerifier;
$this->nonce = $nonce;
$this->redirectUri = $redirectUri;
$this->targetUri = $targetUri;
}
- public static function fromArray(array $data): self {
+ public static function fromArray(array $data): self
+ {
if (!isset($data['code_verifier'])) {
throw new InvalidArgumentException('Missing code_verifier');
}
@@ -42,7 +45,8 @@ public static function fromArray(array $data): self {
);
}
- public function toArray(): array {
+ public function toArray(): array
+ {
return [
'code_verifier' => $this->codeVerifier,
'nonce' => $this->nonce,
@@ -51,4 +55,3 @@ public function toArray(): array {
];
}
}
-
diff --git a/tests/Handler/LogoutTest.php b/tests/Handler/LogoutTest.php
index d2b343d..c59236a 100644
--- a/tests/Handler/LogoutTest.php
+++ b/tests/Handler/LogoutTest.php
@@ -77,7 +77,7 @@ public function testGenerateLogoutUrlWithLoginSync(): void
->method('__invoke');
$this->configMock
- ->method('getLoginSync')
+ ->method('getLogoutSync')
->willReturn($syncCallback);
$this->configMock
diff --git a/tests/Type/AuthCookieTest.php b/tests/Type/AuthCookieTest.php
index 7846e97..1993502 100644
--- a/tests/Type/AuthCookieTest.php
+++ b/tests/Type/AuthCookieTest.php
@@ -56,4 +56,4 @@ public function testFromArrayThrowsExceptionForMissingKeys()
$data = ['sub' => 'user123']; // Missing 'iat'
AuthCookie::fromArray($data);
}
-}
\ No newline at end of file
+}
diff --git a/tests/Type/AuthTest.php b/tests/Type/AuthTest.php
index 2db3e30..a8ceccb 100644
--- a/tests/Type/AuthTest.php
+++ b/tests/Type/AuthTest.php
@@ -12,7 +12,7 @@ public function testConstructorInitializesProperties()
{
$authCookie = new AuthCookie('user123', time());
$authCookie->setExtraProperty('role', 'admin');
-
+
$auth = new Auth(true, $authCookie, 'token123');
$this->assertTrue($auth->isLoggedIn);
diff --git a/tests/Type/OIDCTest.php b/tests/Type/OIDCTest.php
index ca95b96..26c1b17 100644
--- a/tests/Type/OIDCTest.php
+++ b/tests/Type/OIDCTest.php
@@ -5,8 +5,10 @@
use HelloCoop\Type\OIDC;
use PHPUnit\Framework\TestCase;
-class OIDCTest extends TestCase {
- public function testFromArrayValidData(): void {
+class OIDCTest extends TestCase
+{
+ public function testFromArrayValidData(): void
+ {
$data = [
'code_verifier' => 'test_verifier',
'nonce' => 'test_nonce',
@@ -23,7 +25,8 @@ public function testFromArrayValidData(): void {
$this->assertEquals('/home', $oidc->targetUri);
}
- public function testFromArrayMissingKeys(): void {
+ public function testFromArrayMissingKeys(): void
+ {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Missing code_verifier');
@@ -36,7 +39,8 @@ public function testFromArrayMissingKeys(): void {
OIDC::fromArray($data);
}
- public function testToArray(): void {
+ public function testToArray(): void
+ {
$oidc = new OIDC('test_verifier', 'test_nonce', 'https://example.com/callback', '/home');
$expected = [
diff --git a/tests/Utils/QueryParamFetcherTest.php b/tests/Utils/QueryParamFetcherTest.php
index a4249cb..c47e187 100644
--- a/tests/Utils/QueryParamFetcherTest.php
+++ b/tests/Utils/QueryParamFetcherTest.php
@@ -7,6 +7,7 @@
class QueryParamFetcherTest extends TestCase
{
+ protected array $originalGet;
protected function setUp(): void
{
// Backup the original $_GET superglobal