From b66b92f97a9fb97025d5783e837bc6f8b67d94d7 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:10:50 -0400 Subject: [PATCH 01/46] refactor: use ::class constants and import namespaces Replace hardcoded class strings and fully qualified names with ::class syntax and add missing use statements. This improves maintainability and enables better static analysis and IDE support. --- src/Auth.php | 2 +- src/Auth/Config.php | 4 +++- src/Auth/User.php | 3 ++- src/Auth/UsesSubscriptions.php | 3 ++- src/functions.php | 3 ++- tests/roles.test.php | 7 ++++--- tests/subscriptions.test.php | 6 ++++-- tests/tokens.test.php | 7 ++++--- 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 5f77c10..bbf7911 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -49,7 +49,7 @@ class Auth public function __construct() { - if (class_exists('Leaf\App')) { + if (class_exists(App::class)) { $this->middleware('auth.required', function () { response()->redirect(Config::get('redirect.login') ?? '/auth/login'); }); diff --git a/src/Auth/Config.php b/src/Auth/Config.php index fb561d1..21a7bb6 100644 --- a/src/Auth/Config.php +++ b/src/Auth/Config.php @@ -2,6 +2,8 @@ namespace Leaf\Auth; +use RuntimeException; + /** * Config for Leaf Auth * -------- @@ -45,7 +47,7 @@ public static function tokenSecret(): string return hash_hmac('sha256', 'leaf.auth.token.v1', $appKey); } - throw new \RuntimeException( + throw new RuntimeException( 'No auth token secret is set. Set `token.secret` in your auth config, or export AUTH_TOKEN_SECRET in the environment your app actually runs with. In Leaf MVC you can run `leaf key:generate` instead; lite apps have no key:generate and do not load .env files.' ); } diff --git a/src/Auth/User.php b/src/Auth/User.php index 63a60d8..de71f4b 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -2,6 +2,7 @@ namespace Leaf\Auth; +use Leaf\App; use Exception; use Throwable; use ReturnTypeWillChange; @@ -540,7 +541,7 @@ public function __unset($name) */ public function __call($method, $args) { - if (!class_exists('Leaf\App')) { + if (!class_exists(App::class)) { throw new Exception('Relations are only available in Leaf apps.'); } diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index 726ab9e..47eec71 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -2,6 +2,7 @@ namespace Leaf\Auth; +use Leaf\Billing\Tier; use Leaf\Billing\Subscription; /** @@ -104,7 +105,7 @@ public function isSubscribedTo(string $plan): bool $tier = $subscription['tier'] ?? null; - if ($tier instanceof \Leaf\Billing\Tier) { + if ($tier instanceof Tier) { $tier = $tier->toArray(); } diff --git a/src/functions.php b/src/functions.php index fca0b34..281bf35 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,9 +1,10 @@ middleware($name, function () use ($name) { @@ -170,8 +171,8 @@ function registerRoleMiddleware(Leaf\Auth $auth): void function runRoleMiddleware(string $name, $args) { - $registered = (new ReflectionClass(Leaf\Router::class))->getProperty('namedMiddleware'); $registered->setAccessible(true); + $registered = (new ReflectionClass(Router::class))->getProperty('namedMiddleware'); return $registered->getValue()[$name](is_array($args) ? $args : [$args]); } diff --git a/tests/subscriptions.test.php b/tests/subscriptions.test.php index 1b6ce3b..af6056b 100644 --- a/tests/subscriptions.test.php +++ b/tests/subscriptions.test.php @@ -1,5 +1,7 @@ config(['session' => false]); diff --git a/tests/tokens.test.php b/tests/tokens.test.php index 71fa289..c08b81f 100644 --- a/tests/tokens.test.php +++ b/tests/tokens.test.php @@ -1,5 +1,6 @@ 1, 'email' => 'null-roles@example.com', Config::get('roles.key') => null, @@ -118,7 +119,7 @@ // password.key put the bcrypt hash in the session and Inertia props Config::set(['password.key' => 'pass', 'hidden' => ['password', 'remember_token']]); - $user = new \Leaf\Auth\User([ + $user = new User([ 'id' => 1, 'email' => 'leak-check@example.com', 'pass' => '$2y$10$fakehashfakehashfakehash', @@ -136,7 +137,7 @@ unset($_ENV['APP_KEY'], $_ENV['AUTH_TOKEN_SECRET']); // constructing a user must not mint JWTs — session apps never read them - $user = new \Leaf\Auth\User(['id' => 5, 'email' => 'lazy@example.com'], false); + $user = new User(['id' => 5, 'email' => 'lazy@example.com'], false); expect($user->get())->toHaveKey('email'); From 44c0ac0958d5a7bd98f8e7608da342ee7a7fccc6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:11:35 -0400 Subject: [PATCH 02/46] refactor(auth): use null coalescing assignment for redirectUri Replace the manual isset check and assignment with the null coalescing assignment operator. This change simplifies the logic and improves code readability by using modern PHP syntax. --- src/Auth.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index bbf7911..1c15cbf 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -206,9 +206,7 @@ public function withGoogle( unset($options['name']); - if (!isset($options['redirectUri'])) { - $options['redirectUri'] = $this->env('APP_URL') . '/auth/google/callback'; - } + $options['redirectUri'] ??= $this->env('APP_URL') . '/auth/google/callback'; $this->withProvider($clientName, new Google(array_merge([ 'clientId' => $clientId, From 3039275a76d853b978355ed05c8b98c2cd15f506 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:12:08 -0400 Subject: [PATCH 03/46] refactor(auth): use first-class callable syntax for ucwords Update the array_map call to use the first-class callable syntax introduced in PHP 8.1. This improves code readability and allows for better static analysis compared to using string-based callbacks. --- src/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auth.php b/src/Auth.php index 1c15cbf..67814c1 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -1032,7 +1032,7 @@ protected function getTokenFromRequest() } elseif (function_exists('apache_request_headers')) { $requestHeaders = apache_request_headers(); // Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization) - $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); + $requestHeaders = array_combine(array_map(ucwords(...), array_keys($requestHeaders)), array_values($requestHeaders)); if (isset($requestHeaders['Authorization'])) { $headers = trim($requestHeaders['Authorization']); From 442f8bae75a170fde49011c44aae603bd3b0cf97 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:13:03 -0400 Subject: [PATCH 04/46] refactor(auth): implement Stringable interface in User class Implement the Stringable interface and add a string return type hint to the __toString method. This ensures compatibility with PHP 8.0+ and explicitly defines the class's string representation behavior. --- src/Auth/User.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index de71f4b..bfefcc9 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -2,6 +2,7 @@ namespace Leaf\Auth; +use Stringable; use Leaf\App; use Exception; use Throwable; @@ -21,7 +22,7 @@ * @version 1.0.0 * @property mixed $email */ -class User +class User implements Stringable { use UsesRoles; use UsesSubscriptions; @@ -478,7 +479,7 @@ public function errors() } #[ReturnTypeWillChange] - public function __toString() + public function __toString(): string { return json_encode($this->get()) ?: ''; } From 23d11f3249bf435063636e644ae0a8d135caadd5 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:13:49 -0400 Subject: [PATCH 05/46] refactor: remove unused exception variables in catch blocks Update catch statements to use non-capturing catches. This removes unused variables where the exception object is not required for the error handling logic, following PHP 8.0+ syntax. --- src/Auth/User.php | 2 +- src/Auth/UsesRoles.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index bfefcc9..b345c18 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -420,7 +420,7 @@ public function verifyEmail(): bool ->execute(); return true; - } catch (Throwable $th) { + } catch (Throwable) { return false; } } diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index 8353304..1dc5b27 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -46,7 +46,7 @@ public function assign($role): bool if (!array_key_exists($roleKey, (array) $this->data)) { try { $this->db->query('ALTER TABLE ' . Config::get('db.table') . " ADD COLUMN $roleKey TEXT")->execute(); - } catch (Throwable $th) { + } catch (Throwable) { // column already exists in the table, it just wasn't selected } } @@ -59,7 +59,7 @@ public function assign($role): bool ]) ->where(Config::get('id.key'), $this->data[Config::get('id.key')]) ->execute(); - } catch (Throwable $th) { + } catch (Throwable) { return false; } From f58fa30cbf14b7b727b77b654d02ef16dedef2c1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:14:27 -0400 Subject: [PATCH 06/46] refactor: use arrow functions for concise closure syntax Replace traditional anonymous functions with arrow functions in the UsesRoles trait and auth helper. This modernization reduces code verbosity and improves readability for simple return statements. --- src/Auth/UsesRoles.php | 4 +--- src/functions.php | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index 1dc5b27..9a5f0c7 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -164,9 +164,7 @@ protected function unknownRoles($roles): array return array_values(array_filter( is_string($roles) ? [$roles] : $roles, - function ($role) use ($registered) { - return !array_key_exists($role, $registered); - } + fn($role) => !array_key_exists($role, $registered) )); } diff --git a/src/functions.php b/src/functions.php index 281bf35..1db27a7 100644 --- a/src/functions.php +++ b/src/functions.php @@ -13,9 +13,7 @@ function auth() { if (!(Config::getStatic('auth'))) { - Config::singleton('auth', function () { - return new Auth(); - }); + Config::singleton('auth', fn() => new Auth()); } return Config::get('auth'); From cc3c3cb737f8fa718cfd9cb2fd2a546c34c5b728 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:14:51 -0400 Subject: [PATCH 07/46] refactor(Auth): use shorthand ternary operator for subscription return Simplify the return statement in the subscription method by using the shorthand ternary operator (?:). This change removes the redundant reference to the subscription property, making the code cleaner and more concise. --- src/Auth/UsesSubscriptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index 47eec71..7d6e1a5 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -46,7 +46,7 @@ public function subscription(): ?array $this->subscription['tier'] = billing()->tier($this->subscription['plan_id']); } - return $this->subscription ? $this->subscription : null; + return $this->subscription ?: null; } /** From 96a9855883851f532c0ce8a526c15b9ac2fbc39d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:15:19 -0400 Subject: [PATCH 08/46] refactor(tests): replace switch with match in createTableForUsers Update the table creation logic to use a match expression instead of a switch statement. This modernizes the code using PHP 8 features and improves readability by removing redundant break statements. --- tests/Pest.php | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/tests/Pest.php b/tests/Pest.php index 8111b01..35b2f50 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -79,9 +79,8 @@ function createTableForUsers($table = 'users', bool $uuid = false): void $idColumn = $uuid ? 'id TEXT PRIMARY KEY' : null; try { - switch ($_ENV['DB_CONNECTION']) { - case 'sqlite': - $sql = "CREATE TABLE IF NOT EXISTS $table ( + $sql = match ($_ENV['DB_CONNECTION']) { + 'sqlite' => "CREATE TABLE IF NOT EXISTS $table ( " . ($idColumn ?? 'id INTEGER PRIMARY KEY AUTOINCREMENT') . ", username TEXT NOT NULL, email TEXT NOT NULL, @@ -90,11 +89,8 @@ function createTableForUsers($table = 'users', bool $uuid = false): void roles TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - )"; - break; - - case 'mysql': - $sql = "CREATE TABLE IF NOT EXISTS $table ( + )", + 'mysql' => "CREATE TABLE IF NOT EXISTS $table ( " . ($idColumn ?? 'id SERIAL PRIMARY KEY') . ", username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, @@ -103,11 +99,8 @@ function createTableForUsers($table = 'users', bool $uuid = false): void roles JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - )"; - break; - - default: - $sql = "CREATE TABLE IF NOT EXISTS $table ( + )", + default => "CREATE TABLE IF NOT EXISTS $table ( " . ($idColumn ?? 'id SERIAL PRIMARY KEY') . ", username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, @@ -116,8 +109,8 @@ function createTableForUsers($table = 'users', bool $uuid = false): void roles JSONB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - )"; - } + )", + }; $db->query($sql)->execute(); } catch (Throwable $th) { From 588a018954684ba40d726ffe80a740e8886bd3f1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:15:43 -0400 Subject: [PATCH 09/46] test: remove redundant setAccessible calls in reflection tests Remove setAccessible(true) calls from ReflectionProperty instances in google.test.php and roles.test.php. These calls are no longer required in modern PHP versions, and removing them simplifies the test code. --- tests/google.test.php | 8 -------- tests/roles.test.php | 1 - 2 files changed, 9 deletions(-) diff --git a/tests/google.test.php b/tests/google.test.php index a857434..1723f8c 100644 --- a/tests/google.test.php +++ b/tests/google.test.php @@ -47,10 +47,6 @@ function expect($value = null): Expectation $clientSecret = $reflectionClass->getProperty('clientSecret'); $redirectUri = $reflectionClass->getProperty('redirectUri'); - $clientId->setAccessible(true); - $clientSecret->setAccessible(true); - $redirectUri->setAccessible(true); - expect($clientId->getValue($google)) ->toBe($_ENV['GOOGLE_AUTH_CLIENT_ID']); @@ -77,8 +73,6 @@ function expect($value = null): Expectation $reflectionClass = new ReflectionClass(Google::class); $redirectUri = $reflectionClass->getProperty('redirectUri'); - $redirectUri->setAccessible(true); - expect($redirectUri->getValue($google)) ->toBe("{$_ENV['APP_URL']}/auth/register/google"); }); @@ -97,8 +91,6 @@ function expect($value = null): Expectation $reflectionClass = new ReflectionClass(Google::class); $redirectUri = $reflectionClass->getProperty('redirectUri'); - $redirectUri->setAccessible(true); - expect($redirectUri->getValue($google)) ->toBe("{$_ENV['APP_URL']}/auth/google/callback"); }); diff --git a/tests/roles.test.php b/tests/roles.test.php index 6b40dd5..42dec81 100644 --- a/tests/roles.test.php +++ b/tests/roles.test.php @@ -171,7 +171,6 @@ function registerRoleMiddleware(Auth $auth): void function runRoleMiddleware(string $name, $args) { - $registered->setAccessible(true); $registered = (new ReflectionClass(Router::class))->getProperty('namedMiddleware'); return $registered->getValue()[$name](is_array($args) ? $args : [$args]); From 31001029aed3e4808fb5767a8fde71d612747211 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 10 Sep 2026 23:21:56 -0400 Subject: [PATCH 10/46] refactor: enable dead-code rule and remove redundant null initializations Add the dead-code rule set to alchemy configuration to improve code quality and maintainability. Remove explicit null assignments from class properties in Auth and UsesSubscriptions because they are redundant in PHP and flagged as dead code. --- alchemy.yml | 2 ++ src/Auth.php | 4 ++-- src/Auth/UsesSubscriptions.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/alchemy.yml b/alchemy.yml index 325f2dc..c372e82 100644 --- a/alchemy.yml +++ b/alchemy.yml @@ -42,6 +42,8 @@ refactor: php: '8.2' fluent-new-line: true import-names: true + sets: + - dead-code analyse: level: 8 diff --git a/src/Auth.php b/src/Auth.php index 67814c1..51797b2 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -30,13 +30,13 @@ class Auth * The currently authenticated user * @var ?User */ - protected $user = null; + protected $user; /** * Internal instance of Leaf DB * @var ?Db */ - protected $db = null; + protected $db; /** * All errors caught diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index 7d6e1a5..f3c9b52 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -19,7 +19,7 @@ trait UsesSubscriptions * User Subscription * @var array|null */ - protected $subscription = null; + protected $subscription; /** * Get current subscription From c332073bb9f9c17b4d1eb1d2201167bea5e43e20 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 20 Oct 2026 21:45:26 -0400 Subject: [PATCH 11/46] docs: remove redundant PHPDoc tags Remove redundant @return and @param tags from PHPDoc blocks across the codebase. These tags are unnecessary because the methods already use native PHP type hints. This cleanup reduces documentation noise and improves maintainability. --- src/Auth.php | 9 --------- src/Auth/Config.php | 1 - src/Auth/Model.php | 10 ---------- src/Auth/User.php | 8 -------- src/Auth/UsesRoles.php | 3 --- src/Auth/UsesSubscriptions.php | 10 ---------- 6 files changed, 41 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 51797b2..85aff0e 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -189,8 +189,6 @@ public function dbConnection(PDO $connection) * Register a Google OAuth client * --- * Register a Google OAuth client to use with Leaf Auth, should be a league/oauth2-client compatible client. - * @param string $clientId - * @param string $clientSecret * @param array{ * name?: string, * redirectUri?: string, @@ -292,7 +290,6 @@ public function roles() * Verify user credentials and sign them in with token or session * * @param array $credentials User credentials - * @return bool */ public function login(array $credentials): bool { @@ -348,7 +345,6 @@ public function login(array $credentials): bool * Save a new user to the database * * @param array $userData User data - * @return bool */ public function register(array $userData): bool { @@ -413,7 +409,6 @@ public function register(array $userData): bool * Update user data in the database * * @param array $userData User data - * @return bool */ public function update(array $userData): bool { @@ -487,7 +482,6 @@ public function update(array $userData): bool * * @param string $oldPassword Old password * @param string $newPassword New password - * @return bool */ public function updatePassword(string $oldPassword, string $newPassword): bool { @@ -541,8 +535,6 @@ public function updatePassword(string $oldPassword, string $newPassword): bool * Create a new user from OAuth * * @param array $userData User data - * - * @return bool */ public function fromOAuth(array $userData): bool { @@ -672,7 +664,6 @@ public function oauthToken() * Sign out the currently authenticated user * * @param string|mixed[]|callable|null $action Redirect to this url after logout - * @return bool */ public function logout($action = null): bool { diff --git a/src/Auth/Config.php b/src/Auth/Config.php index 21a7bb6..a8f4332 100644 --- a/src/Auth/Config.php +++ b/src/Auth/Config.php @@ -19,7 +19,6 @@ class Config * AUTH_TOKEN_SECRET from the env, then a secret derived from APP_KEY. * Without any of the three, token operations throw instead of signing * with a guessable default. - * @return string */ public static function tokenSecret(): string { diff --git a/src/Auth/Model.php b/src/Auth/Model.php index c5cc61d..c97fb83 100644 --- a/src/Auth/Model.php +++ b/src/Auth/Model.php @@ -48,8 +48,6 @@ public function __construct($data) * Create a new model resource * * @param array $data Data to be inserted - * - * @return PDOStatement|null */ public function create(array $data): ?PDOStatement { @@ -68,8 +66,6 @@ public function create(array $data): ?PDOStatement * Update a model resource * * @param array $data Data to be updated - * - * @return Db */ public function update(array $data): Db { @@ -82,8 +78,6 @@ public function update(array $data): Db /** * Delete a model resource - * - * @return Db */ public function delete(): Db { @@ -95,8 +89,6 @@ public function delete(): Db * Get a model resource * * @param string $columns Columns to search by separated by commas or * - * - * @return Db */ public function table($columns = '*'): Db { @@ -106,8 +98,6 @@ public function table($columns = '*'): Db /** * Save a model resource - * - * @return PDOStatement|null */ public function save(): ?PDOStatement { diff --git a/src/Auth/User.php b/src/Auth/User.php index b345c18..0686b6b 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -142,7 +142,6 @@ public function id() * @param array{ * email?: string, * } | array $userData User data - * @return bool */ public function update(array $userData): bool { @@ -210,7 +209,6 @@ public function update(array $userData): bool * * @param string $oldPassword Old password * @param string $newPassword New password - * @return bool */ public function updatePassword(string $oldPassword, string $newPassword): bool { @@ -264,7 +262,6 @@ public function updatePassword(string $oldPassword, string $newPassword): bool * Reset user password in the database * * @param string $newPassword New password - * @return bool */ public function resetPassword(string $newPassword): bool { @@ -302,7 +299,6 @@ public function resetPassword(string $newPassword): bool /** * Get auth information to be sent to the client - * @return object */ public function getAuthInfo(): object { @@ -345,7 +341,6 @@ public function tokens(): array /** * Generate a new JWT for the user * @param int $tokenLifetime - * @return string */ public function generateToken($tokenLifetime): string { @@ -366,7 +361,6 @@ public function generateToken($tokenLifetime): string * Generate a verification token for the user * @param mixed $expiresIn Token expiration time * @param string|null $purpose Purpose of the token - * @return string */ public function generateVerificationToken($expiresIn = null, ?string $purpose = null): string { @@ -390,7 +384,6 @@ public function generateVerificationToken($expiresIn = null, ?string $purpose = /** * Check if email is verified - * @return bool */ public function isVerified(): bool { @@ -399,7 +392,6 @@ public function isVerified(): bool /** * Verify user's email - * @return bool */ public function verifyEmail(): bool { diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index 9a5f0c7..ac13cc0 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -24,7 +24,6 @@ trait UsesRoles * Assign new role to user * * @param string|string[] $role The role to assign - * @return bool */ public function assign($role): bool { @@ -69,7 +68,6 @@ public function assign($role): bool /** * Check if user has a permission * @param string|string[] $permission The permission(s) to check - * @return bool */ public function can($permission): bool { @@ -92,7 +90,6 @@ public function cannot($permission): bool /** * Check if user has a role * @param string|string[] $role The role(s) to check - * @return bool */ public function is($role): bool { diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index f3c9b52..d500274 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -51,7 +51,6 @@ public function subscription(): ?array /** * Check if user has a subscription - * @return bool */ public function hasSubscription(): bool { @@ -63,8 +62,6 @@ public function hasSubscription(): bool * * Users on a trial, and users who cancelled but are still inside the * period they paid for (grace period), still count as active. - * - * @return bool */ public function hasActiveSubscription(): bool { @@ -89,7 +86,6 @@ public function hasActiveSubscription(): bool * subscription after the tier or not. * * @param string $plan The plan name or tier id to check - * @return bool */ public function isSubscribedTo(string $plan): bool { @@ -118,7 +114,6 @@ public function isSubscribedTo(string $plan): bool /** * Check if user is on a trial - * @return bool */ public function onTrial(): bool { @@ -137,7 +132,6 @@ public function onTrial(): bool /** * Check if user cancelled but still has access until the period ends - * @return bool */ public function onGracePeriod(): bool { @@ -154,7 +148,6 @@ public function onGracePeriod(): bool /** * Check if subscription payment is past due (renewal failed, in dunning) - * @return bool */ public function hasPastDueSubscription(): bool { @@ -169,7 +162,6 @@ public function hasPastDueSubscription(): bool * Pass false to cancel and revoke access immediately. * * @param bool $atPeriodEnd Cancel at period end instead of immediately - * @return bool */ public function cancelSubscription(bool $atPeriodEnd = true): bool { @@ -185,8 +177,6 @@ public function cancelSubscription(bool $atPeriodEnd = true): bool /** * Resume a subscription that was cancelled at period end * but has not run out yet. - * - * @return bool */ public function resumeSubscription(): bool { From 9d9f739d25cfe4a9ab0f21bb7d2ea8768767a675 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 20 Oct 2026 21:50:03 -0400 Subject: [PATCH 12/46] refactor(auth): remove redundant count check for hidden fields Remove the explicit count check before iterating over hidden fields. PHP's foreach handles empty arrays natively, making the check unnecessary. This simplifies the logic and reduces nesting levels. --- src/Auth/User.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index 0686b6b..7135ab4 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -428,19 +428,17 @@ public function get() unset($userData[$passwordKey], $userData['remember_token']); - if (count($hidden) > 0) { - foreach ($hidden as $item) { - if (array_key_exists($item, $userData)) { - unset($userData[$item]); - } + foreach ($hidden as $item) { + if (array_key_exists($item, $userData)) { + unset($userData[$item]); + } - if ($item === 'field.id' && array_key_exists($idKey, $userData)) { - unset($userData[$idKey]); - } + if ($item === 'field.id' && array_key_exists($idKey, $userData)) { + unset($userData[$idKey]); + } - if ($item === 'field.password' && array_key_exists($passwordKey, $userData)) { - unset($userData[$passwordKey]); - } + if ($item === 'field.password' && array_key_exists($passwordKey, $userData)) { + unset($userData[$passwordKey]); } } From 4f2e12f6fb60bfd141019c5226d8e4c311fd4fb0 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 20 Oct 2026 21:50:31 -0400 Subject: [PATCH 13/46] test: remove redundant try-catch block in login test setup Remove the try-catch block that only re-throws exceptions to simplify the code and allow the test runner to handle errors naturally. --- tests/login.test.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/login.test.php b/tests/login.test.php index 1b7a6a8..dc04432 100644 --- a/tests/login.test.php +++ b/tests/login.test.php @@ -5,18 +5,14 @@ beforeAll(function () { createTableForUsers(); - try { - dbInstance() - ->insert('users') - ->params([ - 'username' => 'test-user', - 'email' => 'test-user@example.com', - 'password' => password_hash('password', PASSWORD_BCRYPT) - ]) - ->execute(); - } catch (Throwable $th) { - throw $th; - } + dbInstance() + ->insert('users') + ->params([ + 'username' => 'test-user', + 'email' => 'test-user@example.com', + 'password' => password_hash('password', PASSWORD_BCRYPT) + ]) + ->execute(); }); afterAll(function () { From a457e52e0d474878b4050f116d1dbbe2bf429f57 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 20 Oct 2026 21:51:08 -0400 Subject: [PATCH 14/46] refactor(tests): remove unused constructor in FakeBillingDriver Remove the empty __construct method from the FakeBillingDriver class to clean up the test mock as it performs no initialization logic. --- tests/subscriptions.test.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/subscriptions.test.php b/tests/subscriptions.test.php index af6056b..97e3ad2 100644 --- a/tests/subscriptions.test.php +++ b/tests/subscriptions.test.php @@ -19,10 +19,6 @@ class FakeBillingDriver public static $cancelled = []; public static $resumed = []; - public function __construct($config = []) - { - } - public function tier(string $id): ?array { return SUBSCRIPTION_TIERS[$id] ?? null; From f3f5f077cb2b1e89aa8abb76e28a600ede32b15d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 20 Oct 2026 21:51:38 -0400 Subject: [PATCH 15/46] test: remove redundant try-catch in user test setup Remove the try-catch block in the beforeAll hook that was simply rethrowing exceptions. This simplifies the database initialization logic for tests without changing the error handling behavior. --- tests/user.test.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/user.test.php b/tests/user.test.php index 2283c83..79c7200 100644 --- a/tests/user.test.php +++ b/tests/user.test.php @@ -7,18 +7,14 @@ createTableForUsers(); dbInstance()->delete('users')->execute(); - try { - dbInstance() - ->insert('users') - ->params([ - 'username' => 'test-user', - 'email' => 'test-user@example.com', - 'password' => password_hash('password', PASSWORD_BCRYPT) - ]) - ->execute(); - } catch (Throwable $th) { - throw $th; - } + dbInstance() + ->insert('users') + ->params([ + 'username' => 'test-user', + 'email' => 'test-user@example.com', + 'password' => password_hash('password', PASSWORD_BCRYPT) + ]) + ->execute(); }); afterAll(function () { From db54f1dba241eadaf8d277939e47557271e21955 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 20 Oct 2026 21:51:59 -0400 Subject: [PATCH 16/46] test: remove unused variable from logout callback Remove the unused $testUser variable from the logout closure in tests/user.test.php to clean up the test code and avoid unnecessary variable capturing. --- tests/user.test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/user.test.php b/tests/user.test.php index 79c7200..bd2bed4 100644 --- a/tests/user.test.php +++ b/tests/user.test.php @@ -62,7 +62,7 @@ expect($auth->user())->toBeInstanceOf(User::class); expect($auth->user()->username)->toBe($testUser['username']); - $auth->logout(function ($auth) use ($testUser) { + $auth->logout(function ($auth) { expect($auth)->toBeInstanceOf(Auth::class); }); From 89611e4c2e6a09618516e58d8b7ecfe478020ccd Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:01:48 -0400 Subject: [PATCH 17/46] refactor: remove phpstan-baseline.neon Delete the baseline file to enforce strict type checking and address previously ignored errors in Auth and User classes. --- phpstan-baseline.neon | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 93867b4..e69de29 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,19 +0,0 @@ -parameters: - ignoreErrors: - - - rawMessage: 'Method Leaf\Auth::db() should return Leaf\Db but returns Leaf\Db|null.' - identifier: return.type - count: 1 - path: src/Auth.php - - - - rawMessage: 'Parameter #1 $db of method Leaf\Auth\User::setDb() expects Leaf\Db, Leaf\Db|null given.' - identifier: argument.type - count: 1 - path: src/Auth.php - - - - rawMessage: 'Offset ''status'' might not exist on array|null.' - identifier: offsetAccess.notFound - count: 2 - path: src/Auth/User.php From 2d465bd3da8664a43f8d66981c85ea4eca619fb6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:04:16 -0400 Subject: [PATCH 18/46] style: fix PSR-12 coding standard violations in closures Add missing spaces after the 'fn' keyword in anonymous functions to adhere to project coding standards and improve readability. --- src/Auth/UsesRoles.php | 2 +- src/functions.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index ac13cc0..588874f 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -161,7 +161,7 @@ protected function unknownRoles($roles): array return array_values(array_filter( is_string($roles) ? [$roles] : $roles, - fn($role) => !array_key_exists($role, $registered) + fn ($role) => !array_key_exists($role, $registered) )); } diff --git a/src/functions.php b/src/functions.php index 1db27a7..7e6c36f 100644 --- a/src/functions.php +++ b/src/functions.php @@ -13,7 +13,7 @@ function auth() { if (!(Config::getStatic('auth'))) { - Config::singleton('auth', fn() => new Auth()); + Config::singleton('auth', fn () => new Auth()); } return Config::get('auth'); From f22c6a60f2f847eee3e2798cd0be39bbd194cdb2 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:06:03 -0400 Subject: [PATCH 19/46] refactor: preserve exception stack traces and codes Update exception handling across Auth and User classes to pass the original exception code and the previous exception instance to the new Exception constructor. This ensures that stack traces are not lost during re-throwing. Additionally, add 'code-quality' to the alchemy.yml configuration to enforce better static analysis standards. --- alchemy.yml | 1 + src/Auth.php | 12 ++++++------ src/Auth/User.php | 6 +++--- tests/Pest.php | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/alchemy.yml b/alchemy.yml index c372e82..16a46ee 100644 --- a/alchemy.yml +++ b/alchemy.yml @@ -44,6 +44,7 @@ refactor: import-names: true sets: - dead-code + - code-quality analyse: level: 8 diff --git a/src/Auth.php b/src/Auth.php index 85aff0e..5718c17 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -318,7 +318,7 @@ public function login(array $credentials): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } if ($passwordKey !== false) { @@ -388,7 +388,7 @@ public function register(array $userData): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } $user = $this->db->select($table)->where($userData)->first(); @@ -461,7 +461,7 @@ public function update(array $userData): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } if (Config::get('session')) { @@ -523,7 +523,7 @@ public function updatePassword(string $oldPassword, string $newPassword): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } $this->user->{$passwordKey} = $newPassword; @@ -636,7 +636,7 @@ public function createUserFor($userData) return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } $user = $this->db->select($table)->where($userData)->first(); @@ -734,7 +734,7 @@ public function user() return null; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } return $this->user = (new User( diff --git a/src/Auth/User.php b/src/Auth/User.php index 7135ab4..5120585 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -188,7 +188,7 @@ public function update(array $userData): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } if (Config::get('session')) { @@ -248,7 +248,7 @@ public function updatePassword(string $oldPassword, string $newPassword): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } $this->data[$passwordKey] = $newPassword; @@ -289,7 +289,7 @@ public function resetPassword(string $newPassword): bool return false; } } catch (Throwable $th) { - throw new Exception($th->getMessage()); + throw new Exception($th->getMessage(), $th->getCode(), $th); } $this->data[$passwordKey] = $newPassword; diff --git a/tests/Pest.php b/tests/Pest.php index 35b2f50..7db8969 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -114,6 +114,6 @@ function createTableForUsers($table = 'users', bool $uuid = false): void $db->query($sql)->execute(); } catch (Throwable $th) { - throw new Exception('Failed to create table for users: ' . $th->getMessage()); + throw new Exception('Failed to create table for users: ' . $th->getMessage(), $th->getCode(), $th); } } From 800a2735a7f5ea9197eeebb315f26c5fa03e0b0d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:06:44 -0400 Subject: [PATCH 20/46] refactor(Auth): replace double negation with explicit boolean cast Use (bool) instead of !! for better readability and to clearly express the intent of converting the email verification timestamp to a boolean value. --- src/Auth/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index 5120585..6c57a46 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -387,7 +387,7 @@ public function generateVerificationToken($expiresIn = null, ?string $purpose = */ public function isVerified(): bool { - return !!($this->data['email_verified_at'] ?? false); + return (bool) ($this->data['email_verified_at'] ?? false); } /** From 0251ee5912245506fa67170eda1cacbe228b8b7b Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:06:55 -0400 Subject: [PATCH 21/46] refactor(functions.php): enable strict typing Add declare(strict_types=1) to ensure type safety and prevent implicit type coercion, improving code reliability and maintainability. --- src/functions.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/functions.php b/src/functions.php index 7e6c36f..31da20e 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,5 +1,7 @@ Date: Wed, 21 Oct 2026 00:08:35 -0400 Subject: [PATCH 22/46] refactor: rename catch block variables from $th to $throwable Improve code readability and maintainability by using descriptive variable names in catch blocks. Additionally, add 'coding-style' to the alchemy analysis configuration to enforce these standards. --- alchemy.yml | 1 + src/Auth.php | 32 ++++++++++++++++---------------- src/Auth/User.php | 12 ++++++------ tests/Pest.php | 4 ++-- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/alchemy.yml b/alchemy.yml index 16a46ee..e56c37e 100644 --- a/alchemy.yml +++ b/alchemy.yml @@ -45,6 +45,7 @@ refactor: sets: - dead-code - code-quality + - coding-style analyse: level: 8 diff --git a/src/Auth.php b/src/Auth.php index 5718c17..39995e6 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -317,8 +317,8 @@ public function login(array $credentials): bool $this->errorsArray['auth'] = Config::get('messages.loginParamsError'); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } if ($passwordKey !== false) { @@ -387,8 +387,8 @@ public function register(array $userData): bool $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } $user = $this->db->select($table)->where($userData)->first(); @@ -460,8 +460,8 @@ public function update(array $userData): bool $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } if (Config::get('session')) { @@ -522,8 +522,8 @@ public function updatePassword(string $oldPassword, string $newPassword): bool $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } $this->user->{$passwordKey} = $newPassword; @@ -635,8 +635,8 @@ public function createUserFor($userData) $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } $user = $this->db->select($table)->where($userData)->first(); @@ -733,8 +733,8 @@ public function user() $this->errorsArray = $this->db->errors(); return null; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } return $this->user = (new User( @@ -898,8 +898,8 @@ public function parseToken() $bearerToken, new Key(Config::tokenSecret(), 'HS256') ); - } catch (Throwable $th) { - $this->errorsArray['token'] = $th->getMessage(); + } catch (Throwable $throwable) { + $this->errorsArray['token'] = $throwable->getMessage(); return null; } } @@ -940,8 +940,8 @@ public function verifyToken(string $token, ?string $purpose = null) } return $user; - } catch (Throwable $th) { - $this->errorsArray['token'] = $th->getMessage(); + } catch (Throwable $throwable) { + $this->errorsArray['token'] = $throwable->getMessage(); return null; } } diff --git a/src/Auth/User.php b/src/Auth/User.php index 6c57a46..25e2698 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -187,8 +187,8 @@ public function update(array $userData): bool $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } if (Config::get('session')) { @@ -247,8 +247,8 @@ public function updatePassword(string $oldPassword, string $newPassword): bool $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } $this->data[$passwordKey] = $newPassword; @@ -288,8 +288,8 @@ public function resetPassword(string $newPassword): bool $this->errorsArray = array_merge($this->errorsArray, $this->db->errors()); return false; } - } catch (Throwable $th) { - throw new Exception($th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception($throwable->getMessage(), $throwable->getCode(), $throwable); } $this->data[$passwordKey] = $newPassword; diff --git a/tests/Pest.php b/tests/Pest.php index 7db8969..715efe4 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -113,7 +113,7 @@ function createTableForUsers($table = 'users', bool $uuid = false): void }; $db->query($sql)->execute(); - } catch (Throwable $th) { - throw new Exception('Failed to create table for users: ' . $th->getMessage(), $th->getCode(), $th); + } catch (Throwable $throwable) { + throw new Exception('Failed to create table for users: ' . $throwable->getMessage(), $throwable->getCode(), $throwable); } } From ae4afe120afa26ef95652064d1bd88d172f33bfd Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:08:44 -0400 Subject: [PATCH 23/46] style(tests): add missing newline for better readability Add a blank line between static properties in FakeBillingDriver to adhere to PSR coding standards and improve code structure. --- tests/subscriptions.test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/subscriptions.test.php b/tests/subscriptions.test.php index 97e3ad2..457a3d7 100644 --- a/tests/subscriptions.test.php +++ b/tests/subscriptions.test.php @@ -17,6 +17,7 @@ class FakeBillingDriver { public static $cancelled = []; + public static $resumed = []; public function tier(string $id): ?array From bce8d1297ae2a11348f37084ef18bad9b38e6219 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:14:22 -0400 Subject: [PATCH 24/46] refactor: add explicit void return type declarations Enforce strict return types across the codebase to improve code predictability and satisfy static analysis requirements. This change updates middleware callbacks, class methods, and test closures to explicitly declare a void return type. --- alchemy.yml | 1 + src/Auth.php | 38 ++++++++++++++++------------------ tests/Pest.php | 2 +- tests/login.test.php | 14 ++++++------- tests/register.test.php | 12 +++++------ tests/roles.test.php | 40 ++++++++++++++++++------------------ tests/session.test.php | 22 ++++++++++---------- tests/subscriptions.test.php | 24 +++++++++++----------- tests/table.test.php | 12 +++++------ tests/tokens.test.php | 24 +++++++++++----------- tests/update.test.php | 12 +++++------ tests/user.test.php | 10 ++++----- 12 files changed, 105 insertions(+), 106 deletions(-) diff --git a/alchemy.yml b/alchemy.yml index e56c37e..8900a2b 100644 --- a/alchemy.yml +++ b/alchemy.yml @@ -46,6 +46,7 @@ refactor: - dead-code - code-quality - coding-style + - type-declarations analyse: level: 8 diff --git a/src/Auth.php b/src/Auth.php index 39995e6..606bb68 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -50,15 +50,15 @@ class Auth public function __construct() { if (class_exists(App::class)) { - $this->middleware('auth.required', function () { + $this->middleware('auth.required', function (): void { response()->redirect(Config::get('redirect.login') ?? '/auth/login'); }); - $this->middleware('auth.guest', function () { + $this->middleware('auth.guest', function (): void { response()->redirect(Config::get('redirect.guest') ?? '/dashboard'); }); - $this->middleware('is', function ($role) { + $this->middleware('is', function ($role): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -66,7 +66,7 @@ public function __construct() ); }); - $this->middleware('isNot', function () { + $this->middleware('isNot', function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -74,7 +74,7 @@ public function __construct() ); }); - $this->middleware('can', function () { + $this->middleware('can', function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -82,7 +82,7 @@ public function __construct() ); }); - $this->middleware('cannot', function () { + $this->middleware('cannot', function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -90,11 +90,11 @@ public function __construct() ); }); - $this->middleware('auth.verified', function () { + $this->middleware('auth.verified', function (): void { response()->redirect('/auth/verify'); }); - $this->middleware('auth.unverified', function () { + $this->middleware('auth.unverified', function (): void { response()->redirect('/dashboard'); }); } @@ -779,17 +779,16 @@ public function tokens() * Register auth middleware for your Leaf apps * @param string $middleware The middleware to register * @param callable $callback The callback to run if middleware fails - * @return void|never * @throws Exception If not used with leafs/leaf installed */ - public function middleware(string $middleware, callable $callback) + public function middleware(string $middleware, callable $callback): void { if (!class_exists(App::class)) { throw new Exception('This feature is only available for Leaf apps'); } if ($middleware === 'auth.required') { - app()->registerMiddleware('auth.required', function () use ($callback) { + app()->registerMiddleware('auth.required', function () use ($callback): void { if (!$this->user()) { $callback(); exit; @@ -800,7 +799,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'auth.guest') { - app()->registerMiddleware('auth.guest', function () use ($callback) { + app()->registerMiddleware('auth.guest', function () use ($callback): void { if ($this->user()) { $callback(); exit; @@ -813,7 +812,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'is') { - app()->registerMiddleware('is', function ($role) use ($callback) { + app()->registerMiddleware('is', function ($role) use ($callback): void { if (!$this->user() || $this->user()->isNot($role)) { $callback($role); exit; @@ -824,7 +823,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'isNot') { - app()->registerMiddleware('isNot', function ($role) use ($callback) { + app()->registerMiddleware('isNot', function ($role) use ($callback): void { if (!$this->user() || $this->user()->is($role)) { $callback($role); exit; @@ -835,7 +834,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'can') { - app()->registerMiddleware('can', function ($role) use ($callback) { + app()->registerMiddleware('can', function ($role) use ($callback): void { if (!$this->user() || $this->user()->cannot($role)) { $callback($role); exit; @@ -846,7 +845,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'cannot') { - app()->registerMiddleware('cannot', function ($role) use ($callback) { + app()->registerMiddleware('cannot', function ($role) use ($callback): void { if (!$this->user() || $this->user()->can($role)) { $callback($role); exit; @@ -857,7 +856,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'auth.verified') { - app()->registerMiddleware('auth.verified', function () use ($callback) { + app()->registerMiddleware('auth.verified', function () use ($callback): void { if (!$this->user() || !$this->user()->isVerified()) { $callback(); exit; @@ -868,7 +867,7 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'auth.unverified') { - app()->registerMiddleware('auth.unverified', function () use ($callback) { + app()->registerMiddleware('auth.unverified', function () use ($callback): void { if (!$this->user() || $this->user()->isVerified()) { $callback(); exit; @@ -1049,9 +1048,8 @@ protected function getTokenFromSession() /** * Clear all errors caught - * @return void */ - public function clearErrors() + public function clearErrors(): void { $this->errorsArray = []; } diff --git a/tests/Pest.php b/tests/Pest.php index 715efe4..043d4f7 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -66,7 +66,7 @@ function authInstance(): Auth return $auth; } -function deleteUser(string $username, $table = 'users') +function deleteUser(string $username, $table = 'users'): void { $db = dbInstance(); diff --git a/tests/login.test.php b/tests/login.test.php index dc04432..009e65b 100644 --- a/tests/login.test.php +++ b/tests/login.test.php @@ -2,7 +2,7 @@ use Leaf\Auth\User; -beforeAll(function () { +beforeAll(function (): void { createTableForUsers(); dbInstance() @@ -15,11 +15,11 @@ ->execute(); }); -afterAll(function () { +afterAll(function (): void { dbInstance()->delete('users')->execute(); }); -test('user can login', function () { +test('user can login', function (): void { $auth = authInstance(); $testUser = [ @@ -35,7 +35,7 @@ expect($auth->user()->username)->toBe($testUser['username']); }); -test('login generates tokens on success', function () { +test('login generates tokens on success', function (): void { $auth = authInstance(); $testUser = [ @@ -52,7 +52,7 @@ expect($auth->data()->refreshToken)->toBeString(); }); -test('login fails with incorrect password', function () { +test('login fails with incorrect password', function (): void { $auth = authInstance(); $userData = [ @@ -69,7 +69,7 @@ expect($auth->errors()['password'] ?? null)->toBe($loginPasswordError); }); -test('login should fail if user does not exist', function () { +test('login should fail if user does not exist', function (): void { $auth = authInstance(); $userData = [ @@ -86,7 +86,7 @@ expect($auth->errors()['auth'] ?? null)->toBe($loginParamsError); }); -test('login should work without password is password.key is false', function () { +test('login should work without password is password.key is false', function (): void { $auth = authInstance(); $auth->config('password.key', false); diff --git a/tests/register.test.php b/tests/register.test.php index d87cb91..9ca5591 100644 --- a/tests/register.test.php +++ b/tests/register.test.php @@ -2,16 +2,16 @@ use Leaf\Auth\User; -beforeAll(function () { +beforeAll(function (): void { createTableForUsers(); dbInstance()->delete('users')->execute(); }); -afterEach(function () { +afterEach(function (): void { dbInstance()->delete('users')->execute(); }); -test('user can register an account', function () { +test('user can register an account', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -32,7 +32,7 @@ expect($auth->user()->username)->toBe($userData['username']); }); -test('user can login after registering', function () { +test('user can login after registering', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -53,7 +53,7 @@ expect($auth->user()->username)->toBe($userData['username']); }); -test('user can only sign up once', function () { +test('user can only sign up once', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -81,7 +81,7 @@ ]); }); -test('register passwords are encrypted', function () { +test('register passwords are encrypted', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); diff --git a/tests/roles.test.php b/tests/roles.test.php index 42dec81..6916950 100644 --- a/tests/roles.test.php +++ b/tests/roles.test.php @@ -23,15 +23,15 @@ function roleAuthInstance(): Auth return $auth; } -beforeAll(function () { +beforeAll(function (): void { createTableForUsers('role_users'); }); -afterAll(function () { +afterAll(function (): void { dbInstance()->delete('role_users')->execute(); }); -beforeEach(function () { +beforeEach(function (): void { deleteUser('role-user', 'role_users'); $this->auth = roleAuthInstance(); @@ -42,11 +42,11 @@ function roleAuthInstance(): Auth ]); }); -test('createRoles registers roles and their permissions', function () { +test('createRoles registers roles and their permissions', function (): void { expect($this->auth->roles())->toBe(ROLES); }); -test('a fresh user has no roles or permissions', function () { +test('a fresh user has no roles or permissions', function (): void { $user = $this->auth->user(); expect($user->roles())->toBe([]); @@ -55,7 +55,7 @@ function roleAuthInstance(): Auth expect($user->can('view-ticket'))->toBeFalse(); }); -test('assign gives a user a role and its permissions', function () { +test('assign gives a user a role and its permissions', function (): void { $user = $this->auth->user(); expect($user->assign('admin'))->toBeTrue(); @@ -66,7 +66,7 @@ function roleAuthInstance(): Auth expect($user->cannot('nonexistent-permission'))->toBeTrue(); }); -test('assign accepts multiple roles and merges permissions without duplicates', function () { +test('assign accepts multiple roles and merges permissions without duplicates', function (): void { $user = $this->auth->user(); $user->assign(['support', 'guest']); @@ -75,7 +75,7 @@ function roleAuthInstance(): Auth expect($user->permissions())->toBe(['view-ticket', 'edit-ticket']); }); -test('assigning an unknown role fails loudly instead of silently doing nothing', function () { +test('assigning an unknown role fails loudly instead of silently doing nothing', function (): void { $user = $this->auth->user(); $errors = []; @@ -95,7 +95,7 @@ function roleAuthInstance(): Auth expect($user->permissions())->toBe([]); }); -test('a known role mixed with an unknown one assigns nothing', function () { +test('a known role mixed with an unknown one assigns nothing', function (): void { $user = $this->auth->user(); set_error_handler(fn () => true); @@ -106,7 +106,7 @@ function roleAuthInstance(): Auth expect($user->roles())->toBe([]); }); -test('is and can accept arrays and match any entry', function () { +test('is and can accept arrays and match any entry', function (): void { $user = $this->auth->user(); $user->assign('support'); @@ -116,7 +116,7 @@ function roleAuthInstance(): Auth expect($user->can(['delete-ticket']))->toBeFalse(); }); -test('roles persist and rehydrate on a fresh login', function () { +test('roles persist and rehydrate on a fresh login', function (): void { $this->auth->user()->assign('support'); $freshAuth = roleAuthInstance(); @@ -132,7 +132,7 @@ function roleAuthInstance(): Auth expect($user->can('delete-ticket'))->toBeFalse(); }); -test('unassign revokes a role and recalculates permissions', function () { +test('unassign revokes a role and recalculates permissions', function (): void { $user = $this->auth->user(); $user->assign(['admin', 'guest']); @@ -163,7 +163,7 @@ class MiddlewareDenied extends Exception function registerRoleMiddleware(Auth $auth): void { foreach (['is', 'isNot', 'can', 'cannot'] as $name) { - $auth->middleware($name, function () use ($name) { + $auth->middleware($name, function () use ($name): void { throw new MiddlewareDenied($name); }); } @@ -176,21 +176,21 @@ function runRoleMiddleware(string $name, $args) return $registered->getValue()[$name](is_array($args) ? $args : [$args]); } -test('is middleware allows a user holding the role', function () { +test('is middleware allows a user holding the role', function (): void { $this->auth->user()->assign('admin'); registerRoleMiddleware($this->auth); expect(fn () => runRoleMiddleware('is', ['admin']))->not->toThrow(MiddlewareDenied::class); }); -test('is middleware blocks a user without the role', function () { +test('is middleware blocks a user without the role', function (): void { $this->auth->user()->assign('guest'); registerRoleMiddleware($this->auth); expect(fn () => runRoleMiddleware('is', ['admin']))->toThrow(MiddlewareDenied::class); }); -test('is middleware allows any role in a piped list', function () { +test('is middleware allows any role in a piped list', function (): void { $this->auth->user()->assign('support'); registerRoleMiddleware($this->auth); @@ -198,28 +198,28 @@ function runRoleMiddleware(string $name, $args) expect(fn () => runRoleMiddleware('is', ['admin', 'support']))->not->toThrow(MiddlewareDenied::class); }); -test('can middleware allows a permission the role grants', function () { +test('can middleware allows a permission the role grants', function (): void { $this->auth->user()->assign('support'); registerRoleMiddleware($this->auth); expect(fn () => runRoleMiddleware('can', ['edit-ticket']))->not->toThrow(MiddlewareDenied::class); }); -test('can middleware blocks a permission the role does not grant', function () { +test('can middleware blocks a permission the role does not grant', function (): void { $this->auth->user()->assign('support'); registerRoleMiddleware($this->auth); expect(fn () => runRoleMiddleware('can', ['delete-ticket']))->toThrow(MiddlewareDenied::class); }); -test('isNot middleware blocks a user holding the role', function () { +test('isNot middleware blocks a user holding the role', function (): void { $this->auth->user()->assign('admin'); registerRoleMiddleware($this->auth); expect(fn () => runRoleMiddleware('isNot', ['admin']))->toThrow(MiddlewareDenied::class); }); -test('cannot middleware blocks a user holding the permission', function () { +test('cannot middleware blocks a user holding the permission', function (): void { $this->auth->user()->assign('admin'); registerRoleMiddleware($this->auth); diff --git a/tests/session.test.php b/tests/session.test.php index 9eefe62..2ff2eea 100644 --- a/tests/session.test.php +++ b/tests/session.test.php @@ -2,23 +2,23 @@ use Leaf\Auth\User; -beforeAll(function () { +beforeAll(function (): void { createTableForUsers(); dbInstance()->delete('users')->execute(); }); -afterEach(function () { +afterEach(function (): void { if (session_status() === PHP_SESSION_ACTIVE) { $_SESSION = []; session_destroy(); } }); -afterAll(function () { +afterAll(function (): void { dbInstance()->delete('users')->execute(); }); -test('register should create a new session when session => true', function () { +test('register should create a new session when session => true', function (): void { $auth = authInstance(); $auth->config(['session' => true]); $auth->config(['db.table' => 'users']); @@ -39,7 +39,7 @@ expect($_SESSION['auth']['user']['username'] ?? null)->toBe($userData['username']); }); -test('register should not create a new session when session => false', function () { +test('register should not create a new session when session => false', function (): void { $auth = authInstance(); $auth->config(['session' => false]); $auth->config(['db.table' => 'users']); @@ -59,7 +59,7 @@ expect($_SESSION['auth']['user']['username'] ?? null)->toBeNull(); }); -test('login should create session when session => true', function () { +test('login should create session when session => true', function (): void { $auth = authInstance(); $auth->config(['session' => true]); $auth->config(['db.table' => 'users']); @@ -80,7 +80,7 @@ expect($_SESSION['auth']['user']['username'] ?? null)->toBe($userData['username']); }); -test('session should create auth.ttl when session.lifetime is not 0', function () { +test('session should create auth.ttl when session.lifetime is not 0', function (): void { $auth = authInstance(); $auth->config(['session' => true, 'session.lifetime' => 2]); $auth->config(['db.table' => 'users']); @@ -105,7 +105,7 @@ expect($_SESSION['auth']['ttl'])->toBeGreaterThan($timeBeforeLogin); }); -test('session should not create auth.ttl when session.lifetime is 0', function () { +test('session should not create auth.ttl when session.lifetime is 0', function (): void { $auth = authInstance(); $auth->config(['session' => true, 'session.lifetime' => 0]); $auth->config(['db.table' => 'users']); @@ -128,7 +128,7 @@ expect($_SESSION['auth']['ttl'] ?? null)->toBeNull(); }); -test('session should expire after session.lifetime', function () { +test('session should expire after session.lifetime', function (): void { $auth = authInstance(); $auth->config(['session' => true, 'session.lifetime' => 2]); $auth->config(['db.table' => 'users']); @@ -154,7 +154,7 @@ expect($auth->user())->toBeNull(); }); -test('login should regenerate session id when session => true and session is already active', function () { +test('login should regenerate session id when session => true and session is already active', function (): void { $auth = authInstance(); $auth->config(['session' => true]); $auth->config(['db.table' => 'users']); @@ -183,7 +183,7 @@ expect($newSessionId)->not()->toBe($sessionId); }); -test('logout should remove auth info from session when session => true', function () { +test('logout should remove auth info from session when session => true', function (): void { $auth = authInstance(); $auth->config(['session' => true]); $auth->config(['db.table' => 'users']); diff --git a/tests/subscriptions.test.php b/tests/subscriptions.test.php index 457a3d7..dd5767a 100644 --- a/tests/subscriptions.test.php +++ b/tests/subscriptions.test.php @@ -84,7 +84,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void ], $overrides))->execute(); } -beforeAll(function () { +beforeAll(function (): void { // point the db() singleton (leafs/db ships it since leaf core is a // transitive dev dep) at the shared test connection db()->connection(dbInstance()->connection()); @@ -110,14 +110,14 @@ function seedUserSubscription(int $userId, array $overrides = []): void createTableForUsers(); }); -beforeEach(function () { +beforeEach(function (): void { dbInstance()->delete('subscriptions')->execute(); deleteUser('sub-user'); FakeBillingDriver::$cancelled = []; FakeBillingDriver::$resumed = []; }); -test('a user with no subscription rows has no subscription', function () { +test('a user with no subscription rows has no subscription', function (): void { $user = subscriptionUser(); expect($user->subscription())->toBeNull(); @@ -125,7 +125,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->hasActiveSubscription())->toBeFalse(); }); -test('an active subscription is returned with its tier attached', function () { +test('an active subscription is returned with its tier attached', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id()); @@ -133,7 +133,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->subscription()['tier']['name'])->toBe('Basic'); }); -test('the latest subscription wins after a resubscribe', function () { +test('the latest subscription wins after a resubscribe', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), [ 'subscription_id' => 'sub_old', @@ -146,7 +146,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->hasActiveSubscription())->toBeTrue(); }); -test('a cancelled subscription keeps access through the grace period', function () { +test('a cancelled subscription keeps access through the grace period', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), [ 'status' => Subscription::STATUS_CANCELLED, @@ -158,7 +158,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->hasSubscription())->toBeFalse(); }); -test('a cancelled subscription past its end date has no access', function () { +test('a cancelled subscription past its end date has no access', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), [ 'status' => Subscription::STATUS_CANCELLED, @@ -169,7 +169,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->hasActiveSubscription())->toBeFalse(); }); -test('trial state is reported from status or trial_ends_at', function () { +test('trial state is reported from status or trial_ends_at', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), [ 'status' => Subscription::STATUS_TRIAL, @@ -180,7 +180,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->hasActiveSubscription())->toBeTrue(); }); -test('past due subscriptions lose access but are flagged for dunning', function () { +test('past due subscriptions lose access but are flagged for dunning', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), ['status' => Subscription::STATUS_PAST_DUE]); @@ -188,7 +188,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->hasActiveSubscription())->toBeFalse(); }); -test('cancelSubscription defaults to period end and can cancel immediately', function () { +test('cancelSubscription defaults to period end and can cancel immediately', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id()); @@ -199,7 +199,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect(FakeBillingDriver::$cancelled[1])->toBe(['sub_1', false]); }); -test('resumeSubscription only works during the grace period', function () { +test('resumeSubscription only works during the grace period', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), [ 'status' => Subscription::STATUS_CANCELLED, @@ -210,7 +210,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect(FakeBillingDriver::$resumed)->toBe(['sub_1']); }); -test('resumeSubscription refuses when the subscription is fully over', function () { +test('resumeSubscription refuses when the subscription is fully over', function (): void { $user = subscriptionUser(); seedUserSubscription($user->id(), [ 'status' => Subscription::STATUS_CANCELLED, diff --git a/tests/table.test.php b/tests/table.test.php index 4429e7c..2649676 100644 --- a/tests/table.test.php +++ b/tests/table.test.php @@ -1,17 +1,17 @@ delete('myusers')->execute(); }); -test('register should save user in user defined table', function () { +test('register should save user in user defined table', function (): void { $auth = authInstance(); $auth->config(['session' => false, 'db.table' => 'myusers']); @@ -28,7 +28,7 @@ expect($auth->user()->username)->toBe('test-user'); }); -test('login should work with user defined table', function () { +test('login should work with user defined table', function (): void { $auth = authInstance(); $auth->config(['session' => false, 'db.table' => 'myusers']); @@ -45,7 +45,7 @@ expect($auth->user()->username)->toBe('test-user'); }); -test('update should work with user defined table', function () { +test('update should work with user defined table', function (): void { $auth = authInstance(); $auth->config(['session' => true, 'db.table' => 'myusers', 'session.lifetime' => '1 day']); @@ -72,7 +72,7 @@ expect($auth->user()->email)->toBe('test-user55@example.com'); }); -test('user table can use uuid as id', function () { +test('user table can use uuid as id', function (): void { createTableForUsers('uuid_users', uuid: true); $auth = authInstance(); diff --git a/tests/tokens.test.php b/tests/tokens.test.php index c08b81f..46a8285 100644 --- a/tests/tokens.test.php +++ b/tests/tokens.test.php @@ -14,17 +14,17 @@ | */ -afterEach(function () { +afterEach(function (): void { // config is static — leave it as other suites expect Config::set(['token.secret' => null, 'token.lifetime' => 60 * 60 * 24 * 365]); unset($_ENV['APP_KEY'], $_ENV['AUTH_TOKEN_SECRET']); }); -test('the default token lifetime is a year, not zero', function () { +test('the default token lifetime is a year, not zero', function (): void { expect(Config::get('token.lifetime'))->toBe(60 * 60 * 24 * 365); }); -test('tokens are minted with a future expiry on default config', function () { +test('tokens are minted with a future expiry on default config', function (): void { $auth = authInstance(); $auth->createUserFor([ 'username' => 'token-check', @@ -45,7 +45,7 @@ deleteUser('token-check'); }); -test('the token secret derives from APP_KEY when nothing else is set', function () { +test('the token secret derives from APP_KEY when nothing else is set', function (): void { Config::set(['token.secret' => null]); $_ENV['APP_KEY'] = 'base64:test-app-key'; @@ -56,7 +56,7 @@ ->and(strlen($secret))->toBe(64); // long enough for HS256 everywhere }); -test('AUTH_TOKEN_SECRET beats the APP_KEY derivation', function () { +test('AUTH_TOKEN_SECRET beats the APP_KEY derivation', function (): void { Config::set(['token.secret' => null]); $_ENV['APP_KEY'] = 'base64:test-app-key'; $_ENV['AUTH_TOKEN_SECRET'] = 'explicit-secret-that-is-long-enough-for-hs256'; @@ -64,15 +64,15 @@ expect(Config::tokenSecret())->toBe('explicit-secret-that-is-long-enough-for-hs256'); }); -test('token operations throw clearly when no secret can be resolved', function () { +test('token operations throw clearly when no secret can be resolved', function (): void { Config::set(['token.secret' => null]); unset($_ENV['APP_KEY'], $_ENV['AUTH_TOKEN_SECRET']); - expect(fn () => Config::tokenSecret()) + expect(fn (): string => Config::tokenSecret()) ->toThrow(RuntimeException::class, 'No auth token secret'); }); -test('each operation starts with a clean error state', function () { +test('each operation starts with a clean error state', function (): void { $auth = authInstance(); $auth->createUserFor([ 'username' => 'error-state', @@ -103,7 +103,7 @@ deleteUser('error-state'); }); -test('a present-but-null roles column is hidden from get()', function () { +test('a present-but-null roles column is hidden from get()', function (): void { $auth = authInstance(); $user = new User([ 'id' => 1, @@ -114,7 +114,7 @@ expect($user->get())->not->toHaveKey(Config::get('roles.key')); }); -test('the password hash never leaks from get(), whatever hidden says', function () { +test('the password hash never leaks from get(), whatever hidden says', function (): void { // following the old docs (literal 'password') with a custom // password.key put the bcrypt hash in the session and Inertia props Config::set(['password.key' => 'pass', 'hidden' => ['password', 'remember_token']]); @@ -132,7 +132,7 @@ Config::set(['password.key' => 'password', 'hidden' => ['field.id', 'field.password', 'remember_token']]); }); -test('session-only users need no token secret until tokens are read', function () { +test('session-only users need no token secret until tokens are read', function (): void { Config::set(['token.secret' => null]); unset($_ENV['APP_KEY'], $_ENV['AUTH_TOKEN_SECRET']); @@ -146,7 +146,7 @@ ->toThrow(RuntimeException::class, 'No auth token secret'); }); -test('middleware redirect targets are configurable', function () { +test('middleware redirect targets are configurable', function (): void { expect(Config::get('redirect.login'))->toBe('/auth/login') ->and(Config::get('redirect.guest'))->toBe('/dashboard'); diff --git a/tests/update.test.php b/tests/update.test.php index 76b2286..5fb7373 100644 --- a/tests/update.test.php +++ b/tests/update.test.php @@ -1,6 +1,6 @@ config(['db.table' => 'users']); @@ -19,11 +19,11 @@ sleep(1); }); -afterAll(function () { +afterAll(function (): void { dbInstance()->delete('users')->execute(); }); -test('update should update user data', function () { +test('update should update user data', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -49,7 +49,7 @@ expect($auth->user()->username)->toBe($updateData['username']); }); -test('update should fail if user already exists', function () { +test('update should fail if user already exists', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -72,7 +72,7 @@ expect($auth->errors()['username'])->toBe('username already exists'); }); -test('updatePassword should update user password', function () { +test('updatePassword should update user password', function (): void { $auth = authInstance(); $auth->config(['unique' => ['username']]); $auth->config(['db.table' => 'users']); @@ -104,7 +104,7 @@ expect($auth->user()->{$auth->config('password.key')})->not()->toBe($oldPassword); }); -test('update should regenerate session id if session => true', function () { +test('update should regenerate session id if session => true', function (): void { $auth = authInstance(); $auth->config(['session' => true]); $auth->config(['db.table' => 'users']); diff --git a/tests/user.test.php b/tests/user.test.php index bd2bed4..f76a703 100644 --- a/tests/user.test.php +++ b/tests/user.test.php @@ -3,7 +3,7 @@ use Leaf\Auth; use Leaf\Auth\User; -beforeAll(function () { +beforeAll(function (): void { createTableForUsers(); dbInstance()->delete('users')->execute(); @@ -17,11 +17,11 @@ ->execute(); }); -afterAll(function () { +afterAll(function (): void { dbInstance()->delete('users')->execute(); }); -test('auth user is instance of Leaf\Auth\User', function () { +test('auth user is instance of Leaf\Auth\User', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -42,7 +42,7 @@ expect($auth->user()->username)->toBe($testUser['username']); }); -test('logout can use logout callback to run custom action', function () { +test('logout can use logout callback to run custom action', function (): void { $auth = authInstance(); $auth->config(['db.table' => 'users']); @@ -62,7 +62,7 @@ expect($auth->user())->toBeInstanceOf(User::class); expect($auth->user()->username)->toBe($testUser['username']); - $auth->logout(function ($auth) { + $auth->logout(function ($auth): void { expect($auth)->toBeInstanceOf(Auth::class); }); From 7827b1119330775ea92e3437c02c4a532639003c Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:15:21 -0400 Subject: [PATCH 25/46] refactor: use static return type hints for fluent interface methods Update method signatures across Auth and User classes to use the 'static' return type hint instead of docblock annotations. This improves type safety, enables better IDE autocompletion, and ensures consistency with modern PHP standards for fluent interfaces. --- src/Auth.php | 18 ++++++------------ src/Auth/User.php | 3 +-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 606bb68..1ef633e 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -147,9 +147,8 @@ private function env(string $name, $default = false) * dbtype?: string, * pdoOptions?: mixed[], * } $dbConfig Configuration for leaf db connection - * @return $this */ - public function connect($dbConfig = []) + public function connect($dbConfig = []): static { $this->db = new Db(); $this->db->connect($dbConfig); @@ -161,9 +160,8 @@ public function connect($dbConfig = []) * Connect to database using environment variables * * @param mixed[] $pdoOptions Options for PDO connection - * @return $this */ - public function autoConnect(array $pdoOptions = []) + public function autoConnect(array $pdoOptions = []): static { $this->db = new Db(); $this->db->autoConnect($pdoOptions); @@ -175,9 +173,8 @@ public function autoConnect(array $pdoOptions = []) * Pass in db connection instance directly * * @param PDO $connection A connection instance of your db - * @return $this */ - public function dbConnection(PDO $connection) + public function dbConnection(PDO $connection): static { $this->db = new Db(); $this->db->connection($connection); @@ -193,13 +190,12 @@ public function dbConnection(PDO $connection) * name?: string, * redirectUri?: string, * } $options If `$options['redirectUri']` is not set, it will default to the value of `APP_URL` (as resolved by this class's environment helper) with `/auth/google/callback` appended; if `APP_URL` is unset or empty, the default will be `/auth/google/callback`. - * @return static */ public function withGoogle( string $clientId, string $clientSecret, array $options = [] - ) { + ): static { $clientName = $options['name'] ?? 'google'; unset($options['name']); @@ -221,9 +217,8 @@ public function withGoogle( * Register a generic OAuth client to use with Leaf Auth, should be a league/oauth2-client compatible client. * @param string $clientName The name of the client to register * @param AbstractProvider $client An instance of a league/oauth2-client compatible client - * @return static */ - public function withProvider(string $clientName, $client) + public function withProvider(string $clientName, $client): static { $this->oauthClients[$clientName] = $client; return $this; @@ -263,9 +258,8 @@ public function config($config, $value = null) * Create roles and permissions * * @param array $roles Array of roles and their permissions - * @return Auth */ - public function createRoles(array $roles) + public function createRoles(array $roles): static { Config::set([ 'roles' => $roles, diff --git a/src/Auth/User.php b/src/Auth/User.php index 25e2698..1dcbd26 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -448,9 +448,8 @@ public function get() /** * Set user db instance * @param Db $db - * @return User */ - public function setDb($db) + public function setDb($db): static { $this->db = $db; From b9cb2b197a3204e7665f14b0e95ecba47a2f9bd1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:19:56 -0400 Subject: [PATCH 26/46] refactor: add explicit return types and parameter types to Auth classes Update method signatures across Auth, Model, and User classes to include native PHP type hints. This improves code readability, enables better static analysis, and ensures type safety throughout the authentication layer. --- src/Auth.php | 6 ++---- src/Auth/Config.php | 3 +-- src/Auth/Model.php | 6 ++---- src/Auth/User.php | 10 +++------- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 1ef633e..8956480 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -876,9 +876,8 @@ public function middleware(string $middleware, callable $callback): void /** * Parse the current user's token - * @return array|null */ - public function parseToken() + public function parseToken(): ?array { $bearerToken = $this->getTokenFromRequest(); @@ -1004,8 +1003,7 @@ protected function checkAndExpireSession(): bool return $isSessionExpired; } - /** @return ?string */ - protected function getTokenFromRequest() + protected function getTokenFromRequest(): ?string { $headers = null; diff --git a/src/Auth/Config.php b/src/Auth/Config.php index a8f4332..b6882aa 100644 --- a/src/Auth/Config.php +++ b/src/Auth/Config.php @@ -119,9 +119,8 @@ public static function get($key = null) /** * Set user cache * @param string $key - * @param mixed $value */ - public static function setUserCache($key, $value): void + public static function setUserCache($key, mixed $value): void { if (isset($_SESSION)) { $_SESSION[$key] = $value; diff --git a/src/Auth/Model.php b/src/Auth/Model.php index c97fb83..0fb4401 100644 --- a/src/Auth/Model.php +++ b/src/Auth/Model.php @@ -110,9 +110,8 @@ public function save(): ?PDOStatement /** * @param string $name - * @param mixed $value */ - public function __set($name, $value) + public function __set($name, mixed $value) { $this->dataToSave[$name] = $value; } @@ -120,9 +119,8 @@ public function __set($name, $value) /** * @param string $name * @param mixed[] $arguments - * @return mixed */ - public function __call($name, $arguments) + public function __call($name, $arguments): mixed { return $this->table()->$name(...$arguments); } diff --git a/src/Auth/User.php b/src/Auth/User.php index 1dcbd26..212c2db 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -417,8 +417,7 @@ public function verifyEmail(): bool } } - /** @return array */ - public function get() + public function get(): array { $userData = $this->data; @@ -475,9 +474,8 @@ public function __toString(): string /** * @param string $name - * @return ?mixed */ - public function __get($name) + public function __get($name): mixed { // using data instead of get() here because // we want people to be able to user()->get hidden fields @@ -487,10 +485,8 @@ public function __get($name) /** * @param string $name - * @param mixed $value - * @return void */ - public function __set($name, $value) + public function __set($name, mixed $value): void { $this->data[$name] = $value; } From c28d16624f9bc46415e26d24c4d7c9819541db84 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:24:57 -0400 Subject: [PATCH 27/46] refactor: add explicit return types to closures Add explicit return type hints to closures across the codebase to improve static analysis, code clarity, and ensure type safety for callback functions. --- src/Auth/UsesRoles.php | 2 +- src/functions.php | 2 +- tests/roles.test.php | 4 ++-- tests/tokens.test.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index 588874f..af2a3ca 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -161,7 +161,7 @@ protected function unknownRoles($roles): array return array_values(array_filter( is_string($roles) ? [$roles] : $roles, - fn ($role) => !array_key_exists($role, $registered) + fn ($role): bool => !array_key_exists($role, $registered) )); } diff --git a/src/functions.php b/src/functions.php index 31da20e..320efbe 100644 --- a/src/functions.php +++ b/src/functions.php @@ -15,7 +15,7 @@ function auth() { if (!(Config::getStatic('auth'))) { - Config::singleton('auth', fn () => new Auth()); + Config::singleton('auth', fn (): Auth => new Auth()); } return Config::get('auth'); diff --git a/tests/roles.test.php b/tests/roles.test.php index 6916950..60f19b6 100644 --- a/tests/roles.test.php +++ b/tests/roles.test.php @@ -79,7 +79,7 @@ function roleAuthInstance(): Auth $user = $this->auth->user(); $errors = []; - set_error_handler(function ($errno, $errstr) use (&$errors) { + set_error_handler(function ($errno, $errstr) use (&$errors): true { $errors[] = $errstr; return true; }); @@ -98,7 +98,7 @@ function roleAuthInstance(): Auth test('a known role mixed with an unknown one assigns nothing', function (): void { $user = $this->auth->user(); - set_error_handler(fn () => true); + set_error_handler(fn (): true => true); $result = $user->assign(['admin', 'nope']); restore_error_handler(); diff --git a/tests/tokens.test.php b/tests/tokens.test.php index 46a8285..6e6a17d 100644 --- a/tests/tokens.test.php +++ b/tests/tokens.test.php @@ -142,7 +142,7 @@ expect($user->get())->toHaveKey('email'); // reading tokens is the moment the secret becomes required - expect(fn () => $user->tokens()) + expect(fn (): array => $user->tokens()) ->toThrow(RuntimeException::class, 'No auth token secret'); }); From dbaea565045072e0e533de4ab4a7c75eb8bbc2a4 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:28:41 -0400 Subject: [PATCH 28/46] style(Auth/User.php): remove redundant empty line in docblock Remove an unnecessary empty line in the __call method docblock to improve code readability and maintain consistency with project coding standards. --- src/Auth/User.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index 212c2db..593ff8e 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -522,7 +522,6 @@ public function __unset($name) * @param mixed $method The table to relate to * @param mixed $args * @throws Exception - * * @return Model */ public function __call($method, $args) From 78d5d739a08fd063e87432329137f9b34af1a9f4 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:40:10 -0400 Subject: [PATCH 29/46] refactor(auth): add explicit return type hints to Auth class methods Add return type declarations to `find`, `createUserFor`, and `verifyToken` methods to improve code clarity, static analysis, and type safety. refactor(tests): add type hint to deleteUser table parameter Update the `deleteUser` helper function in Pest to explicitly type the `$table` parameter as a string, ensuring consistency with expected database operations. --- src/Auth.php | 9 +++------ tests/Pest.php | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 8956480..d8993f4 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -564,9 +564,8 @@ public function fromOAuth(array $userData): bool * Select and return an existing user from db * * @param string|int $id The id of the user to grab - * @return User|null */ - public function find($id) + public function find($id): ?User { $this->checkDbConnection(); @@ -585,10 +584,9 @@ public function find($id) * Create an account for another user * * @param array $userData The user details to save - * @return User|false|never * @throws Exception If database connection is not established */ - public function createUserFor($userData) + public function createUserFor($userData): false|User { // each operation owns its error state: stale errors from a // previous call must never leak into this result @@ -899,9 +897,8 @@ public function parseToken(): ?array /** * Verify a user's token * @param string $token The token to verify - * @return User|null */ - public function verifyToken(string $token, ?string $purpose = null) + public function verifyToken(string $token, ?string $purpose = null): ?User { try { $decodedToken = (array) JWT::decode( diff --git a/tests/Pest.php b/tests/Pest.php index 043d4f7..67d3a46 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -66,7 +66,7 @@ function authInstance(): Auth return $auth; } -function deleteUser(string $username, $table = 'users'): void +function deleteUser(string $username, string $table = 'users'): void { $db = dbInstance(); From 7f797fcfb25a5828fbd92b4633b976147767d0b4 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 00:41:10 -0400 Subject: [PATCH 30/46] refactor: improve type safety and strictness in Auth checks - Update Auth::find check to verify instance of User instead of truthy - Update User methods to check for empty array instead of truthy - Ensure consistent validation logic to prevent unexpected behavior when dealing with falsy return values from the data layer. --- src/Auth.php | 2 +- src/Auth/User.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index d8993f4..9c147e0 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -913,7 +913,7 @@ public function verifyToken(string $token, ?string $purpose = null): ?User $user = $this->find($decodedToken['user.id']); - if (!$user) { + if (!$user instanceof User) { $this->errorsArray['token'] = 'User not found'; return null; } diff --git a/src/Auth/User.php b/src/Auth/User.php index 593ff8e..3b05cc0 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -147,7 +147,7 @@ public function update(array $userData): bool { $user = $this->get(); - if (!$user) { + if ($user === []) { return false; } @@ -214,7 +214,7 @@ public function updatePassword(string $oldPassword, string $newPassword): bool { $user = $this->get(); - if (!$user) { + if ($user === []) { return false; } @@ -267,7 +267,7 @@ public function resetPassword(string $newPassword): bool { $user = $this->get(); - if (!$user) { + if ($user === []) { return false; } From ee8ff6e027e2172b65ae70e3888947bcc70a6f1f Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:00:15 -0400 Subject: [PATCH 31/46] refactor: adopt named arguments for improved code readability Update alchemy.yml to include named-args in the refactoring rules. Apply named arguments to Auth and User classes to clarify the intent of boolean flags and configuration parameters, making the codebase easier to maintain and understand. --- alchemy.yml | 4 ++++ src/Auth.php | 6 +++--- src/Auth/User.php | 2 +- tests/login.test.php | 2 +- tests/subscriptions.test.php | 2 +- tests/tokens.test.php | 8 ++++---- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/alchemy.yml b/alchemy.yml index 8900a2b..c14eebb 100644 --- a/alchemy.yml +++ b/alchemy.yml @@ -47,6 +47,10 @@ refactor: - code-quality - coding-style - type-declarations + - type-declaration-docblocks + - privatization + # - naming + - named-args analyse: level: 8 diff --git a/src/Auth.php b/src/Auth.php index 9c147e0..c24ef44 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -535,7 +535,7 @@ public function fromOAuth(array $userData): bool $initialPassword = Config::get('password.key'); $this->checkDbConnection(); - $this->config('password.key', false); + $this->config('password.key', value: false); $user = $this->db->select(Config::get('db.table')) ->where([ @@ -575,7 +575,7 @@ public function find($id): ?User return null; } - return (new User($userData, false))->setDb($this->db); + return (new User($userData, session: false))->setDb($this->db); } /** @@ -638,7 +638,7 @@ public function createUserFor($userData): false|User return false; } - return (new User($user, false))->setDb($this->db); + return (new User($user, session: false))->setDb($this->db); } /** diff --git a/src/Auth/User.php b/src/Auth/User.php index 3b05cc0..fcfa936 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -121,7 +121,7 @@ public function __construct($data, $session = true) } if ($data[Config::get('roles.key')] ?? null) { - $this->setRolesAndPermissions(json_decode($data[Config::get('roles.key')], true)); + $this->setRolesAndPermissions(json_decode($data[Config::get('roles.key')], associative: true)); } } diff --git a/tests/login.test.php b/tests/login.test.php index 009e65b..fc1413a 100644 --- a/tests/login.test.php +++ b/tests/login.test.php @@ -89,7 +89,7 @@ test('login should work without password is password.key is false', function (): void { $auth = authInstance(); - $auth->config('password.key', false); + $auth->config('password.key', value: false); $userData = [ 'username' => 'test-user' diff --git a/tests/subscriptions.test.php b/tests/subscriptions.test.php index dd5767a..6741a2e 100644 --- a/tests/subscriptions.test.php +++ b/tests/subscriptions.test.php @@ -195,7 +195,7 @@ function seedUserSubscription(int $userId, array $overrides = []): void expect($user->cancelSubscription())->toBeTrue(); expect(FakeBillingDriver::$cancelled)->toBe([['sub_1', true]]); - expect($user->cancelSubscription(false))->toBeTrue(); + expect($user->cancelSubscription(atPeriodEnd: false))->toBeTrue(); expect(FakeBillingDriver::$cancelled[1])->toBe(['sub_1', false]); }); diff --git a/tests/tokens.test.php b/tests/tokens.test.php index 6e6a17d..cf9f543 100644 --- a/tests/tokens.test.php +++ b/tests/tokens.test.php @@ -37,7 +37,7 @@ expect($login)->toBeTrue(); $token = $auth->data()->accessToken; - $payload = json_decode(base64_decode(explode('.', $token)[1]), true); + $payload = json_decode(base64_decode(explode('.', $token)[1]), associative: true); // the born-expired bug: exp equalled iat on default config expect($payload['exp'])->toBeGreaterThan($payload['iat'] + 60 * 60 * 24 * 364); @@ -109,7 +109,7 @@ 'id' => 1, 'email' => 'null-roles@example.com', Config::get('roles.key') => null, - ], false); + ], session: false); expect($user->get())->not->toHaveKey(Config::get('roles.key')); }); @@ -124,7 +124,7 @@ 'email' => 'leak-check@example.com', 'pass' => '$2y$10$fakehashfakehashfakehash', 'remember_token' => 'session-equivalent-secret', - ], false); + ], session: false); expect($user->get())->not->toHaveKey('pass') ->and($user->get())->not->toHaveKey('remember_token'); @@ -137,7 +137,7 @@ unset($_ENV['APP_KEY'], $_ENV['AUTH_TOKEN_SECRET']); // constructing a user must not mint JWTs — session apps never read them - $user = new User(['id' => 5, 'email' => 'lazy@example.com'], false); + $user = new User(['id' => 5, 'email' => 'lazy@example.com'], session: false); expect($user->get())->toHaveKey('email'); From d8ba98d65d549e5b19a4625d8b6d858e7896a272 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:03:34 -0400 Subject: [PATCH 32/46] refactor: enable strict types and update rector configuration Add `declare(strict_types=1);` to all core Auth files to enforce strict type checking and prevent unexpected type coercion. Update alchemy.yml to include additional rector rules for improved code quality and automated refactoring. --- alchemy.yml | 4 ++++ src/Auth.php | 2 ++ src/Auth/Config.php | 2 ++ src/Auth/Model.php | 2 ++ src/Auth/User.php | 2 ++ src/Auth/UsesRoles.php | 2 ++ src/Auth/UsesSubscriptions.php | 2 ++ 7 files changed, 16 insertions(+) diff --git a/alchemy.yml b/alchemy.yml index c14eebb..6722e0e 100644 --- a/alchemy.yml +++ b/alchemy.yml @@ -51,6 +51,10 @@ refactor: - privatization # - naming - named-args + - instanceof + - if + - early-return + - rector-preset analyse: level: 8 diff --git a/src/Auth.php b/src/Auth.php index c24ef44..60913f2 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -1,5 +1,7 @@ Date: Wed, 21 Oct 2026 01:09:22 -0400 Subject: [PATCH 33/46] refactor(functions.php): improve type safety and performance - Add return type hint 'Auth' to the auth() function. - Use 'static' keyword for the closure to prevent unnecessary binding. - Simplify conditional check and clean up docblock for better readability. --- src/functions.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/functions.php b/src/functions.php index 320efbe..3d62d4c 100644 --- a/src/functions.php +++ b/src/functions.php @@ -7,15 +7,11 @@ use Leaf\Config; if (!function_exists('auth') && class_exists(App::class)) { - /** - * Return the leaf auth object - * - * @return Leaf\Auth - */ - function auth() + /** Return the leaf auth object */ + function auth(): Auth { - if (!(Config::getStatic('auth'))) { - Config::singleton('auth', fn (): Auth => new Auth()); + if (!Config::getStatic('auth')) { + Config::singleton('auth', static fn (): Auth => new Auth()); } return Config::get('auth'); From 6587fe726f66338684c968cae6b0d51ea459b31c Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:15:44 -0400 Subject: [PATCH 34/46] refactor: simplify and standardize docblock formatting Consolidate multi-line docblocks into single-line format and ensure consistent type hinting across class properties to improve code readability and maintainability. --- src/Auth.php | 21 ++++++--------------- src/Auth/Config.php | 4 ++-- src/Auth/Model.php | 14 ++++---------- src/Auth/User.php | 33 +++++++-------------------------- src/Auth/UsesRoles.php | 4 ++-- src/Auth/UsesSubscriptions.php | 5 +---- 6 files changed, 22 insertions(+), 59 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 60913f2..b18005d 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -28,26 +28,17 @@ */ class Auth { - /** - * The currently authenticated user - * @var ?User - */ + /** @var ?User The currently authenticated user */ protected $user; - /** - * Internal instance of Leaf DB - * @var ?Db - */ + /** @var ?Db Internal instance of Leaf DB */ protected $db; - /** - * All errors caught - * @var array - */ - protected $errorsArray = []; + /** All errors caught */ + protected array $errorsArray = []; - /** @var array Configured oauth clients */ - protected $oauthClients = []; + /** Configured oauth clients */ + protected array $oauthClients = []; public function __construct() { diff --git a/src/Auth/Config.php b/src/Auth/Config.php index 315cbe3..941c8e2 100644 --- a/src/Auth/Config.php +++ b/src/Auth/Config.php @@ -53,7 +53,7 @@ public static function tokenSecret(): string ); } - /** @var array Configuration for Leaf Auth */ + /** Configuration for Leaf Auth */ protected static array $config = [ 'id.key' => 'id', 'db.table' => 'users', @@ -83,7 +83,7 @@ public static function tokenSecret(): string 'messages.loginPasswordError' => 'Password is incorrect!', ]; - /** @var array Additional user information for cache */ + /** Additional user information for cache */ protected static array $userCache = []; /** diff --git a/src/Auth/Model.php b/src/Auth/Model.php index 5d516d0..5c3a1b3 100644 --- a/src/Auth/Model.php +++ b/src/Auth/Model.php @@ -18,22 +18,16 @@ */ class Model { - /** - * User Information - */ + /** User Information */ protected User $user; - /** - * DB table name - */ + /** DB table name */ protected string $table; - /** - * DB connection - */ + /** DB connection */ protected Db $db; - /** @var array User data to save */ + /** User data to save */ protected array $dataToSave = []; /** diff --git a/src/Auth/User.php b/src/Auth/User.php index b5f5dd5..956a534 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -29,42 +29,23 @@ class User implements Stringable use UsesRoles; use UsesSubscriptions; - /** - * Internal instance of Leaf database - * @var Db - */ + /** @var Db Internal instance of Leaf database */ protected $db; - /** - * Internal instance of Leaf session - * @var Session - */ + /** @var Session Internal instance of Leaf session */ protected $session; - /** @var array User Information */ + /** User Information */ protected array $data = []; - /** - * @var array{ - * access?: string, - * refresh?: string, - * } User Tokens - */ + /** @var array{access?: string, refresh?: string} User Tokens */ protected array $tokens = []; - /** - * Expiry used when tokens are lazily minted - * @var int - */ + /** @var int Expiry used when tokens are lazily minted */ protected $tokenLifetime; - /** - * All errors caught - * @var array{ - * password?: string, - * } | array - */ - protected $errorsArray = []; + /** All errors caught */ + protected array $errorsArray = []; /** * @param array $data diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index 1f995ac..5a5881a 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -16,10 +16,10 @@ */ trait UsesRoles { - /** @var string[] User Permissions */ + /** User Permissions */ protected array $permissions = []; - /** @var string[] User Roles */ + /** User Roles */ protected array $roles = []; /** diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index e78dd71..b42e5b1 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -17,10 +17,7 @@ */ trait UsesSubscriptions { - /** - * User Subscription - * @var array|null - */ + /** @var ?array User Subscription */ protected $subscription; /** From 0d02db72b6f8b5d734f7a6618b704a27a755524d Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:17:02 -0400 Subject: [PATCH 35/46] refactor(Auth.php): use static closures for middleware definitions Change middleware closures to static to prevent binding to the current class instance, reducing memory usage and improving performance by avoiding unnecessary object context. --- src/Auth.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index b18005d..99a36a4 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -43,15 +43,15 @@ class Auth public function __construct() { if (class_exists(App::class)) { - $this->middleware('auth.required', function (): void { + $this->middleware('auth.required', static function (): void { response()->redirect(Config::get('redirect.login') ?? '/auth/login'); }); - $this->middleware('auth.guest', function (): void { + $this->middleware('auth.guest', static function (): void { response()->redirect(Config::get('redirect.guest') ?? '/dashboard'); }); - $this->middleware('is', function ($role): void { + $this->middleware('is', static function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -59,7 +59,7 @@ public function __construct() ); }); - $this->middleware('isNot', function (): void { + $this->middleware('isNot', static function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -67,7 +67,7 @@ public function __construct() ); }); - $this->middleware('can', function (): void { + $this->middleware('can', static function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -75,7 +75,7 @@ public function __construct() ); }); - $this->middleware('cannot', function (): void { + $this->middleware('cannot', static function (): void { Pages::error( 'Page not found', 'The page you are looking for could not be found.', @@ -83,11 +83,11 @@ public function __construct() ); }); - $this->middleware('auth.verified', function (): void { + $this->middleware('auth.verified', static function (): void { response()->redirect('/auth/verify'); }); - $this->middleware('auth.unverified', function (): void { + $this->middleware('auth.unverified', static function (): void { response()->redirect('/dashboard'); }); } From 5b3b8d7518d6273a5ec81c74cb13682b877506e5 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:18:10 -0400 Subject: [PATCH 36/46] refactor(Auth.php): add type hints to env method Update the env method signature to use the 'mixed' type for the default parameter and return type to improve code clarity and leverage modern PHP type safety features. --- src/Auth.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 99a36a4..83263b8 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -117,10 +117,8 @@ public function __construct() * reads happen once during setup (e.g. resolving the Google redirect * URI), so the uncached read costs nothing and stays honest when the * environment is set at runtime. - * @param mixed $default - * @return mixed */ - private function env(string $name, $default = false) + private function env(string $name, mixed $default = false): mixed { if (function_exists('_envUncached')) { return _envUncached($name, $default); From 95e716bf69b0cc16cfd3056e4babc5b505095532 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:46:40 -0400 Subject: [PATCH 37/46] style(Auth.php): add trailing commas to array and method arguments Add trailing commas to improve code readability and ensure cleaner git diffs for future modifications to these structures. --- src/Auth.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 83263b8..30d5091 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -55,7 +55,7 @@ public function __construct() Pages::error( 'Page not found', 'The page you are looking for could not be found.', - 403 + 403, ); }); @@ -63,7 +63,7 @@ public function __construct() Pages::error( 'Page not found', 'The page you are looking for could not be found.', - 403 + 403, ); }); @@ -71,7 +71,7 @@ public function __construct() Pages::error( 'Page not found', 'The page you are looking for could not be found.', - 403 + 403, ); }); @@ -79,7 +79,7 @@ public function __construct() Pages::error( 'Page not found', 'The page you are looking for could not be found.', - 403 + 403, ); }); @@ -104,9 +104,9 @@ public function __construct() 'name' => 'google', 'redirectUri' => $this->env( 'GOOGLE_AUTH_REDIRECT_URI', - $this->env('APP_URL') . '/auth/register/google' + $this->env('APP_URL') . '/auth/register/google', ), - ] + ], ); } } @@ -185,7 +185,7 @@ public function dbConnection(PDO $connection): static public function withGoogle( string $clientId, string $clientSecret, - array $options = [] + array $options = [], ): static { $clientName = $options['name'] ?? 'google'; From 1132bacf632ae5929c177b8f203eea6980326be8 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:47:41 -0400 Subject: [PATCH 38/46] refactor(Auth.php): add type hints and return value to config method Update the config method signature to use explicit mixed types for better static analysis and consistency. Add an explicit return null to ensure the method adheres to the defined return type contract. --- src/Auth.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 30d5091..eefbfd1 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -230,9 +230,8 @@ public function client(string $clientName) * * @param string|array $config The auth config key or array of config * @param mixed $value The value if $config is a string - * @return mixed|void */ - public function config($config, $value = null) + public function config($config, mixed $value = null): mixed { if (is_string($config) && $value === null) { return Config::get($config); @@ -243,6 +242,8 @@ public function config($config, $value = null) ? [$config => $value] : $config ); + + return null; } /** From fe43c1d9201646a1e4c8b8d9444e3598849ddaeb Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:50:16 -0400 Subject: [PATCH 39/46] refactor(Auth.php): add explicit return types to class methods Update method signatures to include explicit return types for better type safety, improved IDE autocompletion, and cleaner documentation. Additionally, simplify DocBlock formatting to reduce verbosity. --- src/Auth.php | 76 ++++++++++++++-------------------------------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index eefbfd1..107eb44 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -260,12 +260,8 @@ public function createRoles(array $roles): static return $this; } - /** - * Return all roles and their permissions - * - * @return array - */ - public function roles() + /** Return all roles and their permissions */ + public function roles(): ?array { return Config::get('roles'); } @@ -633,11 +629,8 @@ public function createUserFor($userData): false|User return (new User($user, session: false))->setDb($this->db); } - /** - * Get saved OAuth token - * @return mixed - */ - public function oauthToken() + /** Get saved OAuth token */ + public function oauthToken(): mixed { return Config::getUserCache('oauth-token'); } @@ -670,22 +663,16 @@ public function logout($action = null): bool return true; } - /** - * Get the id of the currently authenticated user - * @return string|int - */ - public function id() + /** Get the id of the currently authenticated user */ + public function id(): string|int { return Config::get('session') ? $this->getFromSession('auth.id') : ($this->user ? $this->user->id() : ($this->parseToken()['user.id'] ?? null)); } - /** - * Get the currently authenticated user - * @return User|null - */ - public function user() + /** Get the currently authenticated user */ + public function user(): ?User { if (Config::get('session')) { $userId = $this->getFromSession('auth.id'); @@ -726,11 +713,8 @@ public function user() ))->setDb($this->db); } - /** - * Get data generated on user login - * @return object|null - */ - public function data() + /** Get data generated on user login */ + public function data(): ?object { $user = $this->user(); @@ -741,14 +725,8 @@ public function data() return $user->getAuthInfo(); } - /** - * Get generated access tokens - * @return array{ - * access?: string, - * refresh?: string, - * }|null - */ - public function tokens() + /** Get generated access tokens */ + public function tokens(): ?array { $user = $this->user(); @@ -927,12 +905,8 @@ public function verifyToken(string $token, ?string $purpose = null): ?User } } - /** - * Return the current db instance - * - * @return Db - */ - public function db() + /** Return the current db instance */ + public function db(): ?Db { return $this->db; } @@ -951,11 +925,8 @@ protected function checkDbConnection(): void } } - /** - * @param string|array $value - * @return ?mixed - */ - protected function getFromSession($value) + /** @param string|array $value */ + protected function getFromSession($value): mixed { if ($this->checkAndExpireSession()) { return null; @@ -964,11 +935,8 @@ protected function getFromSession($value) return Session::get($value); } - /** - * @return void|never - * @throws Exception If sessions are not enabled - */ - protected function sessionCheck() + /** @throws Exception If sessions are not enabled */ + protected function sessionCheck(): void { if (!Config::get('session')) { throw new Exception('Turn on sessions to use this feature.'); @@ -1021,8 +989,7 @@ protected function getTokenFromRequest(): ?string return null; } - /** @return ?mixed */ - protected function getTokenFromSession() + protected function getTokenFromSession(): mixed { return Session::get('auth.token'); } @@ -1035,10 +1002,7 @@ public function clearErrors(): void $this->errorsArray = []; } - /** - * Return all errors caught - * @return array - */ + /** Return all errors caught */ public function errors(): array { return $this->errorsArray; From 12a22cad34155770744425205aed428e48c04447 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:51:40 -0400 Subject: [PATCH 40/46] refactor(Auth.php): simplify PHPDoc blocks Consolidate multi-line docblocks into single-line summaries and remove redundant parameter descriptions to improve code readability and reduce vertical clutter. --- src/Auth.php | 111 ++++++++++++--------------------------------------- 1 file changed, 26 insertions(+), 85 deletions(-) diff --git a/src/Auth.php b/src/Auth.php index 107eb44..9eb3f13 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -147,11 +147,7 @@ public function connect($dbConfig = []): static return $this; } - /** - * Connect to database using environment variables - * - * @param mixed[] $pdoOptions Options for PDO connection - */ + /** Connect to database using environment variables */ public function autoConnect(array $pdoOptions = []): static { $this->db = new Db(); @@ -160,11 +156,7 @@ public function autoConnect(array $pdoOptions = []): static return $this; } - /** - * Pass in db connection instance directly - * - * @param PDO $connection A connection instance of your db - */ + /** Pass in db connection instance directly */ public function dbConnection(PDO $connection): static { $this->db = new Db(); @@ -174,13 +166,12 @@ public function dbConnection(PDO $connection): static } /** - * Register a Google OAuth client - * --- - * Register a Google OAuth client to use with Leaf Auth, should be a league/oauth2-client compatible client. - * @param array{ - * name?: string, - * redirectUri?: string, - * } $options If `$options['redirectUri']` is not set, it will default to the value of `APP_URL` (as resolved by this class's environment helper) with `/auth/google/callback` appended; if `APP_URL` is unset or empty, the default will be `/auth/google/callback`. + * Register a Google OAuth client to use with Leaf Auth, should be a + * league/oauth2-client compatible client. + * @param array{name?: string, redirectUri?: string} $options + * - If `$options['redirectUri']` is not set, it will default to the + * value of `APP_URL` (as resolved by this class's environment helper) + * with `/auth/google/callback` appended. */ public function withGoogle( string $clientId, @@ -203,10 +194,8 @@ public function withGoogle( } /** - * Register a generic OAuth client - * --- - * Register a generic OAuth client to use with Leaf Auth, should be a league/oauth2-client compatible client. - * @param string $clientName The name of the client to register + * Register a generic OAuth client to use with Leaf Auth, should be a + * league/oauth2-client compatible client. * @param AbstractProvider $client An instance of a league/oauth2-client compatible client */ public function withProvider(string $clientName, $client): static @@ -215,11 +204,7 @@ public function withProvider(string $clientName, $client): static return $this; } - /** - * Return an oauth client - * @param string $clientName The name of the client to return - * @return AbstractProvider|null - */ + /** @return AbstractProvider|null An oauth client */ public function client(string $clientName) { return $this->oauthClients[$clientName] ?? null; @@ -227,8 +212,8 @@ public function client(string $clientName) /** * Get/Set Leaf Auth config - * - * @param string|array $config The auth config key or array of config + * @param string|array $config The auth config key or + * array of config * @param mixed $value The value if $config is a string */ public function config($config, mixed $value = null): mixed @@ -248,8 +233,8 @@ public function config($config, mixed $value = null): mixed /** * Create roles and permissions - * - * @param array $roles Array of roles and their permissions + * @param array $roles Array of roles and their + * permissions */ public function createRoles(array $roles): static { @@ -266,13 +251,7 @@ public function roles(): ?array return Config::get('roles'); } - /** - * Sign a user in - * --- - * Verify user credentials and sign them in with token or session - * - * @param array $credentials User credentials - */ + /** Verify user credentials and sign them in with token or session */ public function login(array $credentials): bool { $this->errorsArray = []; @@ -321,13 +300,7 @@ public function login(array $credentials): bool return true; } - /** - * Register a new user - * --- - * Save a new user to the database - * - * @param array $userData User data - */ + /** Save a new user to the database */ public function register(array $userData): bool { // each operation owns its error state: stale errors from a @@ -385,13 +358,7 @@ public function register(array $userData): bool return true; } - /** - * Update user data - * --- - * Update user data in the database - * - * @param array $userData User data - */ + /** Update user data in the database */ public function update(array $userData): bool { // each operation owns its error state: stale errors from a @@ -457,14 +424,7 @@ public function update(array $userData): bool return true; } - /** - * Update user password - * --- - * Update user password in the database - * - * @param string $oldPassword Old password - * @param string $newPassword New password - */ + /** Update user password in the database */ public function updatePassword(string $oldPassword, string $newPassword): bool { $this->checkDbConnection(); @@ -513,11 +473,7 @@ public function updatePassword(string $oldPassword, string $newPassword): bool return true; } - /** - * Create a new user from OAuth - * - * @param array $userData User data - */ + /** Create a new user from OAuth */ public function fromOAuth(array $userData): bool { $initialPassword = Config::get('password.key'); @@ -547,10 +503,7 @@ public function fromOAuth(array $userData): bool } /** - * Find a user by id - * --- - * Select and return an existing user from db - * + * Select and return an existing user from db by id * @param string|int $id The id of the user to grab */ public function find($id): ?User @@ -567,10 +520,7 @@ public function find($id): ?User } /** - * Create a new user - * --- * Create an account for another user - * * @param array $userData The user details to save * @throws Exception If database connection is not established */ @@ -636,11 +586,9 @@ public function oauthToken(): mixed } /** - * Sign a user out - * --- * Sign out the currently authenticated user - * - * @param string|mixed[]|callable|null $action Redirect to this url after logout + * @param string|mixed[]|callable|null $action Redirect to this url + * after logout */ public function logout($action = null): bool { @@ -842,9 +790,7 @@ public function middleware(string $middleware, callable $callback): void app()->registerMiddleware($middleware, $callback); } - /** - * Parse the current user's token - */ + /** Parse the current user's token */ public function parseToken(): ?array { $bearerToken = $this->getTokenFromRequest(); @@ -864,10 +810,7 @@ public function parseToken(): ?array } } - /** - * Verify a user's token - * @param string $token The token to verify - */ + /** Verify a user's token */ public function verifyToken(string $token, ?string $purpose = null): ?User { try { @@ -994,9 +937,7 @@ protected function getTokenFromSession(): mixed return Session::get('auth.token'); } - /** - * Clear all errors caught - */ + /** Clear all errors caught */ public function clearErrors(): void { $this->errorsArray = []; From f1874220117ecbd41a6935318c1aad70b55055f2 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 01:56:53 -0400 Subject: [PATCH 41/46] style(Auth/Model.php): condense docblock formatting Refactor docblocks to single-line format to improve code readability and reduce vertical whitespace in the Auth Model class. --- src/Auth/Model.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Auth/Model.php b/src/Auth/Model.php index 5c3a1b3..036dfb3 100644 --- a/src/Auth/Model.php +++ b/src/Auth/Model.php @@ -30,9 +30,7 @@ class Model /** User data to save */ protected array $dataToSave = []; - /** - * @param array{db: Db, user: User, table: string} $data - */ + /** @param array{db: Db, user: User, table: string} $data */ public function __construct($data) { $this->db = $data['db']; @@ -42,7 +40,6 @@ public function __construct($data) /** * Create a new model resource - * * @param array $data Data to be inserted */ public function create(array $data): ?PDOStatement @@ -60,7 +57,6 @@ public function create(array $data): ?PDOStatement /** * Update a model resource - * * @param array $data Data to be updated */ public function update(array $data): Db @@ -72,9 +68,7 @@ public function update(array $data): Db ->where('user_id', $this->user->id()); } - /** - * Delete a model resource - */ + /** Delete a model resource */ public function delete(): Db { return $this->db->delete($this->table) @@ -83,7 +77,6 @@ public function delete(): Db /** * Get a model resource - * * @param string $columns Columns to search by separated by commas or * */ public function table($columns = '*'): Db @@ -92,9 +85,7 @@ public function table($columns = '*'): Db ->where('user_id', $this->user->id()); } - /** - * Save a model resource - */ + /** Save a model resource */ public function save(): ?PDOStatement { $success = $this->create($this->dataToSave); @@ -104,9 +95,7 @@ public function save(): ?PDOStatement return $success; } - /** - * @param string $name - */ + /** @param string $name */ public function __set($name, mixed $value) { $this->dataToSave[$name] = $value; From 07584505132e6477a7b1daa1fd0fe21eaeef3a60 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 02:04:10 -0400 Subject: [PATCH 42/46] refactor(auth): remove unnecessary ReturnTypeWillChange attribute Remove the ReturnTypeWillChange attribute from the __toString method as the method now explicitly declares a string return type, making the attribute redundant and cleaning up the codebase. --- src/Auth/User.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index 956a534..151b1ea 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -8,7 +8,6 @@ use Leaf\App; use Exception; use Throwable; -use ReturnTypeWillChange; use Leaf\Db; use Firebase\JWT\JWT; use Leaf\Date; @@ -449,7 +448,6 @@ public function errors() return $this->errorsArray; } - #[ReturnTypeWillChange] public function __toString(): string { return json_encode($this->get()) ?: ''; From 6a46787301505bb24b5e797451d09da564e4a8c3 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 02:05:10 -0400 Subject: [PATCH 43/46] refactor(Auth/User.php): add strict return types and parameter types Update method signatures in the User class to include explicit return types and parameter types. This improves code readability, enables better static analysis, and ensures type safety across the authentication module. --- src/Auth/User.php | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index 151b1ea..028ea05 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -107,11 +107,8 @@ public function __construct($data, $session = true) } } - /** - * Return the id of current user - * @return string|int - */ - public function id() + /** Return the id of current user */ + public function id(): null|string|int { return $this->data[Config::get('id.key')] ?? null; } @@ -437,13 +434,8 @@ public function setDb($db): static return $this; } - /** - * Get user errors - * @return array{ - * password?: string, - * } | array - */ - public function errors() + /** Get user errors */ + public function errors(): array { return $this->errorsArray; } @@ -472,40 +464,29 @@ public function __set($name, mixed $value): void $this->data[$name] = $value; } - /** - * @param string $name - * @return bool - */ - public function __isset($name) + /** @param string $name */ + public function __isset($name): bool { return isset($this->data[$name]); } - /** - * @param string $name - * @return void - */ - public function __unset($name) + /** @param string $name */ + public function __unset($name): void { unset($this->data[$name]); } /** * Get a "user to many" table relation - * - * + * ``` * auth()->user()->orders()->all(); * auth()->user()->transactions()->where('amount', '>', 100)->get(); * auth()->user()->notes()->where('title', 'like', '%important%')->get(); * auth()->user()->posts()->where('published', true)->all(); - * - * - * @param mixed $method The table to relate to - * @param mixed $args - * @throws Exception - * @return Model + * ``` + * @throws Exception If Leaf App class does not exist */ - public function __call($method, $args) + public function __call(mixed $method, mixed $args): Model { if (!class_exists(App::class)) { throw new Exception('Relations are only available in Leaf apps.'); From fcbce09d1e32ff784d1d5e17c7325ebc6425c3da Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 02:05:52 -0400 Subject: [PATCH 44/46] refactor(Auth/User.php): simplify docblock comments Remove redundant multi-line docblock descriptions and unnecessary type annotations to reduce code verbosity and improve readability. --- src/Auth/User.php | 56 ++++++++--------------------------------------- 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/src/Auth/User.php b/src/Auth/User.php index 028ea05..5742cd2 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -113,15 +113,7 @@ public function id(): null|string|int return $this->data[Config::get('id.key')] ?? null; } - /** - * Update user data - * --- - * Update user data in the database - * - * @param array{ - * email?: string, - * } | array $userData User data - */ + /** Update user data in the database */ public function update(array $userData): bool { $user = $this->get(); @@ -181,14 +173,7 @@ public function update(array $userData): bool return true; } - /** - * Update user password - * --- - * Update user password in the database - * - * @param string $oldPassword Old password - * @param string $newPassword New password - */ + /** Update user password in the database */ public function updatePassword(string $oldPassword, string $newPassword): bool { $user = $this->get(); @@ -235,13 +220,7 @@ public function updatePassword(string $oldPassword, string $newPassword): bool return true; } - /** - * Reset user password - * --- - * Reset user password in the database - * - * @param string $newPassword New password - */ + /** Reset user password in the database */ public function resetPassword(string $newPassword): bool { $user = $this->get(); @@ -276,9 +255,7 @@ public function resetPassword(string $newPassword): bool return true; } - /** - * Get auth information to be sent to the client - */ + /** Get auth information to be sent to the client */ public function getAuthInfo(): object { $tokens = $this->tokens(); @@ -300,13 +277,7 @@ public function getAuthInfo(): object return $dataToReturn; } - /** - * Return generated tokens - * @return array{ - * access?: string, - * refresh?: string, - * } - */ + /** Return generated tokens */ public function tokens(): array { if (!isset($this->tokens['access'])) { @@ -339,7 +310,6 @@ public function generateToken($tokenLifetime): string /** * Generate a verification token for the user * @param mixed $expiresIn Token expiration time - * @param string|null $purpose Purpose of the token */ public function generateVerificationToken($expiresIn = null, ?string $purpose = null): string { @@ -361,17 +331,13 @@ public function generateVerificationToken($expiresIn = null, ?string $purpose = return JWT::encode($payload, $secretPhrase, 'HS256'); } - /** - * Check if email is verified - */ + /** Check if email is verified */ public function isVerified(): bool { return (bool) ($this->data['email_verified_at'] ?? false); } - /** - * Verify user's email - */ + /** Verify user's email */ public function verifyEmail(): bool { if ($this->isVerified()) { @@ -445,9 +411,7 @@ public function __toString(): string return json_encode($this->get()) ?: ''; } - /** - * @param string $name - */ + /** @param string $name */ public function __get($name): mixed { // using data instead of get() here because @@ -456,9 +420,7 @@ public function __get($name): mixed return $this->data[$name] ?? null; } - /** - * @param string $name - */ + /** @param string $name */ public function __set($name, mixed $value): void { $this->data[$name] = $value; From 6cb2d77fa10a8e8b9361f2e5ffb6a67089199a7c Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 02:07:37 -0400 Subject: [PATCH 45/46] refactor(auth): clean up docblock formatting in UsesRoles trait Remove redundant empty lines and unnecessary return type annotations in docblocks to improve code readability and maintain consistency with the project's documentation style. --- src/Auth/UsesRoles.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index 5a5881a..d0cfe05 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -24,7 +24,6 @@ trait UsesRoles /** * Assign new role to user - * * @param string|string[] $role The role to assign */ public function assign($role): bool @@ -111,19 +110,13 @@ public function isNot($role): bool return !$this->is($role); } - /** - * Return the user's roles - * @return string[] - */ + /** Return the user's roles */ public function roles(): array { return $this->roles; } - /** - * Return the user's permissions - * @return string[] - */ + /** Return the user's permissions */ public function permissions(): array { return $this->permissions; @@ -153,9 +146,7 @@ public function unassign($role): void /** * Return any of the given roles that haven't been registered - * * @param string|string[] $roles The role(s) to check - * @return string[] */ protected function unknownRoles($roles): array { @@ -169,7 +160,6 @@ protected function unknownRoles($roles): array /** * Set the roles and permissions for a user - * * @param string|string[] $roles The role(s) to set */ protected function setRolesAndPermissions($roles): void @@ -195,9 +185,7 @@ protected function setRolesAndPermissions($roles): void /** * Get the permissions for a role - * * @param string|string[] $roles - * @return string[] */ protected function getRolePermissions($roles): array { From 6d4c54376d8f8fa5a1347a48ebd6f39ba76a4f9a Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 21 Oct 2026 02:10:41 -0400 Subject: [PATCH 46/46] refactor(Auth): clean up docblock formatting Remove redundant docblock lines and consolidate single-line comments to improve code readability and maintain consistency across the trait. --- src/Auth/UsesSubscriptions.php | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index b42e5b1..c70ccc9 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -25,8 +25,6 @@ trait UsesSubscriptions * * The most recent subscription is returned, so a resubscribed user * gets their new subscription rather than an older cancelled one. - * - * @return mixed[]|null */ public function subscription(): ?array { @@ -48,9 +46,7 @@ public function subscription(): ?array return $this->subscription ?: null; } - /** - * Check if user has a subscription - */ + /** Check if user has a subscription */ public function hasSubscription(): bool { return $this->subscription() && $this->subscription['status'] !== Subscription::STATUS_CANCELLED; @@ -83,7 +79,6 @@ public function hasActiveSubscription(): bool * Matches on the subscription name or on the billing tier's name or id, * so `isSubscribedTo('Starter')` works whether you named the * subscription after the tier or not. - * * @param string $plan The plan name or tier id to check */ public function isSubscribedTo(string $plan): bool @@ -111,9 +106,7 @@ public function isSubscribedTo(string $plan): bool return ($tier['name'] ?? null) === $plan || ($tier['id'] ?? null) === $plan; } - /** - * Check if user is on a trial - */ + /** Check if user is on a trial */ public function onTrial(): bool { if (!$this->subscription()) { @@ -129,9 +122,7 @@ public function onTrial(): bool return $trialEndsAt && strtotime($trialEndsAt) > time(); } - /** - * Check if user cancelled but still has access until the period ends - */ + /** Check if user cancelled but still has access until the period ends */ public function onGracePeriod(): bool { if (!$this->subscription()) { @@ -159,8 +150,6 @@ public function hasPastDueSubscription(): bool * By default the subscription is cancelled at the end of the current * billing period, so the user keeps access to what they paid for. * Pass false to cancel and revoke access immediately. - * - * @param bool $atPeriodEnd Cancel at period end instead of immediately */ public function cancelSubscription(bool $atPeriodEnd = true): bool {