trait RequiresApprovalWhenChanges (View source)

Applied to the model whose changes should go through an approval workflow.

On saving, an update is either captured as a pending Modification (when requiresApprovalWhen()/requiresApproval() says so), recorded as a full audit Modification row (the default), or saved as-is.

A pending Modification is decided as a whole by approve()/disapprove() unless the model opts into per-group decisions by overriding approvableItemGroups(): in that case each group becomes a ModificationItem that can be approved/rejected independently via approveItem()/disapproveItem().

Properties

protected int $approversRequired

Approvers needed to approve a change. To use another value, override approversRequired().

protected int $disapproversRequired

Disapprovers needed to reject a change. To use another value, override disapproversRequired().

protected bool $updateWhenApproved

Whether approving writes the change to the model, or only marks it as approved.

protected bool $deleteWhenDisapproved

Whether rejecting deletes the Modification instead of marking it as rejected.

protected bool $deleteWhenApproved

Whether approving deletes the Modification instead of marking it as approved.

Methods

static void
bootRequiresApprovalWhenChanges()

Register the model events that hold and audit saves.

int
approversRequired()

Approvers needed to apply a change. Override it to raise the number: the trait's property can't be redeclared with another default on the class using the trait (PHP fatal error).

int
disapproversRequired()

Disapprovers needed to reject a change. See approversRequired().

string
approvalCreationMode()

How a model implementing GatesCreation holds a creation until it's approved.

string
approvalDraftColumn()

The boolean column marking a row as waiting for approval in draft mode.

bool
requiresApprovalWhen(array $modifications)

Rule that decides when an approval process should be triggered for this model. Override directly, or define a requiresApprovalWhenTrait method to keep this concern free for other traits to compose.

array
approvableItemGroups(array $dirtyKeys)

Opt-in: return the field groups that should be decided independently of one another, e.g. ['rules' => ['rules'], 'notes' => ['notes']].

MorphMany
modifications()

The modifications recorded for this model.

static Modification>
creations()

Every creation recorded for this model, decided or not.

static 
pendingCreations()

Creations of this model still waiting for a decision. In deferred mode this is the only way to reach them, since there's no model yet to ask for its modifications().

modifier()

Returns the model that should be recorded as the author of the change.

void
applyModificationChanges(Modification $modification, bool $approved)

Carry out a decision on a modification. Votes call this once they reach the required count.

void
applyModificationItemChanges(ModificationItem $item, bool $approved)

Applies (or rejects) a single ModificationItem's proposed values, touching only the fields captured in that item.

bool
isForcedApprovalUpdate()

Whether the next save skips the approval gate.

bool
setForcedApprovalUpdate(bool $forced = true)

Make the next save skip the approval gate. It covers that save only.

requiresApproval(callable $callback)

Gate this instance with a callback on every later save.

noSaveModifications()

Stop recording audit rows for this instance's direct saves.

withModificationMeta(mixed $meta)

Attach meta to the next Modification this instance produces.

withFullSaveModification()

Record the full attribute set instead of only the changed attributes.

withFullSaveOnlyUpdateModification()

Record the full attribute set, but only when an attribute changed.

forceModification()

Make the next save produce a Modification even if nothing changed, to record or decide something outside the columns: an applied audit row, or a pending request if the gate holds the save.

withOperationId(string|null $operationId)

Tag the next Modifications of this instance with a shared operation id.

string|null
currentOperationId()

The operation id set with withOperationId().

getLastModification()

The last Modification this instance created or matched.

setLastModification(Modification|null $mod)

Set what getLastModification() returns.

recordModification(string $eventType, array $items = [], array|null $context = null, string|null $operationId = null, DateTimeInterface|null $occurredAt = null)

Explicit history API for events that don't go through saving (creation, deletion, attachments, signatures, external integrations).

static bool
captureSave(Model $item)

Hold a save behind a pending Modification.

static bool
captureModification(Model $item)

Prepare the audit row of a direct save, written once the save goes through.

Details

static void bootRequiresApprovalWhenChanges()

internal  
 

Register the model events that hold and audit saves.

Return Value

void

protected int approversRequired()

Approvers needed to apply a change. Override it to raise the number: the trait's property can't be redeclared with another default on the class using the trait (PHP fatal error).

Return Value

int

protected int disapproversRequired()

Disapprovers needed to reject a change. See approversRequired().

Return Value

int

string approvalCreationMode()

How a model implementing GatesCreation holds a creation until it's approved.

Override it to choose per model. Pending rows keep working if this changes, but a model leaving draft mode stops hiding its rows, so approve those first.

Return Value

string

string approvalDraftColumn()

The boolean column marking a row as waiting for approval in draft mode.

Return Value

string

protected bool requiresApprovalWhen(array $modifications)

Rule that decides when an approval process should be triggered for this model. Override directly, or define a requiresApprovalWhenTrait method to keep this concern free for other traits to compose.

Parameters

array $modifications

Return Value

bool

protected array approvableItemGroups(array $dirtyKeys)

Opt-in: return the field groups that should be decided independently of one another, e.g. ['rules' => ['rules'], 'notes' => ['notes']].

Changed keys not covered by any group stay together as a single decision in one more item, keyed ModificationItem::KEY_UNGROUPED.

Parameters

array $dirtyKeys

Return Value

array

MorphMany modifications()

The modifications recorded for this model.

Read on the connection of the Modification, which may not be the one of this model.

Return Value

MorphMany

static Modification> creations()

Every creation recorded for this model, decided or not.

Return Value

Modification>

static pendingCreations()

Creations of this model still waiting for a decision. In deferred mode this is the only way to reach them, since there's no model yet to ask for its modifications().

protected modifier()

Returns the model that should be recorded as the author of the change.

Override this to plug in your app's auth resolution.

void applyModificationChanges(Modification $modification, bool $approved)

Carry out a decision on a modification. Votes call this once they reach the required count.

If a listener vetoes the save, the modification stays pending.

Parameters

Modification $modification
bool $approved

True to apply the change, false to reject it.

Return Value

void

void applyModificationItemChanges(ModificationItem $item, bool $approved)

Applies (or rejects) a single ModificationItem's proposed values, touching only the fields captured in that item.

Parameters

ModificationItem $item
bool $approved

Return Value

void

bool isForcedApprovalUpdate()

Whether the next save skips the approval gate.

Return Value

bool

bool setForcedApprovalUpdate(bool $forced = true)

Make the next save skip the approval gate. It covers that save only.

Parameters

bool $forced

Return Value

bool

The value just set.

RequiresApprovalWhenChanges requiresApproval(callable $callback)

Gate this instance with a callback on every later save.

Parameters

callable $callback

Receives the dirty attributes. Return true to hold the save.

Return Value

RequiresApprovalWhenChanges

RequiresApprovalWhenChanges noSaveModifications()

Stop recording audit rows for this instance's direct saves.

RequiresApprovalWhenChanges withModificationMeta(mixed $meta)

Attach meta to the next Modification this instance produces.

Parameters

mixed $meta

Return Value

RequiresApprovalWhenChanges

RequiresApprovalWhenChanges withFullSaveModification()

Record the full attribute set instead of only the changed attributes.

RequiresApprovalWhenChanges withFullSaveOnlyUpdateModification()

Record the full attribute set, but only when an attribute changed.

RequiresApprovalWhenChanges forceModification()

Make the next save produce a Modification even if nothing changed, to record or decide something outside the columns: an applied audit row, or a pending request if the gate holds the save.

Every empty diff matches, so a second request while one is pending returns that one and drops its meta.

RequiresApprovalWhenChanges withOperationId(string|null $operationId)

Tag the next Modifications of this instance with a shared operation id.

Parameters

string|null $operationId

Return Value

RequiresApprovalWhenChanges

string|null currentOperationId()

The operation id set with withOperationId().

Return Value

string|null

Modification|null getLastModification()

The last Modification this instance created or matched.

A save that creates none leaves it as it was.

Return Value

Modification|null

RequiresApprovalWhenChanges setLastModification(Modification|null $mod)

Set what getLastModification() returns.

Parameters

Modification|null $mod

Return Value

RequiresApprovalWhenChanges

Modification recordModification(string $eventType, array $items = [], array|null $context = null, string|null $operationId = null, DateTimeInterface|null $occurredAt = null)

Explicit history API for events that don't go through saving (creation, deletion, attachments, signatures, external integrations).

Always recorded as an applied, direct Modification: it documents something that already happened, it does not gate anything.

$items entries look like: ['key' => 'attachments.contract', 'original' => null, 'proposed' => [...]].

Parameters

string $eventType
array $items
array|null $context
string|null $operationId
DateTimeInterface|null $occurredAt

Return Value

Modification

static bool captureSave(Model $item)

internal  
 

Hold a save behind a pending Modification.

Parameters

Model $item

Return Value

bool

False when the change was held, true when there was nothing to decide.

static bool captureModification(Model $item)

internal  
 

Prepare the audit row of a direct save, written once the save goes through.

Parameters

Model $item

Return Value

bool

Always true: the save goes on.