›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-approval
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

byrcsc/laravel-approval · 1.x

Approvers and resolvers.

Assign a specific approver or resolve several approvers from an application rule.

An approver entry points to one saved model. Only that model can respond to the assignment.

$stage->approvers()->create([
    'approver_type' => $financeUser->getMorphClass(),
    'approver_id' => $financeUser->getKey(),
]);

The package does not expand a Team, Role, Position, or other container model into its members. Assigning a team assigns the team record itself, and only something acting as that team can approve. To assign the members, use an approver resolver.

Approver columns are polymorphic, so an assignment can name any model. The package never assumes your users are App\Models\User.

Approver resolvers

A resolver converts an application rule into a list of approver models. It runs when you submit a request:

use ByRcsc\LaravelApproval\Contracts\ApproverResolver;
use ByRcsc\LaravelApproval\Data\StageSnapshot;
use Illuminate\Database\Eloquent\Model;

final class FinanceTeamMembers implements ApproverResolver
{
    public function resolve(
        StageSnapshot $stage,
        Model $approvable,
        Model $requester,
        array $config,
    ): iterable {
        return Team::findOrFail($config['team'])->members;
    }
}

Register it on the stage instead of a concrete approver:

$stage->approvers()->create([
    'resolver' => FinanceTeamMembers::class,
    'resolver_config' => ['team' => $financeTeam->getKey()],
]);

An approver entry names either a model or a resolver, never both. The package enforces this rule in code, because no portable database constraint can express it.

Rules resolvers must follow

  • Return persisted models. An assignment records an exact actor identity, so an unsaved model raises InvalidWorkflowException.
  • Be free of side effects. The package plans every stage before it writes the request. A resolver can run, and the submission can still stop because a later stage is invalid.
  • Tolerate an empty answer. A resolver may return nothing. The package decides whether an empty result is fatal. It is fatal for a stage whose condition passed, because a stage with no approvers can never complete.

Laravel Approval resolves resolvers from the container, so constructor injection works.

Resolvers run exactly once

A resolver runs once, at submission. It is not a live query. The package writes its result into the stage's assignments and never runs the resolver again for that request. If the approver list changes tomorrow, today's assignments stay the same.

To fix a stale assignment, delegate it, reassign it, or resync the request.

Duplicates collapse

Two entries that name the same record produce one assignment, even when they come from different sources. The same person returned by two resolvers on one stage produces one assignment. Duplicate assignments do not increase the number of approvals available to the stage.

Shipped resolvers

StaticUsersResolver

StaticUsersResolver assigns a fixed list of primary keys. Use it when several stages share one list and you want to edit the list in one place:

use ByRcsc\LaravelApproval\Resolvers\StaticUsersResolver;

$stage->approvers()->create([
    'resolver' => StaticUsersResolver::class,
    'resolver_config' => ['ids' => [4, 11], 'model' => Team::class],
]);

model is optional and defaults to approval.user_model.

CallbackResolver

CallbackResolver calls a method on a class your application already has. An existing ManagerLookup::forEmployee() becomes an approver rule, and you do not rewrite it around the package's interface:

use ByRcsc\LaravelApproval\Resolvers\CallbackResolver;

$stage->approvers()->create([
    'resolver' => CallbackResolver::class,
    'resolver_config' => ['callback' => 'App\Org\ManagerLookup@forEmployee'],
]);

The package resolves the class from the container, so the container injects its dependencies. The method receives the same four arguments as ApproverResolver::resolve(). Omit @method to call __invoke. The package drops any returned value that is not an Eloquent model.

Use CallbackResolver when the logic already exists. Otherwise, write a resolver. An implementation of the interface is about fifteen lines, and its class name says what it does.

Assignment sources

Every assignment records how its current holder received it:

SourceMeaning
assignedCopied from a concrete approver entry at submission
resolvedProduced by a resolver at submission
delegatedAn approver handed the assignment on themselves
reassignedAn administrator rewrote who the assignment belongs to
escalatedAn overdue stage's escalation policy added the approver
resyncedAn administrative resync added the approver

The submit-time snapshot produces assigned and resolved. The other sources record a deliberate change to a request already in progress.

Self-approval

A requester may not approve their own request unless the policy allows it. Enable it globally:

APPROVAL_ALLOW_SELF_APPROVAL=true

You can also enable it per workflow. The workflow setting takes precedence:

$workflow->update(['settings' => ['allow_self_approval' => true]]);

An eligibility check reports a denied attempt as Ineligibility::SelfApproval. A decision throws SelfApprovalException.

Stranded stages

An approval rule counts assignments. If assigned models are deleted, a stage can require more approvals than the remaining people can provide. The stage can no longer finish without administrative action.

approval:doctor finds stranded stages:

php artisan approval:doctor

The package allows a delegation or reassignment when it does not make the stage's shortage of approvers worse. The change does not have to remove the shortage. An already stranded stage can still receive the delegations and reassignments that try to repair it.

What to read next

  • Workflows and stages to set approval thresholds for resolved assignments.
  • Delegation and reassignment to change assignments on a live request.
  • Eligibility and authorization to check whether an assigned actor can decide.
  • Testing to use resolvers and assignments in application tests.
PreviousChoosing the workflowNextRequests and state
View source

On this page

  1. Approver resolvers
  2. Rules resolvers must follow
  3. Resolvers run exactly once
  4. Duplicates collapse
  5. Shipped resolvers
  6. StaticUsersResolver
  7. CallbackResolver
  8. Assignment sources
  9. Self-approval
  10. Stranded stages
  11. What to read next