Skip to content
Tortue Torche edited this page Apr 29, 2015 · 14 revisions

Let's say we have nested resources set up in our routes.

If you don't know what it is, read this short tutorial.

// app/Http/routes.php # For Laravel 5.0
// app/routes.php # For Laravel 4.*
Route::resource('projects', 'ProjectsController');
Route::resource('projects.tasks', 'TasksController');

We can then tell AuthorizeController to load the project and then load the task through that.

class TasksController extends BaseController
{
    function __construct() 
    {
        $this->loadAndAuthorizeResource('project');
        $this->loadAndAuthorizeResource('task', ['through' => 'project']);
    }
}

This will fetch the project using Project::find($this->params['project_id']); on every controller action, save it in the $this->project instance variable, and authorize it using the read action to ensure the user has the authority to access that project. If you don't want to do the authorization you can simply use loadResource(), but calling just authorizeResource() for the parent object is insufficient. The task is then loaded through the $this->project->tasks() association.

If the resource name (project in this case) does not match the controller then it will be considered a parent resource. You can manually specify parent/child resources using the parent => false option.

Nested through method

It's also possible to nest through a method, this is commonly the $this->getCurrentUser() method.

class ProjectsController extends BaseController
{
    function __construct() 
    {
        $this->loadAndAuthorizeResource(['through' => 'getCurrentUser']);
    }
}

Here everything will be loaded through the $this->getCurrentUser()->projects() association.

Shallow nesting

The parent resource is required to be present and it will raise an exception if the parent is ever null. If you want it to be optional (such as with shallow routes), add the 'shallow' => true option to the child.

class TasksController extends BaseController
{
    function __construct() 
    {
        $this->loadAndAuthorizeResource('project');
        $this->loadAndAuthorizeResource('task', ['through' => 'project', 'shallow' => true]);
    }
}

Singleton resource

What if each project only had one task through a hasOne association? To set up singleton resources you can use the singleton option.

class TasksController extends BaseController
{
    function __construct() 
    {
        $this->loadAndAuthorizeResource('project');
        $this->loadAndAuthorizeResource('task', ['through' => 'project', 'singleton' => true]);
    }
}

It will then use the $this->project->task() and $this->project->task()->associate($this->task) methods for fetching and building respectively.

Polymorphic associations

Let's say tasks can either be assigned to a Project or an Event through a polymorphic association. An array can be passed into the through option and it will use the first one it finds.

$this->loadResource('project');
$this->loadResource('event');
$this->loadAndAuthorizeResource('task', ['through' => ['project', 'event']]);

Here it will check both the $this->project and $this->event variables and fetch the task through whichever one exists. Note that this is only loading the parent model, if you want to authorize the parent you will need to do it through a beforeFilter because there is special logic involved.

function __construct() 
{
    $this->beforeFilter(function() {
        $this->authorize('read', ($this->event ? $this->event : $this->project));
    });
}

Accessing parent in authority rule

Sometimes the child permissions are closely tied to the parent resource. For example, if there is a user_id column on Project, one may want to only allow access to tasks if the user owns their project.

This will happen automatically due to the $this->project instance being authorized in the nesting. However it's still a good idea to restrict the tasks separately. You can do so by going through the project association.

// in the authority-controller config file
$this->allow('manage', 'Task', function ($self, $project) {
    return $project->user_id == $self->user()->id;
});

This means you will need to have a project tied to the tasks which you pass into here. For example, if you are checking if the user has permission to create a new task, do that by building it through the project.

@if (Authority::can('create', $task->project()->associate($project)->project))

It's also possible to check permission through an association like this.

@if (Authority::can('read', ['Task' => $currentProject]))

This will use the above $project in conditional callback and ensure $currentProject meets those conditions.