# Availability

We can declare whether an Action is available through the use of the availability method.

When Lantern checks an Action's availability, it also checks the constraints of its parent Feature, but not the Feature's availability.

Inheritance

The Feature of an Action must have a signature that will allow it to be instantiated without arguments being passed to its __construct() method, as Lantern needs to instantiate the Feature to check its constraints.

# AvailabilityBuilder

When you declare the availability of an Action you will use the AvailabilityBuilder

protected function availability(Lantern\Features\AvailabilityBuilder $builder) {
    //…
}

# Available methods

Examples of Availability Checks

Here are some examples of how you might define checks within the availability method of an Action or Feature, using the $builder object.

<?php

namespace App\Features\Todos\Actions;

use App\Models\Todo;
use App\Services\TodoRepository;
use Lantern\Features\Action;
use Lantern\Features\ActionResponse;
use Lantern\Features\AvailabilityBuilder;

class UpdateTodoAction extends Action
{
    public function __construct(private TodoRepository $todoRepository, private Todo $todo)
    {
        // The specific Todo model is passed in
    }

    public function perform(string $newDescription): ActionResponse
    {
        // ... action logic ...
        return ActionResponse::successful('Todo updated.');
    }

    protected function availability(AvailabilityBuilder $builder)
    {
        // 1. Ensure the user is authenticated (redundant if GUEST_USERS is false, but good practice)
        // Note: mustBeAuthenticated() is not in the core AvailabilityBuilder
        // Instead, use the built-in methods:
        $builder->assertTrue(auth()->check(), 'User must be logged in to update todos.');

        // 2. Use Laravel Policy: Check if the user 'can' update the specific $todo model
        $builder->userCan('update', $this->todo, 'User does not have permission to update this specific todo.');

        // 3. Custom Logic: Check if the todo is not already completed
        $builder->assertFalse($this->todo->is_completed, 'Cannot update a completed todo.');

        // 4. Custom Logic: Check if the user owns the todo (alternative to policy)
        $builder->assertEqual($builder->user()->id, $this->todo->user_id, 'User must own the todo to update it.');

        // 5. Check Parent Feature: Ensure the parent 'TodosFeature' is available
        // Note: featureAvailable() is not in the core AvailabilityBuilder
        // You would need to extend AvailabilityBuilder to add this method
    }
}

Explanation of Examples:

  1. assertTrue(auth()->check(), ...): A generic assertion checking if the user is authenticated. This is a standard way to check authentication status.
  2. userCan('update', $this->todo): Leverages Laravel's authorization. It checks if the $builder->user() (the current user by default) has the update ability for the provided $this->todo model instance, according to your TodoPolicy.
  3. assertFalse($this->todo->is_completed): A generic assertion checking if the is_completed property of the todo is false.
  4. assertEqual($builder->user()->id, $this->todo->user_id): Compares the current user's ID with the todo's user_id.

Note: The core AvailabilityBuilder class doesn't include methods like mustBeAuthenticated() or featureAvailable(). If you need such functionality, you can extend the AvailabilityBuilder class with your own custom methods. See the section on Customising availability checks below.

Debugging availability

It is often helpful in development to see why an Action is failing the availability checks. With all the assert… methods below, you have the option of providing a $failureMessage. This can be inspected when using the gate collector of the Laravel Debugbar (opens new window).

# user()

Returns the user to use for your checks.

Auth::user() is used as the default user for checking availability and typically, you will want to check if a given Action is available to the current logged in user.

However, it can be useful to check the Availability of a different user. E.g. if you list out the users belonging to a company in your app, and have certain Actions only available to certain users.

In order to check the availablity for a different user in this way pass the user through as an argument to the available method

if ($action = MyAction::make()->available($otherUser)) {
    $action->perform();
}

Auth::user()

As it is possible to check an Action against a user other than the logged in user you should never get your user object from Auth::user() from within protected function availability(){}.

# userCan()

Check Laravel's authorisation policy (opens new window) for a given ability.

# userCannot()

The opposite of userCan().

# assertTrue()

Check if an expression is true.

# assertFalse()

Check if an expression is false.

# assertNull()

Check if an expression is null.

# assertNotNull()

Check if an expression is not null.

# assertEmpty()

Check if an expression is empty.

# assertNotEmpty()

Check if an expression is not empty.

# assertEqual()

Check if 2 expressions are equal in value (== not ===).

# assertNotEqual()

Check if 2 expressions are not equal in value (== not ===).

Authorisation

For more info on using these Availability checks in your app, look at the docs on authorisation.

# Customising availability checks

Since version 1.1.0

See releases on Github (opens new window)

The base AvailabilityBuilder (opens new window) offers only the rudimentary assertions you see above, but as you begin to flesh out your domain model you will quickly want to add a little more meaning to your checks.

To achieve this, you can extend the AvailabilityBuilder with a child class of your own.