diff --git a/readme.md b/readme.md index 563cd13..32a3047 100644 --- a/readme.md +++ b/readme.md @@ -63,7 +63,7 @@ $moderatorRole = \HttpOz\Roles\Models\Role::create([ ]); ``` -> Because of `Slugable` trait, if you make a mistake and for example leave a space in slug parameter, it'll be replaced with a dot automatically, because of `str_slug` function. +> Because of `Sluggable` trait, if you make a mistake and for example leave a space in slug parameter, it'll be replaced with a dot automatically, because of `str_slug` function. ### Attaching And Detaching Roles @@ -262,9 +262,13 @@ $router->get('/example', [ ]); ``` -It throws `\HttpOz\Roles\Exceptions\RoleDeniedException` or `\HttpOz\Roles\Exceptions\GroupDeniedException` exceptions if it goes wrong. +There are two kinds of exceptions that are thrown when a user fails to meet the role or group requirements: +- `\HttpOz\Roles\Exceptions\RoleDeniedException` +- `\HttpOz\Roles\Exceptions\GroupDeniedException` -You can catch these exceptions inside `app/Exceptions/Handler.php` file and do whatever you want. You can control the error page that your application users see when they try to open a page their role is not allowed to. This package already has a view bundled with it that should have been published to `resources/views/vendor/roles/error.blade.php` when you published the package. Simply add the below condition inside your `app\Exceptions\Handler.php`'s render function. Feel free to point to another view of your choice. +You can catch these exceptions inside `app/Exceptions/Handler.php` file and do whatever you want. + +You can control the error page that your application users see when they try to open a page their role is not allowed to. This package already has a view bundled with it that should have been published to `resources/views/vendor/roles/error.blade.php` when you published the package. Simply add the below condition inside your `app\Exceptions\Handler.php`'s render function. Feel free to point to another view of your choice. ```php /** diff --git a/src/Contracts/HasRole.php b/src/Contracts/HasRole.php index dbec437..b23349e 100644 --- a/src/Contracts/HasRole.php +++ b/src/Contracts/HasRole.php @@ -3,91 +3,63 @@ namespace HttpOz\Roles\Contracts; use HttpOz\Roles\Models\Role; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; interface HasRole { /** * User belongs to many roles. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function roles(); + public function roles(): BelongsToMany; /** * Get all roles as collection. - * - * @return \Illuminate\Database\Eloquent\Collection */ - public function getRoles(); + public function getRoles(): Collection; /** * Check if the user has a role or roles. - * - * @param int|string|array $role - * @param bool $all - * @return bool */ - public function isRole($role, $all = false); + public function isRole(array|int|string $role, bool $all = false): bool; /** * Check if the user has all roles. - * - * @param int|string|array $role - * @return bool */ - public function isAll($role); + public function isAll(array|int|string $role): bool; /** * Check if the user has at least one role. - * - * @param int|string|array $role - * @return bool */ - public function isOne($role); + public function isOne(array|int|string $role): bool; /** * Check if the user has role. - * - * @param int|string $role - * @return bool */ - public function hasRole($role); + public function hasRole(int|string $role): bool; /** * Attach role to a user. - * - * @param int|Role $role - * @return bool */ - public function attachRole($role): bool; + public function attachRole(int|Role $role): bool; /** * Detach role from a user. - * - * @param int|Role $role - * @return int */ - public function detachRole($role); + public function detachRole(int|Role $role): int; /** * Sync roles for a user. - * - * @param array|Role[]|\Illuminate\Database\Eloquent\Collection $roles - * @return array */ - public function syncRoles($roles); + public function syncRoles(Collection|array $roles): array; /** * Detach all roles from a user. - * - * @return int */ - public function detachAllRoles(); + public function detachAllRoles(): int; /** * Get role group of a user. - * - * @return string */ - public function group(); + public function group(): string; } diff --git a/src/Contracts/RoleHasRelations.php b/src/Contracts/RoleHasRelations.php index beda738..80f65a9 100644 --- a/src/Contracts/RoleHasRelations.php +++ b/src/Contracts/RoleHasRelations.php @@ -3,14 +3,16 @@ namespace HttpOz\Roles\Contracts; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; + interface RoleHasRelations { /** * Role belongs to many users. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function users(); + public function users(): BelongsToMany; } diff --git a/src/Exceptions/GroupDeniedException.php b/src/Exceptions/GroupDeniedException.php index 20facbf..ba96e43 100644 --- a/src/Exceptions/GroupDeniedException.php +++ b/src/Exceptions/GroupDeniedException.php @@ -7,10 +7,8 @@ class GroupDeniedException extends Exception { /** * Create a new group denied exception instance. - * - * @param string $group */ - public function __construct($group) + public function __construct(string $group) { parent::__construct(); $this->message = sprintf("You are not in the required [%s] group.", $group); diff --git a/src/Exceptions/RoleDeniedException.php b/src/Exceptions/RoleDeniedException.php index 039619c..91247ea 100644 --- a/src/Exceptions/RoleDeniedException.php +++ b/src/Exceptions/RoleDeniedException.php @@ -8,10 +8,8 @@ class RoleDeniedException extends Exception { /** * Create a new role denied exception instance. - * - * @param string $role */ - public function __construct($role) + public function __construct(string $role) { parent::__construct(); $this->message = sprintf("You don't have a required ['%s'] role.", $role); diff --git a/src/Middleware/VerifyGroup.php b/src/Middleware/VerifyGroup.php index d69045c..97e7eef 100644 --- a/src/Middleware/VerifyGroup.php +++ b/src/Middleware/VerifyGroup.php @@ -5,36 +5,28 @@ use Closure; use Illuminate\Contracts\Auth\Guard; use HttpOz\Roles\Exceptions\GroupDeniedException; +use Illuminate\Http\Request; class VerifyGroup { - /** - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - /** * Create a new filter instance. - * - * @param \Illuminate\Contracts\Auth\Guard $auth - * @return void */ - public function __construct(Guard $auth) + public function __construct(protected Guard $auth) { - $this->auth = $auth; } /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param Request $request + * @param Closure $next * @param int $group * @return mixed - * @throws \HttpOz\Roles\Exceptions\GroupDeniedException + * @throws GroupDeniedException */ - public function handle($request, Closure $next, $group) + public function handle(Request $request, Closure $next, int $group): mixed { if ($this->auth->check() && $this->auth->user()->group() == $group) { return $next($request); diff --git a/src/Middleware/VerifyRole.php b/src/Middleware/VerifyRole.php index 6844bf9..2a04111 100644 --- a/src/Middleware/VerifyRole.php +++ b/src/Middleware/VerifyRole.php @@ -5,36 +5,30 @@ use Closure; use Illuminate\Contracts\Auth\Guard; use HttpOz\Roles\Exceptions\RoleDeniedException; +use Illuminate\Http\Request; class VerifyRole { - /** - * @var \Illuminate\Contracts\Auth\Guard - */ - protected $auth; - /** * Create a new filter instance. * - * @param \Illuminate\Contracts\Auth\Guard $auth + * @param Guard $auth * @return void */ - public function __construct(Guard $auth) + public function __construct(protected Guard $auth) { - $this->auth = $auth; } /** * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next + * @param Request $request + * @param Closure $next * @param int|string $role * @return mixed - * @throws \HttpOz\Roles\Exceptions\RoleDeniedException + * @throws RoleDeniedException */ - public function handle($request, Closure $next, $role) + public function handle(Request $request, Closure $next, int|string $role): mixed { if ($this->auth->check() && $this->auth->user()->isRole($role)) { return $next($request); diff --git a/src/Models/Role.php b/src/Models/Role.php index 4dd3a27..198ad9b 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -4,49 +4,62 @@ use HttpOz\Roles\Database\Factories\RoleFactory; -use HttpOz\Roles\Traits\Sluggable; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use HttpOz\Roles\Traits\RoleHasRelations; use HttpOz\Roles\Contracts\RoleHasRelations as RoleHasRelationsContract; +use Illuminate\Support\Str; -class Role extends Model implements RoleHasRelationsContract { - - use Sluggable, RoleHasRelations, HasFactory; - - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = ['name', 'slug', 'description', 'group']; - - /** - * Create a new model instance. - * - * @param array $attributes - * - * @return void - */ - public function __construct(array $attributes = []) { - parent::__construct($attributes); - if ($connection = config('roles.connection')) { - $this->connection = $connection; +class Role extends Model implements RoleHasRelationsContract +{ + + use RoleHasRelations, HasFactory; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = ['name', 'slug', 'description', 'group']; + + /** + * Create a new model instance. + */ + public function __construct(array $attributes = []) + { + parent::__construct($attributes); + if ($connection = config('roles.connection')) { + $this->connection = $connection; + } + } + + /** + * @param $slug + * @return mixed + */ + public static function findBySlug($slug): self + { + return self::where('slug', $slug)->first(); } - } - /** - * @param $slug - * @return mixed - */ - public static function findBySlug($slug) { - return self::where('slug', $slug)->first(); - } + protected static function newFactory(): RoleFactory + { + return new RoleFactory(); + } - protected static function newFactory() - { - return new RoleFactory(); - } + public function Slug(): Attribute + { + return new Attribute( + set: fn ($value) => Str::slug($value, config('roles.separator')), + ); + } + public function Group(): Attribute + { + return new Attribute( + set: fn ($value) => Str::slug($value, config('roles.separator')), + ); + } } diff --git a/src/Observers/RoleObserver.php b/src/Observers/RoleObserver.php new file mode 100644 index 0000000..b2519c0 --- /dev/null +++ b/src/Observers/RoleObserver.php @@ -0,0 +1,17 @@ +slug = Str::slug($role->name, config('roles.separator')); + } +} diff --git a/src/RolesServiceProvider.php b/src/RolesServiceProvider.php index b218502..50c5f8f 100644 --- a/src/RolesServiceProvider.php +++ b/src/RolesServiceProvider.php @@ -37,6 +37,7 @@ public function boot() public function register() { $this->mergeConfigFrom(__DIR__ . '/../config/roles.php', 'roles'); + $this->app->register(); } public function registerBladeExtensions() diff --git a/src/Traits/HasRole.php b/src/Traits/HasRole.php index 3aa86ac..e260a2e 100644 --- a/src/Traits/HasRole.php +++ b/src/Traits/HasRole.php @@ -3,230 +3,211 @@ namespace HttpOz\Roles\Traits; use HttpOz\Roles\Models\Role; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Str; use Illuminate\Support\Facades\Cache; -trait HasRole { +trait HasRole +{ - /** - * Property for caching roles. - * - * @var \Illuminate\Database\Eloquent\Collection|null - */ - protected $roles; + /** + * Property for caching roles. + */ + protected ?Collection $roles; - /** - * User belongs to many roles. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - public function roles() { - return $this->belongsToMany( config( 'roles.models.role' ), 'role_user', 'user_id', 'role_id' )->withTimestamps(); - } + /** + * User belongs to many roles. + * + * @return BelongsToMany + */ + public function roles(): BelongsToMany + { + return $this->belongsToMany(config('roles.models.role'), 'role_user', 'user_id', 'role_id')->withTimestamps(); + } - /** - * Get all roles as collection. - * - * @return \Illuminate\Database\Eloquent\Collection - */ - public function getRoles() { - if ( $this->cacheEnabled() ) { - if ( is_null( $this->getCachedRoles() ) ) { - Cache::remember( 'roles.user_' . $this->id, config( 'roles.cache.expiry' ), function () { - return $this->roles()->get(); - } ); - } - - return $this->getCachedRoles(); - } else { - return $this->roles()->get(); - } - } + /** + * Get all roles as collection. + * + * @return Collection + */ + public function getRoles(): Collection + { + if ($this->cacheEnabled()) { + if (is_null($this->getCachedRoles())) { + Cache::remember('roles.user_' . $this->id, config('roles.cache.expiry'), function () { + return $this->roles()->get(); + }); + } + + return $this->getCachedRoles(); + } else { + return $this->roles()->get(); + } + } - /** - * Check if the user has a role or roles. - * - * @param int|string|array $role - * @param bool $all - * - * @return bool - */ - public function isRole( $role, $all = false ) { - if ( $this->isPretendEnabled() ) { - return $this->pretend( 'isRole' ); - } + /** + * Check if the user has a role or roles. + * + * @param array|int|string $role + * @param bool $all + * + * @return bool + */ + public function isRole(array|int|string $role, bool $all = false): bool + { + if ($this->isPretendEnabled()) { + return $this->pretend('isRole'); + } - return $this->{$this->getMethodName( 'is', $all )}( $role ); - } + return $this->{$this->getMethodName('is', $all)}($role); + } - /** - * Check if the user has at least one role. - * - * @param int|string|array $role - * - * @return bool - */ - public function isOne( $role ) { - foreach ( $this->getArrayFrom( $role ) as $role ) { - if ( $this->hasRole( $role ) ) { - return true; - } - } + /** + * Check if the user has at least one role. + * + * @param array|int|string $role + * + * @return bool + */ + public function isOne(array|int|string $role): bool + { + foreach ($this->getArrayFrom($role) as $role) { + if ($this->hasRole($role)) { + return true; + } + } - return false; - } + return false; + } - /** - * Check if the user has all roles. - * - * @param int|string|array $role - * - * @return bool - */ - public function isAll( $role ) { - foreach ( $this->getArrayFrom( $role ) as $role ) { - if ( ! $this->hasRole( $role ) ) { - return false; - } - } + /** + * Check if the user has all roles. + * + * @param array|int|string $role + * + * @return bool + */ + public function isAll(array|int|string $role): bool + { + foreach ($this->getArrayFrom($role) as $role) { + if (!$this->hasRole($role)) { + return false; + } + } - return true; - } + return true; + } - /** - * Check if the user has role. - * - * @param int|string $role - * - * @return bool - */ - public function hasRole( $role ) { - if ( $this->getRoles() ) { - return $this->getRoles()->contains( function ( $value, $key ) use ( $role ) { - return $role == $value->id || Str::is( $role, $value->slug ); - } ); - } else { - return false; - } - } + /** + * Check if the user has role. + */ + public function hasRole(int|string $role): bool + { + if ($this->getRoles()) { + return $this->getRoles()->contains(function ($value, $key) use ($role) { + return $role == $value->id || Str::is($role, $value->slug); + }); + } else { + return false; + } + } /** * Attach role to a user. - * - * @param int|Role $role - * - * @return bool */ - public function attachRole($role): bool + public function attachRole(int|Role $role): bool { - if ( ! $this->getRoles()->contains( $role ) ) { - $this->clearCached(); - $this->roles()->attach( $role ); - } + if (!$this->getRoles()->contains($role)) { + $this->clearCached(); + $this->roles()->attach($role); + } - return true; - } + return true; + } - /** - * Detach role from a user. - * - * @param int|Role $role - * - * @return int - */ - public function detachRole( $role ) { - $this->roles()->detach( $role ); - $this->clearCached(); + /** + * Detach role from a user. + */ + public function detachRole(int|Role $role): int + { + $this->roles()->detach($role); + $this->clearCached(); - return true; - } + return true; + } - /** - * Detach all roles from a user. - * - * @return int - */ - public function detachAllRoles() { - $this->clearCached(); + /** + * Detach all roles from a user. + */ + public function detachAllRoles(): int + { + $this->clearCached(); - return $this->roles()->detach(); - } + return $this->roles()->detach(); + } - /** - * Sync roles for a user. - * - * @param array|Role[]|\Illuminate\Database\Eloquent\Collection $roles - * - * @return array - */ - public function syncRoles( $roles ) { - $this->clearCached(); + /** + * Sync roles for a user. + * + * @param array|Collection|Role[] $roles + * + * @return array + */ + public function syncRoles(Collection|array $roles): array + { + $this->clearCached(); - return $this->roles()->sync( $roles ); - } + return $this->roles()->sync($roles); + } - /** - * Get role group of a user. - * - * @return int - */ - public function group() { - return ( $role = $this->getRoles()->sortBy( 'group' )->first() ) ? $role->group : 'default'; - } + /** + * Get role group of a user. + */ + public function group(): string + { + return ($role = $this->getRoles()->sortBy('group')->first()) ? $role->group : 'default'; + } - /** - * Check if user belongs to a given group - * - * @param string $group - * - * @return boolean - */ - public function inGroup( $group ) { - return ( $this->getRoles()->where( 'group', 'LIKE', $group )->first() ? true : false ); - } + /** + * Check if user belongs to a given group + */ + public function inGroup(string $group): bool + { + return ($this->getRoles()->where('group', 'LIKE', $group)->first() ? true : false); + } - /** - * Check if pretend option is enabled. - * - * @return bool - */ - private function isPretendEnabled() { - return (bool) config( 'roles.pretend.enabled' ); - } + /** + * Check if pretend option is enabled. + */ + private function isPretendEnabled(): bool + { + return (bool)config('roles.pretend.enabled'); + } - /** - * Allows to pretend or simulate package behavior. - * - * @param string $option - * - * @return bool - */ - private function pretend( $option ) { - return (bool) config( 'roles.pretend.options.' . $option ); - } + /** + * Allows to pretend or simulate package behavior. + */ + private function pretend(string $option): bool + { + return (bool)config('roles.pretend.options.' . $option); + } - /** - * Get method name. - * - * @param string $methodName - * @param bool $all - * - * @return string - */ - private function getMethodName( $methodName, $all ) { - return ( (bool) $all ) ? $methodName . 'All' : $methodName . 'One'; - } + /** + * Get method name. + */ + private function getMethodName(string $methodName, bool $all): string + { + return ((bool)$all) ? $methodName . 'All' : $methodName . 'One'; + } - /** - * Get an array from argument. - * - * @param int|string|array $argument - * - * @return array - */ - private function getArrayFrom( $argument ) { - return ( ! is_array( $argument ) ) ? preg_split( '/ ?[,|] ?/', $argument ) : $argument; - } + /** + * Get an array from argument. + */ + private function getArrayFrom(array|int|string $argument): array + { + return (!is_array($argument)) ? preg_split('/ ?[,|] ?/', $argument) : $argument; + } /** * Handle dynamic method calls. @@ -241,23 +222,25 @@ public function __call( $method, $parameters ) { return $this->isRole( Str::snake( substr( $method, 2 ), config( 'roles.separator' ) ) ); } - return parent::__call( $method, $parameters ); - } + return parent::__call($method, $parameters); + } - /** - * Get flag on whether role caching is enabled - * @return boolean - */ - private function cacheEnabled() { - return (bool) config( 'roles.cache.enabled' ); - } + /** + * Get flag on whether role caching is enabled + */ + private function cacheEnabled(): bool + { + return (bool)config('roles.cache.enabled'); + } - private function getCachedRoles() { - return Cache::get( 'roles.user_' . $this->id ); - } + private function getCachedRoles() + { + return Cache::get('roles.user_' . $this->id); + } - private function clearCached() { - return ( $this->cacheEnabled() ) ? Cache::forget( 'roles.user_' . $this->id ) : true; - } + private function clearCached(): bool + { + return ($this->cacheEnabled()) ? Cache::forget('roles.user_' . $this->id) : true; + } } diff --git a/src/Traits/RoleHasRelations.php b/src/Traits/RoleHasRelations.php index f3d25cf..51f4612 100644 --- a/src/Traits/RoleHasRelations.php +++ b/src/Traits/RoleHasRelations.php @@ -3,26 +3,24 @@ namespace HttpOz\Roles\Traits; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; + trait RoleHasRelations { /** * Role belongs to many users. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function users() + public function users(): BelongsToMany { return $this->belongsToMany(config('auth.providers.users.model'), 'role_user', 'role_id', 'user_id')->withTimestamps(); } - - + + /** * Role belongs to many users with trashed. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function usersWithTrashed() + public function usersWithTrashed(): BelongsToMany { return $this->belongsToMany(config('auth.providers.users.model'), 'role_user', 'role_id', 'user_id')->withTrashed()->withTimestamps(); } diff --git a/src/Traits/Sluggable.php b/src/Traits/Sluggable.php deleted file mode 100644 index 91e3845..0000000 --- a/src/Traits/Sluggable.php +++ /dev/null @@ -1,26 +0,0 @@ -attributes['slug'] = Str::slug($value, config('roles.separator')); - } - - public function setGroupAttribute($value) - { - $this->attributes['group'] = Str::slug($value, config('roles.separator')); - } - -} diff --git a/tests/Stubs/User.php b/tests/Stubs/User.php index 01a451c..1d1c264 100644 --- a/tests/Stubs/User.php +++ b/tests/Stubs/User.php @@ -25,7 +25,7 @@ public function getAuthIdentifiersName(): array - protected static function newFactory() + protected static function newFactory(): UserFactory { return new UserFactory(); } diff --git a/tests/TestCase.php b/tests/TestCase.php index c60b4fb..ee40dd8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -66,10 +66,8 @@ public function getEnvironmentSetUp($app) /** * Set up the database. - * - * @param Application $app */ - protected function setUpDatabase($app) + protected function setUpDatabase(Application $app) { include_once __DIR__ . '/../database/migrations/2016_09_02_000000_create_roles_table.php'; include_once __DIR__ . '/../database/migrations/2016_09_02_000000_create_role_user_table.php'; diff --git a/tests/Unit/RoleTest.php b/tests/Unit/RoleTest.php index 0573083..c097e55 100644 --- a/tests/Unit/RoleTest.php +++ b/tests/Unit/RoleTest.php @@ -16,4 +16,25 @@ public function testRoleCanBeFoundBySlug() $this->assertEquals($createdRole->name, $foundRole->name); $this->assertEquals($createdRole->slug, $foundRole->slug); } + + /** + * @dataProvider roleProvider + */ + public function testMinimalInputRoleCreation(string $name, ?string $slug, string $expected): void + { + $role = Role::create(['name' => $name, 'slug' => $slug]); + + $this->assertEquals($expected, $role->slug); + } + + public function roleProvider(): array + { + return [ + ['Big Fish', 'Big Fish', 'big.fish'], + ['Small Fish', 'small.fish', 'small.fish'], + ['Medium Fish', 'medium_fish', 'mediumfish'], + ['Dashing Fish', 'dashing-fish', 'dashing.fish'], + ['Dashing Fish', null, 'dashing.fish'] + ]; + } }