DefaultWorkflowApprovalManager
in package
implements
WorkflowApprovalManagerInterface
Default implementation of WorkflowApprovalManagerInterface.
Manages approval gate lifecycle: creation, transactional response recording, candidate discovery for picker UIs, and resolution detection.
Current-user pending eligibility is owned by WorkflowApprovalsTable so queues, badges, and response recording use one canonical decision path.
Table of Contents
Interfaces
- WorkflowApprovalManagerInterface
- Interface for managing workflow approval gates and responses.
Constants
- MAX_OPTIMISTIC_RETRIES : mixed = 3
- Maximum retry attempts for optimistic lock conflicts.
- RETRY_DELAY_US : mixed = 50000
- Base delay in microseconds between optimistic lock retries (50ms).
Properties
- $container : ContainerInterface|null
Methods
- __construct() : mixed
- cancelApprovalsForInstance() : ServiceResult
- Cancel all pending approvals for a workflow instance.
- createApproval() : ServiceResult
- Create an approval gate for a workflow node.
- getApprovalsForInstance() : array<string|int, WorkflowApproval>
- Get all approvals for a workflow instance.
- getEligibleApprovers() : array<string|int, Member>
- Get possible member candidates for approval picker/reassignment UIs.
- getNextApproverCandidates() : array<string|int, int>
- Get candidate member IDs for the "next approver" picker in serial pick-next mode.
- getPendingApprovalsForMember() : array<string|int, WorkflowApproval>
- List the approvals currently pending the given member's action.
- isResolved() : bool
- Check if an approval gate has been resolved.
- reassignApproval() : ServiceResult
- Reassign a pending approval to a different eligible member.
- recordResponse() : ServiceResult
- Record a member's approval decision with optimistic-locking retries.
- attemptRecordResponse() : ServiceResult
- Single transactional attempt to record an approval response.
- findDynamicApprovers() : array<string|int, Member>
- Find Member entities eligible via the dynamic callback approver.
- findEligibleMembers() : array<string|int, Member>
- Find members who are potential candidates for an approval picker.
- findMembersByPermission() : array<string|int, Member>
- Find all members who have an active (unexpired) role granting the specified permission.
- findMembersByRole() : array<string|int, Member>
- Find all members who have the specified active (unexpired) role.
- getResolverBackedPendingDynamicApprovals() : array<string|int, WorkflowApproval>
- Load callback-only dynamic approvals whose eligibility cannot be represented by lookup fields.
- getWorkflowInstanceResponderIds() : array<string|int, int>
- hasPriorDynamicWorkflowResponse() : bool
- Dynamic approvers may qualify through multiple routes, but each member can only respond once per workflow.
- parseDeadline() : DateTime
- Parse a deadline string (e.g., "7d", "24h") into a DateTime.
- resolveDynamicApproverIds() : array<string|int, int>
- Resolve eligible member IDs via configured callback service.
- saveWithVersionCheck() : void
- Save an approval entity with optimistic version bump.
Constants
MAX_OPTIMISTIC_RETRIES
Maximum retry attempts for optimistic lock conflicts.
private
mixed
MAX_OPTIMISTIC_RETRIES
= 3
RETRY_DELAY_US
Base delay in microseconds between optimistic lock retries (50ms).
private
mixed
RETRY_DELAY_US
= 50000
Properties
$container
private
ContainerInterface|null
$container
= null
Methods
__construct()
public
__construct([ContainerInterface|null $container = null ]) : mixed
Parameters
- $container : ContainerInterface|null = null
-
Optional DI container for dynamic resolvers
cancelApprovalsForInstance()
Cancel all pending approvals for a workflow instance.
public
cancelApprovalsForInstance(int $instanceId) : ServiceResult
Parameters
- $instanceId : int
Tags
Return values
ServiceResultcreateApproval()
Create an approval gate for a workflow node.
public
createApproval(int $instanceId, string $nodeId, int $executionLogId, array<string|int, mixed> $config) : ServiceResult
Parameters
- $instanceId : int
- $nodeId : string
- $executionLogId : int
- $config : array<string|int, mixed>
Tags
Return values
ServiceResultgetApprovalsForInstance()
Get all approvals for a workflow instance.
public
getApprovalsForInstance(int $instanceId) : array<string|int, WorkflowApproval>
Parameters
- $instanceId : int
Tags
Return values
array<string|int, WorkflowApproval>getEligibleApprovers()
Get possible member candidates for approval picker/reassignment UIs.
public
getEligibleApprovers(int $approvalId) : array<string|int, Member>
Parameters
- $approvalId : int
Tags
Return values
array<string|int, Member>getNextApproverCandidates()
Get candidate member IDs for the "next approver" picker in serial pick-next mode.
public
getNextApproverCandidates(int $approvalId[, int|null $currentMemberId = null ]) : array<string|int, int>
Returns the full pool of eligible members with the right permission, minus those who have already responded and the requesting member.
Parameters
- $approvalId : int
-
Workflow approval ID
- $currentMemberId : int|null = null
-
Current user ID to exclude (they're approving now)
Return values
array<string|int, int> —Member IDs eligible to be picked as next approver
getPendingApprovalsForMember()
List the approvals currently pending the given member's action.
public
getPendingApprovalsForMember(int $memberId) : array<string|int, WorkflowApproval>
Uses the same canonical eligibility logic as badges, mobile queues, and controller response guards so listed approvals remain actionable.
Parameters
- $memberId : int
Return values
array<string|int, WorkflowApproval>isResolved()
Check if an approval gate has been resolved.
public
isResolved(int $approvalId) : bool
Parameters
- $approvalId : int
Tags
Return values
boolreassignApproval()
Reassign a pending approval to a different eligible member.
public
reassignApproval(int $approvalId, int $newApproverId, int $adminMemberId[, string|null $reason = null ]) : ServiceResult
Parameters
- $approvalId : int
-
Workflow approval ID
- $newApproverId : int
-
Member ID of the new approver
- $adminMemberId : int
-
Member ID of the admin performing the reassignment
- $reason : string|null = null
-
Optional reason for reassignment
Return values
ServiceResult —Contains approvalId, instanceId, nodeId, previousApproverId, newApproverId on success
recordResponse()
Record a member's approval decision with optimistic-locking retries.
public
recordResponse(int $approvalId, int $memberId, string $decision[, string|null $comment = null ][, int|null $nextApproverId = null ][, array<string|int, mixed> $approverConfigUpdates = [] ]) : ServiceResult
Uses a three-layer concurrency defense:
- FOR UPDATE row lock — serialises concurrent transactions
- Atomic SQL increment — prevents lost count updates
- Optimistic version check — detects any out-of-band modification
Parameters
- $approvalId : int
-
Workflow approval ID
- $memberId : int
-
Responding member ID
- $decision : string
-
'approve' or 'reject'
- $comment : string|null = null
-
Optional comment
- $nextApproverId : int|null = null
-
Optional next approver for serial pick-next chains
- $approverConfigUpdates : array<string|int, mixed> = []
-
Config values to commit atomically with the response
Return values
ServiceResultattemptRecordResponse()
Single transactional attempt to record an approval response.
private
attemptRecordResponse(int $approvalId, int $memberId, string $decision, string|null $comment, int|null $nextApproverId, array<string|int, mixed> $approverConfigUpdates) : ServiceResult
Parameters
- $approvalId : int
- $memberId : int
- $decision : string
- $comment : string|null
- $nextApproverId : int|null
- $approverConfigUpdates : array<string|int, mixed>
Tags
Return values
ServiceResultfindDynamicApprovers()
Find Member entities eligible via the dynamic callback approver.
private
findDynamicApprovers(WorkflowApproval $approval) : array<string|int, Member>
Parameters
- $approval : WorkflowApproval
Return values
array<string|int, Member>findEligibleMembers()
Find members who are potential candidates for an approval picker.
private
findEligibleMembers(WorkflowApproval $approval) : array<string|int, Member>
This is not the final current-user response eligibility check. Response recording delegates to WorkflowApprovalsTable::isPendingApprovalForMember().
Parameters
- $approval : WorkflowApproval
Return values
array<string|int, Member>findMembersByPermission()
Find all members who have an active (unexpired) role granting the specified permission.
private
findMembersByPermission(string $permissionName) : array<string|int, Member>
Parameters
- $permissionName : string
Return values
array<string|int, Member>findMembersByRole()
Find all members who have the specified active (unexpired) role.
private
findMembersByRole(string $roleName) : array<string|int, Member>
Parameters
- $roleName : string
Return values
array<string|int, Member>getResolverBackedPendingDynamicApprovals()
Load callback-only dynamic approvals whose eligibility cannot be represented by lookup fields.
private
getResolverBackedPendingDynamicApprovals(int $memberId) : array<string|int, WorkflowApproval>
Parameters
- $memberId : int
-
Member ID.
Return values
array<string|int, WorkflowApproval>getWorkflowInstanceResponderIds()
private
getWorkflowInstanceResponderIds(int $workflowInstanceId) : array<string|int, int>
Parameters
- $workflowInstanceId : int
Return values
array<string|int, int>hasPriorDynamicWorkflowResponse()
Dynamic approvers may qualify through multiple routes, but each member can only respond once per workflow.
private
hasPriorDynamicWorkflowResponse(WorkflowApproval $approval, int $memberId) : bool
Parameters
- $approval : WorkflowApproval
- $memberId : int
Return values
boolparseDeadline()
Parse a deadline string (e.g., "7d", "24h") into a DateTime.
private
parseDeadline(string $deadline) : DateTime
Parameters
- $deadline : string
Return values
DateTimeresolveDynamicApproverIds()
Resolve eligible member IDs via configured callback service.
private
resolveDynamicApproverIds(WorkflowApproval $approval) : array<string|int, int>
Expects approver_config: {"service": "App\Services\MyService", "method": "getEligibleApprovers"} The callback receives the WorkflowApproval and must return int[] of member IDs.
Parameters
- $approval : WorkflowApproval
Tags
Return values
array<string|int, int>saveWithVersionCheck()
Save an approval entity with optimistic version bump.
private
saveWithVersionCheck(mixed $approvalsTable, WorkflowApproval $approval) : void
Increments the version on save and verifies the row was updated. Must be called within a transaction that already holds the FOR UPDATE lock.
Parameters
- $approvalsTable : mixed
- $approval : WorkflowApproval