›
byrcsc/laravel-approval · 1.x
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(),
]);A Team, Role, Position, or other container model is not expanded 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.
A resolver converts an application rule into a list of approver models. It runs when a request is submitted:
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 engine enforces that, since no portable database constraint expresses it.
InvalidWorkflowException.Resolvers are resolved from the container, so constructor injection works.
A resolver is a question asked at a point in time, not a live query. Its answer is written into the stage's assignments and never consulted again, so an approver list that changes tomorrow does not rewrite what was asked today.
Getting a stale assignment fixed is deliberate work: delegate it, reassign it, or resync the request.
Two entries that name the same record are one assignment, however differently they were reached. 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.
StaticUsersResolverA list of primary keys, useful when several stages share one list and you would rather edit it 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.
CallbackResolverDelegates to a class the application already has, so an existing
ManagerLookup::forEmployee() becomes an approver rule without being rewritten
around this package's interface:
use ByRcsc\LaravelApproval\Resolvers\CallbackResolver;
$stage->approvers()->create([
'resolver' => CallbackResolver::class,
'resolver_config' => ['callback' => 'App\Org\ManagerLookup@forEmployee'],
]);The class comes out of the container, so its own dependencies are injected, and
the method receives the same four arguments an ApproverResolver does. Omit
@method to call __invoke. Anything the method returns that is not an
Eloquent model is dropped.
Reach for this when the logic already exists somewhere. When it does not, write the resolver: an implementation of the interface is about fifteen lines and says what it is in its own name.
Every assignment records how its holder came to hold it:
| Source | Meaning |
|---|---|
assigned | Copied from a concrete approver entry at submission |
resolved | Produced by a resolver at submission |
delegated | An approver handed the assignment on themselves |
reassigned | An administrator rewrote who the assignment belongs to |
escalated | An overdue stage's escalation policy added the approver |
resynced | An administrative resync added the approver |
assigned and resolved are what the submit-time snapshot produced; the rest
record a deliberate intervention on a request already in flight.
A requester may not approve their own request unless the policy allows it. Enable it globally:
APPROVAL_ALLOW_SELF_APPROVAL=trueOr per workflow, which takes precedence:
$workflow->update(['settings' => ['allow_self_approval' => true]]);A denied attempt surfaces as Ineligibility::SelfApproval from an eligibility
check, or SelfApprovalException from a decision.
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 them:
php artisan approval:doctorDelegation and reassignment are judged against whether they made a stage's deficit worse, not against perfection, so an already-stranded stage stays open to the interventions that are trying to repair it.