How to (properly) use form requests + policies + resource routes with dependencies?

Viewed 2213

My use case is that an user is/owns a company, which has employees.

Using form controllers along with model policies i am trying to figure out what the best/proper way to do it should be.

routes:

    Route::resource('company', \App\Http\Controllers\Api\v1\CompanyController::class);
    Route::resource('employee', \App\Http\Controllers\Api\v1\EmployeeController::class);

employee store request:

namespace App\Http\Requests;

use App\Models\Employee;
use Illuminate\Foundation\Http\FormRequest;

class EmployeeStoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return $this->user()->can('create', Employee::class);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'company_id' => 'required|integer|exists:companies,id'
        ];
    }
}

employee policy:

...
/**
 * Determine whether the user can create employees.
 *
 * @param  \App\User $user
 * @return mixed
 */
public function create(User $user)
{
    return $user->can('update', Company::find(
        app('request')->get('company_id')
    ));
}
...

So i am not particularly happy in how the policy checks if the user can edit the company the employee will belong to, since this only happens on http, for console/tests this will break.

Then the most logical way to add this check is in the form request's authorize() function, but then you are checking permissions outside the policies, which sounds illogical.

So in short, the question: how & why would you do this using form requests & model policies?

1 Answers

You only need to add the id of the company to the EmployeePolicy@create method, and you will be able to use it outside http

EmployeePolicy

public function create(User $user, $companyId)
{
    return $user->can('update', $companyId);
}

EmployeeStoreRequest

public function authorize()
{
    return $this->user()->can('create', Employee::class, $this->request->get('company_id'));
}

You can test it outside http with tinker

php artisan tinker
$user = User::find(2); // or whatever user you want to test with
$user->can('create', Employee::class, 3); // 3 = company_id
Related