Policies
Policies are classes that resolve permissions for a given object. You can create policies for any class in your application that needs authorization checks.
Creating Policies
Create policies in src/Policy. They do not need to extend a base class or implement a shared interface. Application classes are resolved to matching policy classes by a policy resolver. See Policy Resolvers for the available approaches.
Most applications use the Policy suffix in src/Policy. For example, an article entity policy in src/Policy/ArticlePolicy.php:
<?php
namespace App\Policy;
use App\Model\Entity\Article;
use Authorization\IdentityInterface;
class ArticlePolicy
{
}2
3
4
5
6
7
8
9
Policies can also be resolved for tables and queries. Query objects use their repository() method to derive the table class. For example, App\Model\Table\ArticlesTable maps to App\Policy\ArticlesTablePolicy.
You can generate empty ORM policy classes with Bake:
# Create an entity policy
bin/cake bake policy --type entity Article
# Create a table policy
bin/cake bake policy --type table Articles2
3
4
5
Writing Policy Methods
Define methods that answer whether a user may perform an action:
public function canEdit(IdentityInterface $user, Article $article): bool
{
return $user->id === $article->user_id;
}2
3
4
Policy methods must return true or a Result object to indicate success. Any other return value is treated as failure.
Policy methods receive null for $user when evaluating anonymous users. If you want anonymous users to fail automatically, typehint IdentityInterface.
Policy Result Objects
Besides booleans, policy methods may return Authorization\Policy\Result for additional context:
use Authorization\Policy\Result;
public function canUpdate(IdentityInterface $user, Article $article): Result
{
if ($user->id === $article->user_id) {
return new Result(true);
}
return new Result(false, 'not-owner');
}2
3
4
5
6
7
8
9
10
Any return value other than true or a ResultInterface instance is considered a failure.
Policy Scopes
Policies can also define scopes that modify another object with authorization conditions. A common use case is restricting list views to the current user:
namespace App\Policy;
class ArticlesTablePolicy
{
public function scopeIndex($user, $query)
{
return $query->where(['Articles.user_id' => $user->getIdentifier()]);
}
}2
3
4
5
6
7
8
9
Policy Pre-conditions
Use BeforePolicyInterface when you want common checks across every operation in a policy:
namespace App\Policy;
use Authorization\IdentityInterface;
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\ResultInterface;
class ArticlesPolicy implements BeforePolicyInterface
{
public function before(?IdentityInterface $identity, mixed $resource, string $action): ResultInterface|bool|null
{
if ($identity->getOriginalData()->is_admin) {
return true;
}
return null;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
The before() hook can return:
trueto allow the action immediately.falseto deny the action immediately.nullto let the normal authorization method run.
Scope Pre-conditions
Scopes support similar pre-conditions through BeforeScopeInterface:
namespace App\Policy;
use Authorization\Policy\BeforeScopeInterface;
class ArticlesTablePolicy implements BeforeScopeInterface
{
public function beforeScope($user, $query, $action)
{
if ($user->getOriginalData()->is_trial_user) {
return $query->where(['Articles.is_paid_only' => false]);
}
return null;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
Returning a modified resource short-circuits the normal scope method. Returning null allows the scope method to run normally.
Applying Policies
See Applying Policy Scopes for controller examples.