diff --git a/README.md b/README.md index 75f39bb..c5bf234 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@

+![PHPStan: Level 8](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg?style=flat) [![Latest Stable Version](https://poser.pugx.org/leafs/auth/v/stable)](https://packagist.org/packages/leafs/auth) [![Total Downloads](https://poser.pugx.org/leafs/auth/downloads)](https://packagist.org/packages/leafs/auth) [![License](https://poser.pugx.org/leafs/auth/license)](https://packagist.org/packages/leafs/auth) diff --git a/composer.json b/composer.json index 2e048d9..3dbe6a7 100644 --- a/composer.json +++ b/composer.json @@ -42,13 +42,19 @@ }, "config": { "allow-plugins": { - "pestphp/pest-plugin": true + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true } }, "require-dev": { "pestphp/pest": "^1.0 | ^2.0", "friendsofphp/php-cs-fixer": "^3.64", - "leafs/alchemy": "*" + "leafs/alchemy": "*", + "phpstan/phpstan": "^2.1", + "phpstan/extension-installer": "^1.4", + "leafs/leaf": "^4.4", + "league/oauth2-google": "^4.0", + "leafs/billing": "^0.2.0" }, "scripts": { "alchemy": "./vendor/bin/alchemy setup", @@ -56,4 +62,4 @@ "lint": "./vendor/bin/alchemy setup --lint", "actions": "./vendor/bin/alchemy setup --actions" } -} \ No newline at end of file +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..b7260f7 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,475 @@ +parameters: + ignoreErrors: + - + rawMessage: Access to an undefined property Leaf\Auth\User::$email. + identifier: property.notFound + count: 1 + path: src/Auth.php + + - + rawMessage: 'Variable $credentials in isset() is never defined.' + identifier: isset.variable + count: 2 + path: src/Auth.php + + - + rawMessage: 'Function createTableForUsers() has parameter $table with no type specified.' + identifier: missingType.parameter + count: 1 + path: tests/Pest.php + + - + rawMessage: 'Function deleteUser() has no return type specified.' + identifier: missingType.return + count: 1 + path: tests/Pest.php + + - + rawMessage: 'Function deleteUser() has parameter $table with no type specified.' + identifier: missingType.parameter + count: 1 + path: tests/Pest.php + + - + rawMessage: 'Function getDatabaseConnection() return type has no value type specified in iterable type array.' + identifier: missingType.iterableValue + count: 1 + path: tests/Pest.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::not().' + identifier: method.notFound + count: 1 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBe().' + identifier: method.notFound + count: 4 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeFalse().' + identifier: method.notFound + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeInstanceOf().' + identifier: method.notFound + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeNull().' + identifier: method.notFound + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeString().' + identifier: method.notFound + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeTrue().' + identifier: method.notFound + count: 3 + path: tests/login.test.php + + - + rawMessage: 'Call to method not() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 4 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBeFalse() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBeNull() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBeNull() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBeString() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to method toBeTrue() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 3 + path: tests/login.test.php + + - + rawMessage: Cannot access property $accessToken on object|null. + identifier: property.nonObject + count: 1 + path: tests/login.test.php + + - + rawMessage: Cannot access property $refreshToken on object|null. + identifier: property.nonObject + count: 1 + path: tests/login.test.php + + - + rawMessage: Cannot access property $username on Leaf\Auth\User|null. + identifier: property.nonObject + count: 2 + path: tests/login.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::not().' + identifier: method.notFound + count: 1 + path: tests/register.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBe().' + identifier: method.notFound + count: 3 + path: tests/register.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeFalse().' + identifier: method.notFound + count: 1 + path: tests/register.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeInstanceOf().' + identifier: method.notFound + count: 2 + path: tests/register.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeTrue().' + identifier: method.notFound + count: 6 + path: tests/register.test.php + + - + rawMessage: 'Call to method not() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/register.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 3 + path: tests/register.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/register.test.php + + - + rawMessage: 'Call to method toBeFalse() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/register.test.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/register.test.php + + - + rawMessage: 'Call to method toBeTrue() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 6 + path: tests/register.test.php + + - + rawMessage: Cannot access property $password on Leaf\Auth\User|null. + identifier: property.nonObject + count: 2 + path: tests/register.test.php + + - + rawMessage: Cannot access property $username on Leaf\Auth\User|null. + identifier: property.nonObject + count: 2 + path: tests/register.test.php + + - + rawMessage: 'Undefined variable: $this' + identifier: variable.undefined + count: 1 + path: tests/register.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::not().' + identifier: method.notFound + count: 1 + path: tests/session.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBe().' + identifier: method.notFound + count: 20 + path: tests/session.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeGreaterThan().' + identifier: method.notFound + count: 1 + path: tests/session.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeInstanceOf().' + identifier: method.notFound + count: 8 + path: tests/session.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeNull().' + identifier: method.notFound + count: 6 + path: tests/session.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeTrue().' + identifier: method.notFound + count: 8 + path: tests/session.test.php + + - + rawMessage: 'Call to method not() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/session.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 20 + path: tests/session.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/session.test.php + + - + rawMessage: 'Call to method toBeGreaterThan() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/session.test.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 8 + path: tests/session.test.php + + - + rawMessage: 'Call to method toBeNull() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 6 + path: tests/session.test.php + + - + rawMessage: 'Call to method toBeTrue() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 8 + path: tests/session.test.php + + - + rawMessage: Cannot access property $username on Leaf\Auth\User|null. + identifier: property.nonObject + count: 6 + path: tests/session.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBe().' + identifier: method.notFound + count: 5 + path: tests/table.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 5 + path: tests/table.test.php + + - + rawMessage: Cannot access offset 'user' on bool. + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/table.test.php + + - + rawMessage: Cannot access property $username on Leaf\Auth\User|null. + identifier: property.nonObject + count: 2 + path: tests/table.test.php + + - + rawMessage: Function createUsersTable not found. + identifier: function.notFound + count: 1 + path: tests/table.test.php + + - + rawMessage: 'Undefined variable: $this' + identifier: variable.undefined + count: 5 + path: tests/table.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::not().' + identifier: method.notFound + count: 2 + path: tests/update.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBe().' + identifier: method.notFound + count: 3 + path: tests/update.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeFalse().' + identifier: method.notFound + count: 1 + path: tests/update.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeTrue().' + identifier: method.notFound + count: 1 + path: tests/update.test.php + + - + rawMessage: 'Call to method not() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/update.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 3 + path: tests/update.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/update.test.php + + - + rawMessage: 'Call to method toBeFalse() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/update.test.php + + - + rawMessage: 'Call to method toBeTrue() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/update.test.php + + - + rawMessage: Cannot access property $username on Leaf\Auth\User|null. + identifier: property.nonObject + count: 2 + path: tests/update.test.php + + - + rawMessage: 'Undefined variable: $this' + identifier: variable.undefined + count: 7 + path: tests/update.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBe().' + identifier: method.notFound + count: 2 + path: tests/user.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeInstanceOf().' + identifier: method.notFound + count: 3 + path: tests/user.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeNull().' + identifier: method.notFound + count: 1 + path: tests/user.test.php + + - + rawMessage: 'Call to an undefined method Pest\Expectation|Pest\Support\Extendable::toBeTrue().' + identifier: method.notFound + count: 2 + path: tests/user.test.php + + - + rawMessage: 'Call to method toBe() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/user.test.php + + - + rawMessage: 'Call to method toBeInstanceOf() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 3 + path: tests/user.test.php + + - + rawMessage: 'Call to method toBeNull() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 1 + path: tests/user.test.php + + - + rawMessage: 'Call to method toBeTrue() of internal class Pest\Expectation from outside its root namespace Pest.' + identifier: method.internalClass + count: 2 + path: tests/user.test.php + + - + rawMessage: Cannot access property $username on Leaf\Auth\User|null. + identifier: property.nonObject + count: 2 + path: tests/user.test.php + + - + rawMessage: 'Undefined variable: $this' + identifier: variable.undefined + count: 2 + path: tests/user.test.php diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..c275599 --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,10 @@ +includes: + - vendor/phpstan/phpstan/conf/bleedingEdge.neon + - phpstan-baseline.neon + +parameters: + level: 8 + paths: + - src + - tests + treatPhpDocTypesAsCertain: false diff --git a/src/Auth.php b/src/Auth.php index 7806538..4eb13d8 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -22,9 +22,9 @@ class Auth { /** * The currently authenticated user - * @var User + * @var ?User */ - protected $user; + protected $user = null; /** * Internal instance of Leaf DB @@ -34,13 +34,11 @@ class Auth /** * All errors caught - * @var array + * @var array */ protected $errorsArray = []; - /** - * Configured oauth clients - */ + /** @var array Configured oauth clients */ protected $oauthClients = []; public function __construct() @@ -113,7 +111,14 @@ class_exists('League\OAuth2\Client\Provider\Google') && /** * Connect leaf auth to the database - * @param array $dbConfig Configuration for leaf db connection + * @param array{ + * host?: string, + * dbname?: string, + * username?: string, + * password?: string, + * dbtype?: string, + * pdoOptions?: mixed[], + * } $dbConfig Configuration for leaf db connection * @return $this */ public function connect($dbConfig = []) @@ -127,7 +132,7 @@ public function connect($dbConfig = []) /** * Connect to database using environment variables * - * @param array $pdoOptions Options for PDO connection + * @param mixed[] $pdoOptions Options for PDO connection * @return $this */ public function autoConnect(array $pdoOptions = []) @@ -142,7 +147,7 @@ public function autoConnect(array $pdoOptions = []) * Pass in db connection instance directly * * @param \PDO $connection A connection instance of your db - * @return $this; + * @return $this */ public function dbConnection(\PDO $connection) { @@ -158,7 +163,10 @@ public function dbConnection(\PDO $connection) * 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 $options + * @param array{ + * name?: string, + * redirectUri?: string, + * } $options * @return static */ public function withGoogle( @@ -210,8 +218,9 @@ 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 + * @return mixed|void */ public function config($config, $value = null) { @@ -229,7 +238,7 @@ public function config($config, $value = null) /** * Create roles and permissions * - * @param array $roles Array of roles and their permissions + * @param array $roles Array of roles and their permissions * @return Auth */ public function createRoles(array $roles) @@ -244,7 +253,7 @@ public function createRoles(array $roles) /** * Return all roles and their permissions * - * @return array + * @return array */ public function roles() { @@ -256,7 +265,7 @@ public function roles() * --- * Verify user credentials and sign them in with token or session * - * @param array $credentials User credentials + * @param array $credentials User credentials * @return bool */ public function login(array $credentials): bool @@ -310,7 +319,7 @@ public function login(array $credentials): bool * --- * Save a new user to the database * - * @param array $userData User data + * @param array $userData User data * @return bool */ public function register(array $userData): bool @@ -371,7 +380,7 @@ public function register(array $userData): bool * --- * Update user data in the database * - * @param array $userData User data + * @param array $userData User data * @return bool */ public function update(array $userData): bool @@ -495,7 +504,7 @@ public function updatePassword(string $oldPassword, string $newPassword): bool /** * Create a new user from OAuth * - * @param array $userData User data + * @param array $userData User data * * @return bool */ @@ -553,7 +562,9 @@ public function find($id) * --- * Create an account for another user * - * @param array The user details to save + * @param array $userData The user details to save + * @return User|false|never + * @throws \Exception If database connection is not established */ public function createUserFor($userData) { @@ -608,6 +619,7 @@ public function createUserFor($userData) /** * Get saved OAuth token + * @return mixed */ public function oauthToken() { @@ -619,7 +631,7 @@ public function oauthToken() * --- * Sign out the currently authenticated user * - * @param string|array|callable|null $redirectUrl Redirect to this url after logout + * @param string|mixed[]|callable|null $action Redirect to this url after logout * @return bool */ public function logout($action = null): bool @@ -716,7 +728,10 @@ public function data() /** * Get generated access tokens - * @return array|null + * @return array{ + * access?: string, + * refresh?: string, + * }|null */ public function tokens() { @@ -733,6 +748,8 @@ 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) { @@ -741,16 +758,18 @@ public function middleware(string $middleware, callable $callback) } if ($middleware === 'auth.required') { - return app()->registerMiddleware('auth.required', function () use ($callback) { + app()->registerMiddleware('auth.required', function () use ($callback) { if (!$this->user()) { $callback(); exit; } }); + + return; } if ($middleware === 'auth.guest') { - return app()->registerMiddleware('auth.guest', function () use ($callback) { + app()->registerMiddleware('auth.guest', function () use ($callback) { if ($this->user()) { $callback(); exit; @@ -758,60 +777,74 @@ public function middleware(string $middleware, callable $callback) auth()->clearErrors(); }); + + return; } if ($middleware === 'is') { - return app()->registerMiddleware('is', function ($role) use ($callback) { - if (!$this->user() || ($this->user() && $this->user()->isNot($role))) { + app()->registerMiddleware('is', function ($role) use ($callback) { + if (!$this->user() || $this->user()->isNot($role)) { $callback($role); exit; } }); + + return; } if ($middleware === 'isNot') { - return app()->registerMiddleware('isNot', function ($role) use ($callback) { - if (!$this->user() || ($this->user() && $this->user()->is($role))) { + app()->registerMiddleware('isNot', function ($role) use ($callback) { + if (!$this->user() || $this->user()->is($role)) { $callback($role); exit; } }); + + return; } if ($middleware === 'can') { - return app()->registerMiddleware('can', function ($role) use ($callback) { - if (!$this->user() || ($this->user() && $this->user()->cannot($role))) { + app()->registerMiddleware('can', function ($role) use ($callback) { + if (!$this->user() || $this->user()->cannot($role)) { $callback($role); exit; } }); + + return; } if ($middleware === 'cannot') { - return app()->registerMiddleware('cannot', function ($role) use ($callback) { - if (!$this->user() || ($this->user() && $this->user()->can($role))) { + app()->registerMiddleware('cannot', function ($role) use ($callback) { + if (!$this->user() || $this->user()->can($role)) { $callback($role); exit; } }); + + return; } if ($middleware === 'auth.verified') { - return app()->registerMiddleware('auth.verified', function () use ($callback) { + app()->registerMiddleware('auth.verified', function () use ($callback) { if (!$this->user() || !$this->user()->isVerified()) { $callback(); exit; } }); + + return; } if ($middleware === 'auth.unverified') { - return app()->registerMiddleware('auth.unverified', function () use ($callback) { + app()->registerMiddleware('auth.unverified', function () use ($callback) { if (!$this->user() || $this->user()->isVerified()) { $callback(); exit; } }); + + return; } app()->registerMiddleware($middleware, $callback); @@ -819,6 +852,7 @@ public function middleware(string $middleware, callable $callback) /** * Parse the current user's token + * @return array|null */ public function parseToken() { @@ -904,6 +938,10 @@ protected function checkDbConnection(): void } } + /** + * @param string|array $value + * @return ?mixed + */ protected function getFromSession($value) { if ($this->checkAndExpireSession()) { @@ -913,6 +951,10 @@ protected function getFromSession($value) return Session::get($value); } + /** + * @return void|never + * @throws \Exception If sessions are not enabled + */ protected function sessionCheck() { if (!Config::get('session')) { @@ -937,6 +979,7 @@ protected function checkAndExpireSession(): bool return $isSessionExpired; } + /** @return ?string */ protected function getTokenFromRequest() { $headers = null; @@ -966,6 +1009,7 @@ protected function getTokenFromRequest() return null; } + /** @return ?mixed */ protected function getTokenFromSession() { return Session::get('auth.token'); @@ -973,6 +1017,7 @@ protected function getTokenFromSession() /** * Clear all errors caught + * @return void */ public function clearErrors() { @@ -981,6 +1026,7 @@ public function clearErrors() /** * Return all errors caught + * @return array */ public function errors(): array { diff --git a/src/Auth/Config.php b/src/Auth/Config.php index adec30e..865e192 100644 --- a/src/Auth/Config.php +++ b/src/Auth/Config.php @@ -12,10 +12,7 @@ */ class Config { - /** - * configuration for Leaf Auth - * @var array - */ + /** @var array Configuration for Leaf Auth */ protected static array $config = [ 'id.key' => 'id', 'db.table' => 'users', @@ -42,13 +39,12 @@ class Config 'messages.loginPasswordError' => 'Password is incorrect!', ]; - /** - * Additional user information for cache - */ + /** @var array Additional user information for cache */ protected static array $userCache = []; /** * Set Leaf Auth config + * @param array $config */ public static function set($config): void { @@ -57,6 +53,7 @@ public static function set($config): void /** * Overwrite Leaf Auth config + * @param array $config */ public static function overwrite($config): void { @@ -65,6 +62,8 @@ public static function overwrite($config): void /** * Get Leaf Auth config + * @param ?string $key + * @return ?mixed */ public static function get($key = null) { @@ -77,6 +76,8 @@ public static function get($key = null) /** * Set user cache + * @param string $key + * @param mixed $value */ public static function setUserCache($key, $value): void { @@ -89,6 +90,8 @@ public static function setUserCache($key, $value): void /** * Get user cache + * @param ?string $key + * @return mixed|array */ public static function getUserCache($key = null) { diff --git a/src/Auth/Model.php b/src/Auth/Model.php index f14ecb0..23b761e 100644 --- a/src/Auth/Model.php +++ b/src/Auth/Model.php @@ -29,11 +29,16 @@ class Model */ protected \Leaf\Db $db; - /** - * User data to save - */ + /** @var array User data to save */ protected array $dataToSave = []; + /** + * @param array{ + * db: \Leaf\Db, + * user: User, + * table: string, + * } $data + */ public function __construct($data) { $this->db = $data['db']; @@ -44,7 +49,7 @@ public function __construct($data) /** * Create a new model resource * - * @param array $data Data to be inserted + * @param array $data Data to be inserted * * @return PDOStatement|null */ @@ -64,7 +69,7 @@ public function create(array $data): ?PDOStatement /** * Update a model resource * - * @param array $data Data to be updated + * @param array $data Data to be updated * * @return \Leaf\Db */ @@ -115,11 +120,20 @@ public function save(): ?PDOStatement return $success; } + /** + * @param string $name + * @param mixed $value + */ public function __set($name, $value) { $this->dataToSave[$name] = $value; } + /** + * @param string $name + * @param mixed[] $arguments + * @return mixed + */ public function __call($name, $arguments) { return $this->table()->$name(...$arguments); diff --git a/src/Auth/User.php b/src/Auth/User.php index c397b18..65539c4 100644 --- a/src/Auth/User.php +++ b/src/Auth/User.php @@ -20,7 +20,7 @@ class User /** * Internal instance of Leaf database - * @var \Leaf\DB + * @var \Leaf\Db */ protected $db; @@ -30,16 +30,21 @@ class User */ protected $session; - /** - * User Information - */ + /** @var array User Information */ protected array $data = []; /** - * User Tokens + * @var array{ + * access?: string, + * refresh?: string, + * } User Tokens */ protected array $tokens = []; + /** + * @param array $data + * @param bool $session + */ public function __construct($data, $session = true) { $this->data = $data; @@ -81,7 +86,7 @@ public function __construct($data, $session = true) $sessionLifetime = $sessionLifetime && !is_numeric($sessionLifetime) ? strtotime($sessionLifetime) - : (time() + $sessionLifetime); + : (time() + intval($sessionLifetime)); $this->tokens['access'] = $this->generateToken($sessionLifetime); $this->tokens['refresh'] = $this->generateToken($sessionLifetime + 259200); @@ -125,7 +130,10 @@ public function getAuthInfo(): object /** * Return generated tokens - * @return array + * @return array{ + * access?: string, + * refresh?: string, + * } */ public function tokens(): array { @@ -134,6 +142,7 @@ public function tokens(): array /** * Generate a new JWT for the user + * @param int $tokenLifetime * @return string */ public function generateToken($tokenLifetime): string @@ -214,6 +223,7 @@ public function verifyEmail(): bool } } + /** @return array */ public function get() { $userData = $this->data; @@ -255,9 +265,13 @@ public function setDb($db) public function __toString() { - return json_encode($this->get()); + return json_encode($this->get()) ?: ''; } + /** + * @param string $name + * @return ?mixed + */ public function __get($name) { // using data instead of get() here because @@ -266,16 +280,29 @@ public function __get($name) return $this->data[$name] ?? null; } + /** + * @param string $name + * @param mixed $value + * @return void + */ public function __set($name, $value) { $this->data[$name] = $value; } + /** + * @param string $name + * @return bool + */ public function __isset($name) { return isset($this->data[$name]); } + /** + * @param string $name + * @return void + */ public function __unset($name) { unset($this->data[$name]); diff --git a/src/Auth/UsesRoles.php b/src/Auth/UsesRoles.php index ba82196..c4b3690 100644 --- a/src/Auth/UsesRoles.php +++ b/src/Auth/UsesRoles.php @@ -12,20 +12,16 @@ */ trait UsesRoles { - /** - * User Permissions - */ + /** @var string[] User Permissions */ protected array $permissions = []; - /** - * User Roles - */ + /** @var string[] User Roles */ protected array $roles = []; /** * Assign new role to user * - * @param string|array $role The role to assign + * @param string|string[] $role The role to assign * @return bool */ public function assign($role): bool @@ -55,7 +51,7 @@ public function assign($role): bool /** * Check if user has a permission - * @param string|array $permission The permission(s) to check + * @param string|string[] $permission The permission(s) to check * @return bool */ public function can($permission): bool @@ -69,6 +65,7 @@ public function can($permission): bool /** * Check if a user does not have a permission + * @param string[] $permission */ public function cannot($permission): bool { @@ -77,7 +74,7 @@ public function cannot($permission): bool /** * Check if user has a role - * @param string|array $role The role(s) to check + * @param string|string[] $role The role(s) to check * @return bool */ public function is($role): bool @@ -91,6 +88,7 @@ public function is($role): bool /** * Check if user does not have a role + * @param string[] $role */ public function isNot($role): bool { @@ -99,7 +97,7 @@ public function isNot($role): bool /** * Return the user's roles - * @return array + * @return string[] */ public function roles(): array { @@ -108,7 +106,7 @@ public function roles(): array /** * Return the user's permissions - * @return array + * @return string[] */ public function permissions(): array { @@ -117,7 +115,7 @@ public function permissions(): array /** * Remove a role from a user - * @param string|array $role The role(s) to revoke + * @param string|string[] $role The role(s) to revoke */ public function unassign($role): void { @@ -140,7 +138,7 @@ public function unassign($role): void /** * Set the roles and permissions for a user * - * @param string|array $roles The role(s) to set + * @param string|string[] $roles The role(s) to set */ protected function setRolesAndPermissions($roles): void { @@ -166,8 +164,8 @@ protected function setRolesAndPermissions($roles): void /** * Get the permissions for a role * - * @param string|array $role - * @return array + * @param string|string[] $roles + * @return string[] */ protected function getRolePermissions($roles): array { diff --git a/src/Auth/UsesSubscriptions.php b/src/Auth/UsesSubscriptions.php index 2dabad7..2c77004 100644 --- a/src/Auth/UsesSubscriptions.php +++ b/src/Auth/UsesSubscriptions.php @@ -14,15 +14,14 @@ trait UsesSubscriptions { /** * User Subscription - * @var array|null + * @var array|null */ protected $subscription = null; /** * Get current subscription * - * @param string|array $subscription The subscription to assign - * @return array|null + * @return mixed[]|null */ public function subscription(): ?array { @@ -50,7 +49,7 @@ public function subscription(): ?array */ public function hasSubscription(): bool { - return $this->subscription() && $this->subscription['status'] !== \Leaf\Billing\Subscription::STATUS_CANCELLED; + return $this->subscription() && ($this->subscription['status'] ?? null) !== \Leaf\Billing\Subscription::STATUS_CANCELLED; } /** @@ -59,7 +58,7 @@ public function hasSubscription(): bool */ public function hasActiveSubscription(): bool { - return $this->subscription() && ($this->subscription['status'] === \Leaf\Billing\Subscription::STATUS_ACTIVE || $this->subscription['status'] === \Leaf\Billing\Subscription::STATUS_TRIAL); + return $this->subscription() && (($this->subscription['status'] ?? null) === \Leaf\Billing\Subscription::STATUS_ACTIVE || ($this->subscription['status'] ?? null) === \Leaf\Billing\Subscription::STATUS_TRIAL); } /** @@ -74,6 +73,6 @@ public function cancelSubscription(): bool return true; } - return billing()->cancelSubcription($subscription['subscription_id']); + return billing()->cancelSubscription($subscription['subscription_id']); } } diff --git a/tests/table.test.php b/tests/table.test.php index ff235cd..f33216b 100644 --- a/tests/table.test.php +++ b/tests/table.test.php @@ -59,6 +59,7 @@ $this->fail(json_encode($auth->errors())); } + // TODO: $response is a bool, not a bool|array $response = $auth->update([ 'username' => 'test-user55', 'email' => 'test-user55@example.com', diff --git a/tests/user.test.php b/tests/user.test.php index 4086518..c100b2e 100644 --- a/tests/user.test.php +++ b/tests/user.test.php @@ -63,7 +63,7 @@ expect($auth->user())->toBeInstanceOf(\Leaf\Auth\User::class); expect($auth->user()->username)->toBe($testUser['username']); - $auth->logout(function ($auth) use ($testUser) { + $auth->logout(function ($auth) { expect($auth)->toBeInstanceOf(\Leaf\Auth::class); });