Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Caraes_Naur's avatar

Model Policies and Relationships

In an application with hierarchies of parent-child relationships, what is the preferred approach to which policy contains a given method regarding other related models?

I am in the process of starting a FOSS project in Laravel 11 to recreate an existing (non-framework) FOSS project, which is an issue tracker. Not my first Laravel project, but this time I'm exploring the framework for myself in more detail.

I'm starting with roles & privileges using Spatie Permissions (with teams enabled). Model policies will be full of custom methods which depend on related models' class and/or instance.

I began putting together the privilege list with an approach where parent policies referenced the children, such as ProjectPolicy::addTask(). The original project did privileges this way (mostly), but I am no longer certain this is the Laravel Way(TM).

Hierarchy examples:

Project -> Task -> Attachment
Project -> Task -> Comment -> Attachment

Task & Project models I'll implement myself. Here, Attachment is an uploaded file.

I'm envisioning the privilege structure as a kind of sieve that tracks all the way up the chain to the Project, which is always the root-level model. I think (correct me if I'm wrong) basic Project privileges would be implemented as a gate.

So, to check if a user can add an Attachment to a Task, should that method be TaskPolicy::addAttachment() or AttachmentPolicy::addToTask()?

This is complicated by an Attachment can be the child of a Task or a Comment, which involves separate privileges based on each parent.

I've decided to use beyondcode/laravel-comments for comments and spatie/laravel-medialibrary for attached files, both of which can be attached to multiple models.

For this and other reasons, it looks like I'll need to extend the Spatie Media model as App\Models\Attachment. I'll then have an AttachmentPolicy whose methods can peek at $attachment->model_type and $attachment->model_id to resolve privileges based on different parent classes, and then proceed up the hierarchy. Is that the proper approach?

0 likes
8 replies
vincent15000's avatar

I'm interested in knowing what was the first approach.

I never created static methods inside the policy classes.

1 like
Caraes_Naur's avatar

@vincent15000 I haven't actually written any code yet, just planning it out.

They're not static methods, despite writing the post using '::' notation. I've switched to arrows below.

The initial approach would result in methods like ProjectPolicy->addTask(User $user, Project $project, Task $task). The parent model's (Project) policy references the child model Task. This was an attempt to literally recreate the other codebase's implementation.

The approach I now think is more proper in Laravel would result in TaskPolicy->create(User $user, Project $project). This keeps policies more self-contained and more closely aligned with their models. It also seems to result in less code that is simpler.

The question is what methods belong in which model policy, not whether the the methods are static.

jlrdw's avatar

@Caraes_Naur

The question is what methods belong in which model policy

Basically what the current logged in user with role can do (or cannot).

  • update
  • create
  • etc

I use a role based technique myself. Checking if the users role (one of their roles) matches a required role:

    public static function chkRole($role = null)
    {
        $userrole = Auth::user()->role;
        $checkrole = explode(',', $userrole);
        if (in_array($role, $checkrole)) {
            return true;
        }
        return false;
    }

Rather than a related table of roles, I use a simple comma separated list.

For example I may have for a user:

admin,bkeep,user

bkeep is for bookkeeper. But for your question it's what the user can or cannot do.

@martinbean also has a technique in a blog:

https://martinbean.dev/blog/2021/07/29/simple-role-based-authentication-laravel/

You need to ensure that a id isn't changed in the query string also, by using auth::id.

    public function getPets($query, $petsearch = '')
    {
        $petsearch = $petsearch . "%";
        $query->where('petname', 'like', $petsearch);
        if (ChkAuth::chkRole('admin') === false) {   // custom code
            $userid = Auth::user()->id;
            $query->where('ownerid', '=', $userid);
        }
        $results = $query->orderBy('petname', 'asc')->paginate(5);
        return $results;
    }

So if admin show all, otherwise show only current users. You have to strategically write queries that include security (protection from tampering with the url query string).

Otherwise a user can change an id from 1 to 55 for example.

Above just examples.

In the 30 days to learn laravel FREE video course, @jeffreyway covers gates and policies.

2 likes
vincent15000's avatar

@jlrdw But your static function isn't in the policy class, it's just a helper, isn't it ?

2 likes
jlrdw's avatar

@vincent15000 Yes,I write my own authorization but still use laravels authentication.

2 likes
vincent15000's avatar

@jlrdw This reminds me how I did for my policy.

For example if I have a policy for the Endpoint model, but the creation or the update of the endpoint depends on the Branch model (an endpoint has a connection which has a branch).

Endpoint : id, name Branch : id, name Connection : id, name, endpoint_id, branch_id

What a user can do on an endpoint (create, update, delete) depends only on his rights on the branch in which the endpoint is connected.

Here is a small example of how I have written the policy

// EndpointPolicy

public function update(User $user, Endpoint $endpoint)
{
	return $user->isOwnerOf($endpoint->connection->branch);
}

How would you write the policy in this case ?

1 like
jlrdw's avatar

@vincent15000 something like:

User or admin can edit

    public static function chkUserId($userid)
    {
        if ($userid === Auth::user()->id || self::chkRole('admin') === true) {
            return true;
        }
        return false;
    }

Or if only user can edit:

    public static function chkUserId($userid)
    {
        if ($userid === Auth::user()->id) {
            return true;
        }
        return false;
    }

Or this check can also be done in controller method with a redirect if false.

1 like
vincent15000's avatar

@jlrdw Ok so for editing an endpoint, you don't apply the endpoint policy but a totally different policy / check.

Please or to participate in this conversation.