Laravel offers loads of fantastic ways to handle authorization, all of which rely on arguments to be passed dynamically
from Gate
to its callbacks or policies. That dynamic nature, however, requires us to either look up and inspect the
underlying policy or callback to discover its expected arguments, or rely on IDE helpers to do so:
$user->can('update-post', $post);
// or using invokable classes...
$user->can(UpdatePostAbility::class, $post);
With this package, we aim at improving this by leveraging on constructor arguments to explicitly expose ability dependencies, allowing us to rewrite the above as:
$user->can([new UpdatePostAbility($post)]);
- Improved type-hinting using constructor arguments.
- Automatic evaluation of abilities without having to define them in
Gate
. - Create ability classes with the Artisan CLI.
This package requires:
- PHP 8.3
You can install this package using Composer by running the following command:
composer require craftzing/laravel-abilities
To create a class-based ability, implement the Craftzing\Laravel\Abilities\Ability
interface:
use App\Models\Post;
use Craftzing\Laravel\Abilities\Contracts\Ability;
final readonly class MergePullRequestAbility implements Ability
{
public function __construct(
private PullRequest $pullRequest,
) {}
public function granted(mixed $user): bool
{
return $user->isMaintainerForRepository($pullRequest->repository);
}
}
Tip
You can use the php artisan make:ability
command to create new abilities.
Once your ability is all setup, you can use it right away. There is no need to define it in Gate
:
use Illuminate\Support\Facades\Gate;
$user->can([new MergePullRequestAbility($pullRequest)]);
Gate::allows([new MergePullRequestAbility($pullRequest)]);
Important
Note that the ability MUST be wrapped in an array. This is due to a technical constraint where Gate
does not
accept object instances to be passed along directly.
We highly recommend to unit test all of your ability classes extensively. Doing so will allow you to confidently fake ability checks in any functional or feature tests:
use Illuminate\Support\Facades\Gate;
// By explicitly defining the ability class in Gate, the provided callback will
// be called instead of the `granted()` method of the according ability.
Gate::define(MergePullRequestAbility::class, fn (): bool => true);
If you want to inspect the arguments that were passed to the ability's constructor, you can pass variables by reference to the callback:
use Database\Factories\UserFactory;
use Database\Factories\Git\RepositoryFactory;
use Illuminate\Support\Facades\Gate;
// Arrange
$user = UserFactory::new()->create();
$repository = RepositoryFactory::new()->created();
$grantedUser = null;
$grantedAbility = null;
Gate::define(MergePullRequestAbility::class, function (
mixed $user,
MergePullRequestAbility $ability,
) use (&$grantedUser, &$grantedAbility): bool {
$grantedUser = $user;
$grantedAbility = $ability;
return true;
});
// Act phase of your test...
// Assert
$this->assertInstanceOf(User::class, $grantedUser);
$this->assertTrue($grantedUser->is($user));
$this->assertInstanceOf(MergePullRequestAbility::class, $grantedAbility);
$this->assertTrue($grantedAbility->repository->is($repository));
Check out our Change log for information on what has changed recently.
Have an idea for a feature? Wanna improve the docs? Found a bug? Check out our Contributing guide.
If you discover any security-related issues, please email security@craftzing.com instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.