diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 5940c1979..8c6da4eb4 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,8 @@
-blank_issues_enabled: false
+blank_issues_enabled: true
contact_links:
- - name: Ask a Question
- url: https://github.com/spatie/laravel-permission/discussions/new?category=q-a
- about: Ask the community for help
- name: Feature Request
url: https://github.com/spatie/laravel-permission/discussions/new?category=ideas
about: Share ideas for new features
+ - name: Ask a Question
+ url: https://github.com/spatie/laravel-permission/discussions/new?category=q-a
+ about: Ask the community for help
diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml
index 60dc90d6f..e3be7f171 100644
--- a/.github/workflows/fix-php-code-style-issues.yml
+++ b/.github/workflows/fix-php-code-style-issues.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
ref: ${{ github.head_ref }}
@@ -19,6 +19,6 @@ jobs:
uses: aglipanci/laravel-pint-action@v2
- name: Commit changes
- uses: stefanzweifel/git-auto-commit-action@v5
+ uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: Fix styling
diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml
index be7882397..17bbcdf3f 100644
--- a/.github/workflows/phpstan.yml
+++ b/.github/workflows/phpstan.yml
@@ -15,7 +15,7 @@ jobs:
name: phpstan
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 3f47884ba..2dd07ca39 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- php: [8.4, 8.3, 8.2, 8.1, 8.0]
+ php: [8.5, 8.4, 8.3, 8.2, 8.1, 8.0]
laravel: ["^12.0", "^11.0", "^10.0", "^9.0", "^8.12"]
dependency-version: [prefer-lowest, prefer-stable]
include:
@@ -34,16 +34,22 @@ jobs:
php: 8.0
- laravel: "^10.0"
php: 8.0
+ - laravel: "^10.0"
+ php: 8.5
+ - laravel: "^9.0"
+ php: 8.5
- laravel: "^8.12"
php: 8.3
- laravel: "^8.12"
php: 8.4
+ - laravel: "^8.12"
+ php: 8.5
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/test-cache-drivers.yml b/.github/workflows/test-cache-drivers.yml
index 2bfd5b28a..3b9398c9c 100644
--- a/.github/workflows/test-cache-drivers.yml
+++ b/.github/workflows/test-cache-drivers.yml
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup PHP
uses: shivammathur/setup-php@v2
diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml
index 0cdea2336..cb7fa9f8b 100644
--- a/.github/workflows/update-changelog.yml
+++ b/.github/workflows/update-changelog.yml
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
ref: main
@@ -21,7 +21,7 @@ jobs:
release-notes: ${{ github.event.release.body }}
- name: Commit updated CHANGELOG
- uses: stefanzweifel/git-auto-commit-action@v5
+ uses: stefanzweifel/git-auto-commit-action@v7
with:
branch: main
commit_message: Update CHANGELOG
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3778ef834..b76054f36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,63 @@
All notable changes to `laravel-permission` will be documented in this file
+## 6.21.0 - 2025-07-23
+
+### What's Changed
+
+* Allow removing multiple roles with the `removeRole` method by @TobMoeller in https://github.com/spatie/laravel-permission/pull/2859
+* [Docs] Correct middleware order for documentation example in `teams-permissions.md` by @dualklip in https://github.com/spatie/laravel-permission/pull/2863
+
+### New Contributors
+
+* @dualklip made their first contribution in https://github.com/spatie/laravel-permission/pull/2863
+* @TobMoeller made their first contribution in https://github.com/spatie/laravel-permission/pull/2859
+
+**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.20.0...6.21.0
+
+## 6.20.0 - 2025-06-14
+
+### What's Changed
+
+* Add translations support for exception messages by @nAa6666 in https://github.com/spatie/laravel-permission/pull/2852
+
+### New Contributors
+
+* @nAa6666 made their first contribution in https://github.com/spatie/laravel-permission/pull/2852
+
+**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.19.0...6.20.0
+
+## 6.19.0 - 2025-05-31
+
+### What's Changed
+
+* Revert "Remove `collectPermissions` that is not being assigned" by @erikn69 in https://github.com/spatie/laravel-permission/pull/2851
+* Fix guard_name not used to set default attribute in Role and Permission model by @Ken-vdE in https://github.com/spatie/laravel-permission/pull/2837
+
+**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.18.0...6.19.0
+
+## 6.18.0 - 2025-05-14
+
+### What's Changed
+
+* refactor exception throwing in migration file to use throw_if() by @ccaioadriano in https://github.com/spatie/laravel-permission/pull/2819
+* Fix: Example in config comment includes `permission.` prefix on `wildcard_permission` key by @jerrens in https://github.com/spatie/laravel-permission/pull/2835
+* Fix commented config key typo by @erikn69 in https://github.com/spatie/laravel-permission/pull/2844
+* Remove `collectPermissions` that is not being assigned by @JHWelch in https://github.com/spatie/laravel-permission/pull/2840
+* [Docs] Update multiple-guards.md by @Ken-vdE in https://github.com/spatie/laravel-permission/pull/2836
+* [Docs] Remove extra period by @coreyhn in https://github.com/spatie/laravel-permission/pull/2841
+* Add JetAdmin as UI Option. by @aliqasemzadeh in https://github.com/spatie/laravel-permission/pull/2814
+
+### New Contributors
+
+* @ccaioadriano made their first contribution in https://github.com/spatie/laravel-permission/pull/2819
+* @coreyhn made their first contribution in https://github.com/spatie/laravel-permission/pull/2841
+* @jerrens made their first contribution in https://github.com/spatie/laravel-permission/pull/2835
+* @Ken-vdE made their first contribution in https://github.com/spatie/laravel-permission/pull/2836
+* @JHWelch made their first contribution in https://github.com/spatie/laravel-permission/pull/2840
+
+**Full Changelog**: https://github.com/spatie/laravel-permission/compare/6.17.0...6.18.0
+
## 6.17.0 - 2025-04-09
### What's Changed
@@ -1002,6 +1059,10 @@ The following changes are not "breaking", but worth making the updates to your a
+
+
+
+
@@ -1080,6 +1141,10 @@ The following changes are not "breaking", but worth making the updates to your a
+
+
+
+
diff --git a/README.md b/README.md
index a8a54d3fd..413063953 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,14 @@
-
+
Associate users with permissions and roles
[](https://packagist.org/packages/spatie/laravel-permission)
-[](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain)
+[](https://github.com/spatie/laravel-permission/actions?query=workflow%3ATests+branch%3Amain)
[](https://packagist.org/packages/spatie/laravel-permission)
diff --git a/config/permission.php b/config/permission.php
index 8e84e9d53..f39f6b5bf 100644
--- a/config/permission.php
+++ b/config/permission.php
@@ -172,7 +172,7 @@
* The class to use for interpreting wildcard permissions.
* If you need to modify delimiters, override the class and specify its name here.
*/
- // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class,
+ // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
/* Cache-specific settings */
diff --git a/docs/advanced-usage/seeding.md b/docs/advanced-usage/seeding.md
index 30dc21bf2..51c56e8d2 100644
--- a/docs/advanced-usage/seeding.md
+++ b/docs/advanced-usage/seeding.md
@@ -7,7 +7,7 @@ weight: 2
You may discover that it is best to flush this package's cache **BEFORE seeding, to avoid cache conflict errors**.
-And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER creating any roles/permissions as well, before assigning or granting them.**.
+And if you use the `WithoutModelEvents` trait in your seeders, flush it **AFTER creating any roles/permissions as well, before assigning or granting them**.
```php
// reset cached roles and permissions
diff --git a/docs/advanced-usage/ui-options.md b/docs/advanced-usage/ui-options.md
index ebfb552ef..4e1d5d41d 100644
--- a/docs/advanced-usage/ui-options.md
+++ b/docs/advanced-usage/ui-options.md
@@ -31,3 +31,5 @@ If you decide you need a UI, even if it's not for creating/editing role/permissi
- [LiveWire Base Admin Panel](https://github.com/aliqasemzadeh/bap) User management by [AliQasemzadeh](https://github.com/aliqasemzadeh)
- [JetAdmin](https://github.com/aliqasemzadeh/jetadmin) JetAdmin use laravel livewire starter kit and manage permissions. [AliQasemzadeh](https://github.com/aliqasemzadeh)
+
+- [QuickPanel](https://github.com/aliqasemzadeh/quickpanel) Quick Panel (TALL Flowbite Starter Kit). [AliQasemzadeh](https://github.com/aliqasemzadeh)
diff --git a/docs/basic-usage/direct-permissions.md b/docs/basic-usage/direct-permissions.md
index e171c8163..6b4987ec5 100644
--- a/docs/basic-usage/direct-permissions.md
+++ b/docs/basic-usage/direct-permissions.md
@@ -46,7 +46,7 @@ Like all permissions assigned via roles, you can check if a user has a permissio
$user->can('edit articles');
```
-> NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny`, `canAll` instead.
+> NOTE: The following `hasPermissionTo`, `hasAnyPermission`, `hasAllPermissions` functions do not support Super-Admin functionality. Use `can`, `canAny` instead.
You can check if a user has a permission:
diff --git a/docs/basic-usage/middleware.md b/docs/basic-usage/middleware.md
index 259e9214f..e9173ec59 100644
--- a/docs/basic-usage/middleware.md
+++ b/docs/basic-usage/middleware.md
@@ -22,7 +22,7 @@ This package comes with `RoleMiddleware`, `PermissionMiddleware` and `RoleOrPerm
You can register their aliases for easy reference elsewhere in your app:
-In Laravel 11 open `/bootstrap/app.php` and register them there:
+In Laravel 11+ open `/bootstrap/app.php` and register them there:
```php
->withMiddleware(function (Middleware $middleware) {
diff --git a/docs/basic-usage/multiple-guards.md b/docs/basic-usage/multiple-guards.md
index d9360d6cf..062e49fad 100644
--- a/docs/basic-usage/multiple-guards.md
+++ b/docs/basic-usage/multiple-guards.md
@@ -20,7 +20,8 @@ Note that this package requires you to register a permission name (same for role
If your app structure does NOT differentiate between guards when it comes to roles/permissions, (ie: if ALL your roles/permissions are the SAME for ALL guards), you can override the `getDefaultGuardName` function by adding it to your User model, and specifying your desired `$guard_name`. Then you only need to create roles/permissions for that single `$guard_name`, not duplicating them. The example here sets it to `web`, but use whatever your application's default is:
```php
- protected function getDefaultGuardName(): string { return 'web'; }
+ protected string $guard_name = 'web';
+ protected function getDefaultGuardName(): string { return $this->guard_name; }
````
diff --git a/docs/basic-usage/teams-permissions.md b/docs/basic-usage/teams-permissions.md
index ad220a1ac..15095a7ab 100644
--- a/docs/basic-usage/teams-permissions.md
+++ b/docs/basic-usage/teams-permissions.md
@@ -77,12 +77,15 @@ class AppServiceProvider extends ServiceProvider
$kernel = app()->make(Kernel::class);
$kernel->addToMiddlewarePriorityBefore(
- SubstituteBindings::class,
YourCustomMiddlewareClass::class,
+ SubstituteBindings::class,
);
}
}
```
+### Using LiveWire?
+
+You may need to register your team middleware as Persisted in Livewire. See [Livewire docs: Configuring Persistent Middleware](https://livewire.laravel.com/docs/security#configuring-persistent-middleware)
## Roles Creating
@@ -99,7 +102,7 @@ Role::create(['name' => 'reader', 'team_id' => 1]);
Role::create(['name' => 'reviewer']);
```
-## Roles/Permissions Assignment & Removal
+## Roles/Permissions Assignment and Removal
The role/permission assignment and removal for teams are the same as without teams, but they take the global `team_id` which is set on login.
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 022878407..e6a4b0f89 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -17,3 +17,6 @@ parameters:
# wildcard permissions:
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getWildcardClass#'
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::getAllPermissions#'
+ # contract checks:
+ - '#Call to function is_a\(\) with (.*) and ''Spatie\\\\Permission\\\\Contracts\\\\Permission'' will always evaluate to true\.$#'
+ - '#Call to function is_a\(\) with (.*) and ''Spatie\\\\Permission\\\\Contracts\\\\Role'' will always evaluate to true\.$#'
diff --git a/src/Events/RoleDetached.php b/src/Events/RoleDetached.php
index 287995fe6..9a32518ab 100644
--- a/src/Events/RoleDetached.php
+++ b/src/Events/RoleDetached.php
@@ -18,8 +18,8 @@ class RoleDetached
use SerializesModels;
/**
- * Internally the HasRoles trait passes $rolesOrIds as a single Eloquent record
- * Theoretically one could register the event to other places with an array etc
+ * Internally the HasRoles trait passes an array of role ids (eg: int's or uuid's)
+ * Theoretically one could register the event to other places passing other types
* So a Listener should inspect the type of $rolesOrIds received before using.
*
* @param array|int[]|string[]|Role|Role[]|Collection $rolesOrIds
diff --git a/src/Exceptions/GuardDoesNotMatch.php b/src/Exceptions/GuardDoesNotMatch.php
index 6506bb658..7c57b3155 100644
--- a/src/Exceptions/GuardDoesNotMatch.php
+++ b/src/Exceptions/GuardDoesNotMatch.php
@@ -9,6 +9,9 @@ class GuardDoesNotMatch extends InvalidArgumentException
{
public static function create(string $givenGuard, Collection $expectedGuards)
{
- return new static("The given role or permission should use guard `{$expectedGuards->implode(', ')}` instead of `{$givenGuard}`.");
+ return new static(__('The given role or permission should use guard `:expected` instead of `:given`.', [
+ 'expected' => $expectedGuards->implode(', '),
+ 'given' => $givenGuard,
+ ]));
}
}
diff --git a/src/Exceptions/PermissionAlreadyExists.php b/src/Exceptions/PermissionAlreadyExists.php
index 2748c9d1d..2270db148 100644
--- a/src/Exceptions/PermissionAlreadyExists.php
+++ b/src/Exceptions/PermissionAlreadyExists.php
@@ -8,6 +8,9 @@ class PermissionAlreadyExists extends InvalidArgumentException
{
public static function create(string $permissionName, string $guardName)
{
- return new static("A `{$permissionName}` permission already exists for guard `{$guardName}`.");
+ return new static(__('A `:permission` permission already exists for guard `:guard`.', [
+ 'permission' => $permissionName,
+ 'guard' => $guardName,
+ ]));
}
}
diff --git a/src/Exceptions/PermissionDoesNotExist.php b/src/Exceptions/PermissionDoesNotExist.php
index c04f5eaf0..853bab10e 100644
--- a/src/Exceptions/PermissionDoesNotExist.php
+++ b/src/Exceptions/PermissionDoesNotExist.php
@@ -8,7 +8,10 @@ class PermissionDoesNotExist extends InvalidArgumentException
{
public static function create(string $permissionName, ?string $guardName)
{
- return new static("There is no permission named `{$permissionName}` for guard `{$guardName}`.");
+ return new static(__('There is no permission named `:permission` for guard `:guard`.', [
+ 'permission' => $permissionName,
+ 'guard' => $guardName,
+ ]));
}
/**
@@ -17,6 +20,9 @@ public static function create(string $permissionName, ?string $guardName)
*/
public static function withId($permissionId, ?string $guardName)
{
- return new static("There is no [permission] with ID `{$permissionId}` for guard `{$guardName}`.");
+ return new static(__('There is no [permission] with ID `:id` for guard `:guard`.', [
+ 'id' => $permissionId,
+ 'guard' => $guardName,
+ ]));
}
}
diff --git a/src/Exceptions/RoleAlreadyExists.php b/src/Exceptions/RoleAlreadyExists.php
index 939c55bac..141be8d26 100644
--- a/src/Exceptions/RoleAlreadyExists.php
+++ b/src/Exceptions/RoleAlreadyExists.php
@@ -8,6 +8,9 @@ class RoleAlreadyExists extends InvalidArgumentException
{
public static function create(string $roleName, string $guardName)
{
- return new static("A role `{$roleName}` already exists for guard `{$guardName}`.");
+ return new static(__('A role `:role` already exists for guard `:guard`.', [
+ 'role' => $roleName,
+ 'guard' => $guardName,
+ ]));
}
}
diff --git a/src/Exceptions/RoleDoesNotExist.php b/src/Exceptions/RoleDoesNotExist.php
index a4871c665..641b4d37f 100644
--- a/src/Exceptions/RoleDoesNotExist.php
+++ b/src/Exceptions/RoleDoesNotExist.php
@@ -8,7 +8,10 @@ class RoleDoesNotExist extends InvalidArgumentException
{
public static function named(string $roleName, ?string $guardName)
{
- return new static("There is no role named `{$roleName}` for guard `{$guardName}`.");
+ return new static(__('There is no role named `:role` for guard `:guard`.', [
+ 'role' => $roleName,
+ 'guard' => $guardName,
+ ]));
}
/**
@@ -17,6 +20,9 @@ public static function named(string $roleName, ?string $guardName)
*/
public static function withId($roleId, ?string $guardName)
{
- return new static("There is no role with ID `{$roleId}` for guard `{$guardName}`.");
+ return new static(__('There is no role with ID `:id` for guard `:guard`.', [
+ 'id' => $roleId,
+ 'guard' => $guardName,
+ ]));
}
}
diff --git a/src/Exceptions/UnauthorizedException.php b/src/Exceptions/UnauthorizedException.php
index 249898a75..b9e9fe902 100644
--- a/src/Exceptions/UnauthorizedException.php
+++ b/src/Exceptions/UnauthorizedException.php
@@ -13,10 +13,10 @@ class UnauthorizedException extends HttpException
public static function forRoles(array $roles): self
{
- $message = 'User does not have the right roles.';
+ $message = __('User does not have the right roles.');
if (config('permission.display_role_in_exception')) {
- $message .= ' Necessary roles are '.implode(', ', $roles);
+ $message .= ' '.__('Necessary roles are :roles', ['roles' => implode(', ', $roles)]);
}
$exception = new static(403, $message, null, []);
@@ -27,10 +27,10 @@ public static function forRoles(array $roles): self
public static function forPermissions(array $permissions): self
{
- $message = 'User does not have the right permissions.';
+ $message = __('User does not have the right permissions.');
if (config('permission.display_permission_in_exception')) {
- $message .= ' Necessary permissions are '.implode(', ', $permissions);
+ $message .= ' '.__('Necessary permissions are :permissions', ['permissions' => implode(', ', $permissions)]);
}
$exception = new static(403, $message, null, []);
@@ -41,10 +41,10 @@ public static function forPermissions(array $permissions): self
public static function forRolesOrPermissions(array $rolesOrPermissions): self
{
- $message = 'User does not have any of the necessary access rights.';
+ $message = __('User does not have any of the necessary access rights.');
if (config('permission.display_permission_in_exception') && config('permission.display_role_in_exception')) {
- $message .= ' Necessary roles or permissions are '.implode(', ', $rolesOrPermissions);
+ $message .= ' '.__('Necessary roles or permissions are :values', ['values' => implode(', ', $rolesOrPermissions)]);
}
$exception = new static(403, $message, null, []);
@@ -57,12 +57,14 @@ public static function missingTraitHasRoles(Authorizable $user): self
{
$class = get_class($user);
- return new static(403, "Authorizable class `{$class}` must use Spatie\Permission\Traits\HasRoles trait.", null, []);
+ return new static(403, __('Authorizable class `:class` must use Spatie\\Permission\\Traits\\HasRoles trait.', [
+ 'class' => $class,
+ ]), null, []);
}
public static function notLoggedIn(): self
{
- return new static(403, 'User is not logged in.', null, []);
+ return new static(403, __('User is not logged in.'), null, []);
}
public function getRequiredRoles(): array
diff --git a/src/Exceptions/WildcardPermissionInvalidArgument.php b/src/Exceptions/WildcardPermissionInvalidArgument.php
index 44f092401..2c0a802ee 100644
--- a/src/Exceptions/WildcardPermissionInvalidArgument.php
+++ b/src/Exceptions/WildcardPermissionInvalidArgument.php
@@ -8,6 +8,6 @@ class WildcardPermissionInvalidArgument extends InvalidArgumentException
{
public static function create()
{
- return new static('Wildcard permission must be string, permission id or permission instance');
+ return new static(__('Wildcard permission must be string, permission id or permission instance'));
}
}
diff --git a/src/Exceptions/WildcardPermissionNotImplementsContract.php b/src/Exceptions/WildcardPermissionNotImplementsContract.php
index 7b81c73c9..52deaa456 100644
--- a/src/Exceptions/WildcardPermissionNotImplementsContract.php
+++ b/src/Exceptions/WildcardPermissionNotImplementsContract.php
@@ -8,6 +8,6 @@ class WildcardPermissionNotImplementsContract extends InvalidArgumentException
{
public static function create()
{
- return new static('Wildcard permission class must implements Spatie\Permission\Contracts\Wildcard contract');
+ return new static(__('Wildcard permission class must implement Spatie\\Permission\\Contracts\\Wildcard contract'));
}
}
diff --git a/src/Exceptions/WildcardPermissionNotProperlyFormatted.php b/src/Exceptions/WildcardPermissionNotProperlyFormatted.php
index 721157e6c..54b1e2915 100644
--- a/src/Exceptions/WildcardPermissionNotProperlyFormatted.php
+++ b/src/Exceptions/WildcardPermissionNotProperlyFormatted.php
@@ -8,6 +8,8 @@ class WildcardPermissionNotProperlyFormatted extends InvalidArgumentException
{
public static function create(string $permission)
{
- return new static("Wildcard permission `{$permission}` is not properly formatted.");
+ return new static(__('Wildcard permission `:permission` is not properly formatted.', [
+ 'permission' => $permission,
+ ]));
}
}
diff --git a/src/Models/Permission.php b/src/Models/Permission.php
index fd54b8ca0..490a6c23c 100644
--- a/src/Models/Permission.php
+++ b/src/Models/Permission.php
@@ -26,7 +26,7 @@ class Permission extends Model implements PermissionContract
public function __construct(array $attributes = [])
{
- $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard');
+ $attributes['guard_name'] ??= Guard::getDefaultName(static::class);
parent::__construct($attributes);
@@ -41,7 +41,7 @@ public function __construct(array $attributes = [])
*/
public static function create(array $attributes = [])
{
- $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class);
+ $attributes['guard_name'] ??= Guard::getDefaultName(static::class);
$permission = static::getPermission(['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']]);
@@ -88,7 +88,7 @@ public function users(): BelongsToMany
*/
public static function findByName(string $name, ?string $guardName = null): PermissionContract
{
- $guardName = $guardName ?? Guard::getDefaultName(static::class);
+ $guardName ??= Guard::getDefaultName(static::class);
$permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]);
if (! $permission) {
throw PermissionDoesNotExist::create($name, $guardName);
@@ -106,7 +106,7 @@ public static function findByName(string $name, ?string $guardName = null): Perm
*/
public static function findById(int|string $id, ?string $guardName = null): PermissionContract
{
- $guardName = $guardName ?? Guard::getDefaultName(static::class);
+ $guardName ??= Guard::getDefaultName(static::class);
$permission = static::getPermission([(new static)->getKeyName() => $id, 'guard_name' => $guardName]);
if (! $permission) {
@@ -123,7 +123,7 @@ public static function findById(int|string $id, ?string $guardName = null): Perm
*/
public static function findOrCreate(string $name, ?string $guardName = null): PermissionContract
{
- $guardName = $guardName ?? Guard::getDefaultName(static::class);
+ $guardName ??= Guard::getDefaultName(static::class);
$permission = static::getPermission(['name' => $name, 'guard_name' => $guardName]);
if (! $permission) {
diff --git a/src/Models/Role.php b/src/Models/Role.php
index 5bab4878e..7aa729dc1 100644
--- a/src/Models/Role.php
+++ b/src/Models/Role.php
@@ -27,7 +27,7 @@ class Role extends Model implements RoleContract
public function __construct(array $attributes = [])
{
- $attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard');
+ $attributes['guard_name'] ??= Guard::getDefaultName(static::class);
parent::__construct($attributes);
@@ -42,7 +42,7 @@ public function __construct(array $attributes = [])
*/
public static function create(array $attributes = [])
{
- $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class);
+ $attributes['guard_name'] ??= Guard::getDefaultName(static::class);
$params = ['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']];
if (app(PermissionRegistrar::class)->teams) {
@@ -97,7 +97,7 @@ public function users(): BelongsToMany
*/
public static function findByName(string $name, ?string $guardName = null): RoleContract
{
- $guardName = $guardName ?? Guard::getDefaultName(static::class);
+ $guardName ??= Guard::getDefaultName(static::class);
$role = static::findByParam(['name' => $name, 'guard_name' => $guardName]);
@@ -115,7 +115,7 @@ public static function findByName(string $name, ?string $guardName = null): Role
*/
public static function findById(int|string $id, ?string $guardName = null): RoleContract
{
- $guardName = $guardName ?? Guard::getDefaultName(static::class);
+ $guardName ??= Guard::getDefaultName(static::class);
$role = static::findByParam([(new static)->getKeyName() => $id, 'guard_name' => $guardName]);
@@ -133,7 +133,7 @@ public static function findById(int|string $id, ?string $guardName = null): Role
*/
public static function findOrCreate(string $name, ?string $guardName = null): RoleContract
{
- $guardName = $guardName ?? Guard::getDefaultName(static::class);
+ $guardName ??= Guard::getDefaultName(static::class);
$role = static::findByParam(['name' => $name, 'guard_name' => $guardName]);
diff --git a/src/Traits/HasRoles.php b/src/Traits/HasRoles.php
index d10dd77e3..38d2f02c0 100644
--- a/src/Traits/HasRoles.php
+++ b/src/Traits/HasRoles.php
@@ -193,13 +193,14 @@ function ($object) use ($roles, $model, $teamPivot, &$saved) {
/**
* Revoke the given role from the model.
*
- * @param string|int|Role|\BackedEnum $role
+ * @param string|int|array|Role|Collection|\BackedEnum ...$role
+ * @return $this
*/
- public function removeRole($role)
+ public function removeRole(...$role)
{
- $storedRole = $this->getStoredRole($role);
+ $roles = $this->collectRoles($role);
- $this->roles()->detach($storedRole);
+ $this->roles()->detach($roles);
$this->unsetRelation('roles');
@@ -208,7 +209,7 @@ public function removeRole($role)
}
if (config('permission.events_enabled')) {
- event(new RoleDetached($this->getModel(), $storedRole));
+ event(new RoleDetached($this->getModel(), $roles));
}
return $this;
diff --git a/tests/HasPermissionsTest.php b/tests/HasPermissionsTest.php
index 49f450359..95ad3a473 100644
--- a/tests/HasPermissionsTest.php
+++ b/tests/HasPermissionsTest.php
@@ -611,6 +611,23 @@ public function it_can_avoid_sync_duplicated_permissions()
$this->assertTrue($this->testUser->hasDirectPermission('edit-blog'));
}
+ /** @test */
+ #[Test]
+ public function it_can_avoid_detach_on_permission_that_does_not_exist_sync()
+ {
+ $this->testUser->syncPermissions('edit-articles');
+
+ try {
+ $this->testUser->syncPermissions('permission-does-not-exist');
+ $this->fail('Expected PermissionDoesNotExist exception was not thrown.');
+ } catch (PermissionDoesNotExist $e) {
+ //
+ }
+
+ $this->assertTrue($this->testUser->hasDirectPermission('edit-articles'));
+ $this->assertFalse($this->testUser->checkPermissionTo('permission-does-not-exist'));
+ }
+
/** @test */
#[Test]
public function it_can_sync_multiple_permissions_by_id()
diff --git a/tests/HasRolesTest.php b/tests/HasRolesTest.php
index 7f8a137e0..734394706 100644
--- a/tests/HasRolesTest.php
+++ b/tests/HasRolesTest.php
@@ -181,42 +181,62 @@ public function it_can_assign_and_remove_a_role_on_a_permission()
/** @test */
#[Test]
- public function it_can_assign_a_role_using_an_object()
+ public function it_can_assign_and_remove_a_role_using_an_object()
{
$this->testUser->assignRole($this->testUserRole);
$this->assertTrue($this->testUser->hasRole($this->testUserRole));
+
+ $this->testUser->removeRole($this->testUserRole);
+
+ $this->assertFalse($this->testUser->hasRole($this->testUserRole));
}
/** @test */
#[Test]
- public function it_can_assign_a_role_using_an_id()
+ public function it_can_assign_and_remove_a_role_using_an_id()
{
$this->testUser->assignRole($this->testUserRole->getKey());
$this->assertTrue($this->testUser->hasRole($this->testUserRole));
+
+ $this->testUser->removeRole($this->testUserRole->getKey());
+
+ $this->assertFalse($this->testUser->hasRole($this->testUserRole));
}
/** @test */
#[Test]
- public function it_can_assign_multiple_roles_at_once()
+ public function it_can_assign_and_remove_multiple_roles_at_once()
{
$this->testUser->assignRole($this->testUserRole->getKey(), 'testRole2');
$this->assertTrue($this->testUser->hasRole('testRole'));
$this->assertTrue($this->testUser->hasRole('testRole2'));
+
+ $this->testUser->removeRole($this->testUserRole->getKey(), 'testRole2');
+
+ $this->assertFalse($this->testUser->hasRole('testRole'));
+
+ $this->assertFalse($this->testUser->hasRole('testRole2'));
}
/** @test */
#[Test]
- public function it_can_assign_multiple_roles_using_an_array()
+ public function it_can_assign_and_remove_multiple_roles_using_an_array()
{
$this->testUser->assignRole([$this->testUserRole->getKey(), 'testRole2']);
$this->assertTrue($this->testUser->hasRole('testRole'));
$this->assertTrue($this->testUser->hasRole('testRole2'));
+
+ $this->testUser->removeRole([$this->testUserRole->getKey(), 'testRole2']);
+
+ $this->assertFalse($this->testUser->hasRole('testRole'));
+
+ $this->assertFalse($this->testUser->hasRole('testRole2'));
}
/** @test */
@@ -318,6 +338,23 @@ public function it_can_avoid_sync_duplicated_roles()
$this->assertTrue($this->testUser->hasRole('testRole2'));
}
+ /** @test */
+ #[Test]
+ public function it_can_avoid_detach_on_role_that_does_not_exist_sync()
+ {
+ $this->testUser->syncRoles('testRole');
+
+ try {
+ $this->testUser->syncRoles('role-does-not-exist');
+ $this->fail('Expected RoleDoesNotExist exception was not thrown.');
+ } catch (RoleDoesNotExist $e) {
+ //
+ }
+
+ $this->assertTrue($this->testUser->hasRole('testRole'));
+ $this->assertFalse($this->testUser->hasRole('role-does-not-exist'));
+ }
+
/** @test */
#[Test]
public function it_can_sync_multiple_roles()
@@ -956,14 +993,19 @@ public function it_fires_an_event_when_a_role_is_removed()
Event::fake();
app('config')->set('permission.events_enabled', true);
- $this->testUser->assignRole('testRole');
+ $this->testUser->assignRole('testRole', 'testRole2');
- $this->testUser->removeRole('testRole');
+ $this->testUser->removeRole('testRole', 'testRole2');
+
+ $roleIds = app(Role::class)::whereIn('name', ['testRole', 'testRole2'])
+ ->pluck($this->testUserRole->getKeyName())
+ ->toArray();
- Event::assertDispatched(RoleDetached::class, function ($event) {
+ Event::assertDispatched(RoleDetached::class, function ($event) use ($roleIds) {
return $event->model instanceof User
&& ! $event->model->hasRole('testRole')
- && $event->rolesOrIds->name === 'testRole';
+ && ! $event->model->hasRole('testRole2')
+ && $event->rolesOrIds === $roleIds;
});
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 073e7c8fb..cc243faa1 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -148,7 +148,7 @@ protected function getEnvironmentSetUp($app)
// FOR MANUAL TESTING OF ALTERNATE CACHE STORES:
// $app['config']->set('cache.default', 'array');
// Laravel supports: array, database, file
- // requires extensions: apc, memcached, redis, dynamodb, octane
+ // requires extensions: memcached, redis, dynamodb, octane
}
/**
@@ -230,7 +230,7 @@ protected function setUpPassport($app): void
$app['config']->set('auth.guards.api', ['driver' => 'passport', 'provider' => 'users']);
// mimic passport:install (must load migrations using our own call to loadMigrationsFrom() else rollbacks won't occur, and migrations will be left in skeleton directory
- $this->artisan('passport:keys');
+ // $this->artisan('passport:keys');
$this->loadMigrationsFrom(__DIR__.'/../vendor/laravel/passport/database/migrations/');
$provider = in_array('users', array_keys(config('auth.providers'))) ? 'users' : null;
$this->artisan('passport:client', ['--personal' => true, '--name' => config('app.name').' Personal Access Client']);