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

ella-stinnes's avatar

ella-stinnes liked a comment+100 XP

2mos ago

Policy for belongsToMany Pivot Table

You don’t need a ProjectUser model just to authorize “assigning a project to a user”.

In Laravel, the “pivot” is usually not treated as a first-class resource for authorization; instead you authorize the action on one of the real models (most commonly User or Project) and pass the other model(s) as arguments.

  1. Define a custom ability on UserPolicy
// app/Policies/UserPolicy.php

public function assignProject(User $actor, User $target, Project $project): bool
{
    // 1) Full Admin: can link users to projects within the user's company
    if ($actor->isFullAdmin()) {
        return $target->company_id === $project->company_id;
    }

    // 2) Company Admin: only within their own company
    if ($actor->isCompanyAdmin()) {
        return $actor->company_id === $target->company_id
            && $actor->company_id === $project->company_id;
    }

    return false;
}
  1. Use it in controller (best place, because you have all objects)
public function update(User $user, Request $request)
{
    $project = Project::findOrFail($request->project_id);

    $this->authorize('assignProject', [$user, $project]);

    $user->projects()->syncWithoutDetaching([$project->id]);
}

Route middleware: possible, but you’ll still need the objects

Route ->can() works well when your policy method can be resolved from route params. Since the ability needs User + Project, your route must provide both:

Route::post('/users/{user}/projects/{project}', [UserProjectController::class, 'store'])
    ->can('assignProject', ['user', 'project']);

This calls UserPolicy@assignProject(auth()->user(), $user, $project).

If your “edit” page doesn’t include {project} in the URL, then ->can() can only authorize a broader ability like “canManageUserProjects” (no specific project yet).
ella-stinnes's avatar

ella-stinnes wrote a reply+100 XP

2mos ago

Policy for belongsToMany Pivot Table

My solution of a policy for a pivot table wasn't mentioned in the docs, so your explanation was what I was looking for and makes sense - thank you!

ella-stinnes's avatar

ella-stinnes wrote a reply+100 XP

2mos ago

Policy for belongsToMany Pivot Table
  • Full Admin - Can link any user to projects that are associated with the users' company.

  • Company Admin - Can only view users associated with their own company. They can therefore only link these users to projects associated with their company.

ella-stinnes's avatar

ella-stinnes started a new conversation+100 XP

2mos ago

Policy for belongsToMany Pivot Table
  • projects
  • project_user
  • users

I want to create an authorization policy for the 'project_user' pivot table to define who is able to assign projects to a given user.

At the moment, I don't have a ProjectUser model class. Will I need to create this for the ProjectUserPolicy?

ella-stinnes's avatar

ella-stinnes wrote a reply+100 XP

3mos ago

Policy viewAny

To check my understanding, I would define a gate for the page access as follows:

Gate::define('companies-index', function (User $user) {
        return $user->isRole([UserRole::Administrator, UserRole::CompanyAdministrator]);
    });

Then the policy would determine whether they can viewAny and/or view a specific model as follows?

A company administrator would not be able to viewany so a query scope would be applied to the index page query?

public function viewAny(User $user): bool
{
        return $user->isRole([UserRole::Administrator]);
}

public function view(User $user, Company $company): bool
{
		if ($user->isRole([UserRole::Administrator]))
            return true;
        
        if($user->isRole([UserRole::CompanyAdministrator])) {
            return $user->company_id === $company->id;

		return false;
}
ella-stinnes's avatar

ella-stinnes started a new conversation+100 XP

3mos ago

Policy viewAny

Both an administrator and company administrator should be able to access the companies.index page.

  • Administrator - should be able to list all companies.

  • Company Administrator - should only list the companies they are related to.

I've seen differing opinions on the policy viewAny method.

Should I be using this to define whether the user role can access the index page as I have below:

public function viewAny(User $user): bool
{
        return $user->isRole([UserRole::Administrator, UserRole::CompanyAdministrator]);
}

viewAny implies the CompanyAdministrator role can view any company.

Should I therefore be creating another method for allowing access to the index page (if so, what would you name this)?

ella-stinnes's avatar

ella-stinnes liked a comment+100 XP

3mos ago

What should I test?

@ella-stinnes Sounds like data providers and/or the “test with” utility would be a good solution for your use cases. They basically let you re-run a single test case multiple times, but with a different input each time.

The application has 8 different user roles, some users can access the full CRUD, some are read only and others cannot see the company details at all.

Should I be creating 8 tests for the different user roles?

Using a data provider, you’d have a single test case (e.g. test_create_company), that could take a user role and the expected result as inputs:

#[TestWith(['admin', true])]
#[TestWith(['moderator', true])]
#[TestWith(['member', false])]
// And your other roles...
public function test_create_company(string $role, bool $expected): void
{
    $user = User::factory()->role($role)->createOne();

    $this->assertDatabaseEmpty('companies');

    $response = $this->actingAs($user)->post('/companies', [
        // Valid company data...
    ]);

    if ($expected) {
        $response->assertRedirect()->assertValid();

        $this->assertDatabaseHas('companies', [
            // Fields and values that should exist in database...
        ]);
    } else {
        $response->assertForbidden();

        $this->assertDatabaseEmpty('companies');
    }
}

So the above test case will create a user with the given role and attempt to create a company. Then, depending on the expected result, will either assert the request was successful and a record was inserted into the database, or assert a 403 Forbidden response was returned and no company records were created.

Once I've tested the authorisation, I'm guessing I should have form validation tests - if there's 10 fields on the page, do I create 10 validation tests? Or one test to cover all 10 fields?

Again, you can use a data provider for say, testing required fields. For most resources in my applications, I‘ll have a test method that looks like this:

#[TestWith(['name'])]
#[TestWith(['description'])]
#[TestWith(['email'])]
#[TestWith(['telephone_number'])]
#[TestWith(['website_url'])]
public function test_field_is_required(string $field): void
{
    $user = User::factory()->role('admin')->createOne();

    $data = [
        'name' => fake()->company(),
        'email' => fake()->safeEmail(),
        // All other fields with a valid value...
    ];

    Arr::forget($data, $field);

    $this
        ->actingAs($user)
        ->post('/companies', $data)
        ->assertInvalid($field);
}

The above test case will then be re-ran multiple times with a different field name provided as an argument each time. The test case has an array of “valid” data, but then removes the named field, and asserts the request throws a validation error for that named field.

ella-stinnes's avatar

ella-stinnes wrote a reply+100 XP

3mos ago

What should I test?

Thank you both, I'll look at the Pest Driven Laravel series although this appears to be TDD?

Does this cover a general rule of thumb as to what should be tested?

ella-stinnes's avatar

ella-stinnes started a new conversation+100 XP

3mos ago

What should I test?

I'm new to testing so I'm trying to figure out what I should test.

I have a CRUD for managing company details.

The application has 8 different user roles, some users can access the full CRUD, some are read only and others cannot see the company details at all.

Should I be creating 8 tests for the different user roles?

Once I've tested the authorisation, I'm guessing I should have form validation tests - if there's 10 fields on the page, do I create 10 validation tests? Or one test to cover all 10 fields?

Then there's the store/update methods, I'm guessing I assert the database contains the values.

Do you have a testing workflow for a standard CRUD as to what should be covered?

I'm not sure if I'm writing too many tests.