PHPackages                             menma/approval-binary - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. menma/approval-binary

ActiveLibrary[Framework](/categories/framework)

menma/approval-binary
=====================

Binary approval engine for Laravel

V1.0.1(1mo ago)11AGPL-3.0-or-laterPHPPHP ^8.1

Since Feb 17Pushed 1mo agoCompare

[ Source](https://github.com/menma977/Approval-Binary)[ Packagist](https://packagist.org/packages/menma/approval-binary)[ Fund](https://www.buymeacoffee.com/comowlminer)[ GitHub Sponsors](https://github.com/menma977)[ RSS](/packages/menma-approval-binary/feed)WikiDiscussions main Synced today

READMEChangelog (4)Dependencies (12)Versions (7)Used By (0)

Approval Binary
===============

[](#approval-binary)

Approval Binary is a Laravel package for building approval workflow engines with binary/bitmask state tracking.

It is not a CRUD approval scaffold and it does not ship a UI builder. The package focuses on reusable workflow orchestration: define approval blueprints in database records, resolve contributors at runtime, clone the selected blueprint into an execution event, and track approval progress with bitwise masks.

Contents
--------

[](#contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Why Binary Approval](#why-binary-approval)
- [Architecture](#architecture)
- [Runtime Lifecycle](#runtime-lifecycle)
- [Dynamic Contributors](#dynamic-contributors)
- [Condition Routing](#condition-routing)
- [Using the Package](#using-the-package)
- [Workflow Behavior](#workflow-behavior)
- [Use Cases](#use-cases)
- [Technical Philosophy](#technical-philosophy)

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10.x / 11.x / 12.x / 13.x

Installation
------------

[](#installation)

```
composer require menma/approval-binary
```

Publish package assets:

```
php artisan vendor:publish --tag=approval-migrations
php artisan vendor:publish --tag=approval-config
php artisan vendor:publish --tag=approval-lang
```

Run migrations:

```
php artisan migrate
```

Why Binary Approval
-------------------

[](#why-binary-approval)

Approval Binary uses bitwise state representation, inspired by the same idea behind Linux permission masks. Instead of storing approval progress only as rows or a single status string, each workflow component contributes one bit to a bigint approval mask.

Example:

Approval componentComponent positionRuntime mask valueHR`0``1 all();
    }
}
```

Register it in `config/approval.php`:

```
'group' => [
    Menma\Approval\Models\ApprovalGroup::class,
    App\Models\Department::class,
    App\Models\Position::class,
],
```

Then use the model as an approval contributor:

```
ApprovalContributor::create([
    'approval_component_id' => $component->id,
    'approvable_type' => App\Models\Department::class,
    'approvable_id' => $department->id,
]);
```

When the event is initialized, the department is resolved into concrete `ApprovalEventContributor` users.

Condition Routing
-----------------

[](#condition-routing)

The condition system controls which components are copied into the runtime event. This is how the engine supports jump/skip flows and contextual routing.

Structure:

```
Approval
└── ApprovalCondition
    └── ApprovalConditionComponent
        └── ApprovalComponent

```

Rules:

- Conditions are evaluated by highest `priority` first.
- `priority = 0` is the default fallback condition and is created automatically when an approval is created.
- A condition can contain multiple condition components.
- Each condition component points to one approval component.
- `expression = null` means the component always matches for that condition.
- `expression` supports `all` (AND) and `any` (OR) routing logic.
- Expression paths use Laravel `data_get` dot notation against `getApprovalConditions()`.
- The first condition that produces at least one matched component wins.
- Invalid expression structure or unsupported operators fail loudly with validation errors.

Condition `all`/`any` is only for routing expressions. Component contributor approval logic is separate and uses `ContributorTypeEnum::AND` or `ContributorTypeEnum::OR`.

Expression example:

```
use Menma\Approval\Support\ApprovalExpression;

ApprovalExpression::all()
    ->where('requester.division', '==', 'HR')
    ->where('amount', '>', 10000)
    ->toArray();
```

This produces JSON-like data:

```
[
    'all' => [
        ['path' => 'requester.division', 'operator' => '==', 'value' => 'HR'],
        ['path' => 'amount', 'operator' => '>', 'value' => 10000],
    ],
]
```

Supported operators are configured in `config('approval.operators')`:

```
['', '=', '==', '!=']
```

### Contextual Routing Example

[](#contextual-routing-example)

Requirement: if requester division is HR, skip directly to manager approval. Otherwise use the default approval path.

```
Approval: Operational Request
├── Condition priority 1: requester.division == HR
│   └── Manager Approval
└── Condition priority 0: default fallback
    ├── HR Approval
    ├── Manager Approval
    └── Finance Approval

```

Implementation shape:

```
use Menma\Approval\Models\ApprovalCondition;
use Menma\Approval\Models\ApprovalConditionComponent;
use Menma\Approval\Support\ApprovalExpression;

$hrShortcut = ApprovalCondition::create([
    'approval_id' => $approval->id,
    'priority' => 1,
]);

ApprovalConditionComponent::create([
    'approval_condition_id' => $hrShortcut->id,
    'approval_component_id' => $managerComponent->id,
    'expression' => ApprovalExpression::all()
        ->where('requester.division', '==', 'HR')
        ->toArray(),
]);
```

The default condition (`priority = 0`) already links new approval components automatically with `expression = null`. If no higher-priority condition selects components, the resolver falls back to the default path.

Using the Package
-----------------

[](#using-the-package)

### Prepare an App Model

[](#prepare-an-app-model)

Extend `ApprovalAbstract` on models that need approval behavior.

```
namespace App\Models;

use Menma\Approval\Abstracts\ApprovalAbstract;
use Menma\Approval\Models\ApprovalEvent;

class PurchaseOrder extends ApprovalAbstract
{
    protected $guarded = [];

    public function getApprovalConditions(): array
    {
        return [
            'amount' => $this->amount,
            'requester' => [
                'division' => $this->requester?->division?->name,
                'position' => $this->requester?->position?->name,
            ],
        ];
    }

    protected function onApprove(ApprovalEvent $event): void
    {
        $this->update(['approval_status' => $event->status->value]);
    }

    protected function onReject(ApprovalEvent $event): void
    {
        $this->update(['approval_status' => $event->status->value]);
    }
}
```

### Define a Workflow Blueprint

[](#define-a-workflow-blueprint)

```
use App\Models\PurchaseOrder;
use App\Models\User;
use Menma\Approval\Enums\ApprovalTypeEnum;
use Menma\Approval\Enums\ContributorTypeEnum;
use Menma\Approval\Models\Approval;
use Menma\Approval\Models\ApprovalComponent;
use Menma\Approval\Models\ApprovalContributor;
use Menma\Approval\Models\ApprovalDictionary;
use Menma\Approval\Models\ApprovalFlow;
use Menma\Approval\Models\ApprovalFlowComponent;

$dictionary = ApprovalDictionary::create([
    'key' => PurchaseOrder::class,
    'name' => 'Purchase Order',
]);

$flow = ApprovalFlow::create([
    'name' => 'Purchase Order Flow',
]);

ApprovalFlowComponent::create([
    'approval_flow_id' => $flow->id,
    'approval_dictionary_id' => $dictionary->id,
    'key' => PurchaseOrder::class,
]);

$approval = Approval::create([
    'approval_flow_id' => $flow->id,
    'name' => 'Purchase Order Approval v1',
    'type' => ApprovalTypeEnum::SEQUENTIAL,
]);

// step is the bit position. Runtime mask becomes 1  $approval->id,
    'name' => 'Manager Approval',
    'step' => 0,
    'type' => ContributorTypeEnum::OR,
]);

$financeComponent = ApprovalComponent::create([
    'approval_id' => $approval->id,
    'name' => 'Finance Approval',
    'step' => 1,
    'type' => ContributorTypeEnum::AND,
]);

ApprovalContributor::create([
    'approval_component_id' => $managerComponent->id,
    'approvable_type' => User::class,
    'approvable_id' => $managerUser->id,
]);

ApprovalContributor::create([
    'approval_component_id' => $financeComponent->id,
    'approvable_type' => App\Models\Department::class,
    'approvable_id' => $financeDepartment->id,
]);
```

### Execute Runtime Actions

[](#execute-runtime-actions)

```
$purchaseOrder->initEvent($requester);

$purchaseOrder->approve($manager);
$purchaseOrder->reject($financeUser);
$purchaseOrder->cancel($manager);
$purchaseOrder->rollback($admin);

// Force reset to draft, binary 0.
$purchaseOrder->force($admin);

// Force specific binary state and status.
$purchaseOrder->force($admin, 3, \Menma\Approval\Enums\ApprovalStatusEnum::APPROVED->value);
```

Manual service usage is also available:

```
$event = app(\Menma\Approval\Services\ApprovalService::class)
    ->forBinary($purchaseOrder)
    ->user($manager->id)
    ->approve();
```

Workflow Behavior
-----------------

[](#workflow-behavior)

### Sequential Approval

[](#sequential-approval)

`ApprovalTypeEnum::SEQUENTIAL` selects the first pending event component by step order when no explicit binary target is provided. The lower-level service API also accepts a binary target; use that only when the caller intentionally wants to address a specific event component.

### Parallel Approval

[](#parallel-approval)

`ApprovalTypeEnum::PARALLEL` can select a pending component that belongs to the current user. This allows different branches or components to move without strict global ordering.

### Contributor Logic

[](#contributor-logic)

Each component has `ContributorTypeEnum`:

- `OR`: any contributor approval completes the component.
- `AND`: all contributors must approve before the component is complete.

Rejection behavior follows component logic. For OR components, one rejection rejects the component/event. For AND components, rejection is evaluated against approvals.

### Partial Approval State

[](#partial-approval-state)

The event can be partially approved because `step` and `target` are masks. A model can be in `DRAFT` while some components are already completed.

### Jump and Skip

[](#jump-and-skip)

Conditions can select a subset of components. This changes the generated `target` mask for that event. It is not a visual BPMN jump; it is runtime component selection before the event snapshot is created.

### No-Contributor Components

[](#no-contributor-components)

If a selected component has no resolved contributors, the component is automatically marked approved. If no selected component has any contributor, the entire event is approved.

### Rollback

[](#rollback)

`rollback()` resets the runtime event to draft state, clears contributor/component timestamps for the active event, re-resolves contributors, re-runs condition routing, and rebuilds the target mask from the current blueprint. It updates or creates selected event components; it is not documented as a destructive cleanup of every stale historical component row.

### Force

[](#force)

`force()` bypasses normal contributor validation and sets the event state directly. Calling `force($user)` with no binary resets the event to draft (`0`). Passing a non-zero binary with an approved status can mark matching component bits as approved.

Event Runtime Queries
---------------------

[](#event-runtime-queries)

`ApprovalEvent` exposes useful accessors:

- `is_approved`
- `is_rejected`
- `is_cancelled`
- `is_rollback`
- `can_approve`
- `component`
- `current_component`

`can_approve` uses the current authenticated user (`Auth::id()`) and the next pending event component. If no user is authenticated, or the authenticated user is not a pending contributor, it returns false.

The package isolates bitwise SQL in `BitmaskQueryService` so mask queries can be implemented per database driver instead of scattering raw bit operations across the codebase.

Practical Use Cases
-------------------

[](#practical-use-cases)

- HR request workflow: route HR-originated requests directly to manager approval, while other divisions pass through HR first.
- Recruitment approval: recruiter, HR manager, department manager, and finance can be selected based on position, department, and salary range.
- Leave request: short leave can require manager only; long leave can add HR or director approval.
- Procurement approval: approval depth can change based on amount, category, department, or budget owner.
- Operational request flow: field requests can skip office-only components and route to the relevant operations role.
- Finance approval: invoice or reimbursement approval can combine department, finance, and executive layers.

Technical Philosophy
--------------------

[](#technical-philosophy)

Approval Binary is designed around:

- config-driven workflow records instead of hardcoded approval branches
- runtime event snapshots instead of mutable blueprint execution
- reusable orchestration services
- dynamic contributor resolution
- condition-based component routing
- bitmask state for compact partial approval tracking
- abstraction-first integration through model hooks and resolver interfaces

The package is meant to be extended by application code: define your own contributor resolver models, expose your own condition data, and use lifecycle hooks to connect approval results back into your domain.

Testing
-------

[](#testing)

```
./vendor/bin/pest
```

License
-------

[](#license)

GNU AGPLv3

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance90

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~29 days

Total

4

Last Release

48d ago

Major Versions

v0.1.1-beta → V1.0.02026-02-18

### Community

Maintainers

![](https://www.gravatar.com/avatar/58b7a85792fc7ec321d60ed1631dac74f19d51a5e2e559cb9481b4a13ff75b75?d=identicon)[mnema977](/maintainers/mnema977)

---

Top Contributors

[![menma977](https://avatars.githubusercontent.com/u/38025704?v=4)](https://github.com/menma977 "menma977 (28 commits)")

---

Tags

bitmasklaravelworkflow-engine

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/menma-approval-binary/health.svg)

```
[![Health](https://phpackages.com/badges/menma-approval-binary/health.svg)](https://phpackages.com/packages/menma-approval-binary)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.6k29.9M145](/packages/laravel-cashier)[laravel/scout

Laravel Scout provides a driver based solution to searching your Eloquent models.

1.7k55.0M619](/packages/laravel-scout)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M131](/packages/laravel-pulse)[mike-bronner/laravel-model-caching

Automatic caching for Eloquent models.

2.4k90.5k1](/packages/mike-bronner-laravel-model-caching)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45444.2k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
