PHP code example of moffhub / maker-checker

1. Go to this page and download the library: Download moffhub/maker-checker library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

moffhub / maker-checker example snippets


use Illuminate\Database\Eloquent\Model;
use Moffhub\MakerChecker\Traits\RequiresApproval;

class Post extends Model
{
    use RequiresApproval;

    // Optionally specify which actions eate' => ['editor' => 1],
        'delete' => ['admin' => 2],
    ];
}

$post = new Post(['title' => 'My Post', 'user_id' => auth()->id()]);
$saved = $post->save();

if (!$saved && Post::wasIntercepted()) {
    $request = Post::getInterceptedRequest();

    return response()->json([
        'message' => 'Your request has been submitted for approval.',
        'request_id' => $request->id,
        'request_code' => $request->code,
    ], 202); // HTTP 202 Accepted
}

// To bypass approval (for admin operations, seeders, etc.):
$post = Post::createWithoutApproval(['title' => 'Direct Create']);

// Or use the callback method:
Post::withoutApprovalDo(function () {
    Post::create(['title' => 'Also bypassed']);
});

use Moffhub\MakerChecker\Facades\MakerChecker;

// Create a request (auto-injects auth user as maker)
$request = MakerChecker::create(Post::class, ['title' => 'My Post']);

// With custom description
$request = MakerChecker::create(Post::class, ['title' => 'My Post'], 'Create a new blog post');

// Update a model
$request = MakerChecker::update($post, ['title' => 'Updated Title']);

// Delete a model
$request = MakerChecker::delete($post);

// Execute a custom action
$request = MakerChecker::execute(TransferFunds::class, ['amount' => 5000]);

// Approve (uses authenticated user)
MakerChecker::approve($request);
MakerChecker::approve($request, null, 'admin');           // With role
MakerChecker::approve($request, null, 'admin', 'LGTM');  // With role and remarks

// Or with explicit user
MakerChecker::approve($request, $approver, 'admin');

// Reject
MakerChecker::reject($request);
MakerChecker::reject($request, null, 'Missing information');

// Cancel (only maker can cancel)
MakerChecker::cancel($request);

$request->approve();                          // Uses auth user
$request->approve(null, 'admin');             // With role
$request->approve($user, 'admin', 'Approved'); // Explicit user

$request->reject(null, 'Not approved');
$request->cancel();

use Moffhub\MakerChecker\Facades\MakerChecker;

$request = MakerChecker::request()
    ->toCreate(Post::class, ['title' => 'My Post'])
    ->madeBy(auth()->user())
    ->description('Create a new blog post')
    ->withApprovals(['editor' => 1, 'admin' => 1])
    ->beforeApproval(fn($r) => Log::info('Approving...'))
    ->afterApproval(fn($r) => Notification::send(...))
    ->save();

use Moffhub\MakerChecker\Contracts\MakerCheckerUserContract;

class User extends Authenticatable implements MakerCheckerUserContract
{
    public function hasMakerCheckerPermission(string $permission): bool
    {
        return $this->hasPermission($permission); // Your permission logic
    }

    public function getMakerCheckerTeamId(): ?int
    {
        return $this->team_id; // For multi-tenancy, or null
    }

    public function getMakerCheckerRole(): ?string
    {
        return $this->role; // e.g., 'admin', 'manager'
    }

    public function getMakerCheckerEmail(): ?string
    {
        return $this->email;
    }
}

use Moffhub\MakerChecker\Facades\MakerChecker;
use App\Models\Post;

// Create request for a new Post
$request = MakerChecker::request()
    ->toCreate(Post::class, [
        'title' => 'My New Post',
        'content' => 'Post content here...',
        'user_id' => auth()->id(),
    ])
    ->madeBy(auth()->user())
    ->description('Create a new blog post')
    ->save();

use Moffhub\MakerChecker\Facades\MakerChecker;

// Simple - uses authenticated user automatically
MakerChecker::approve($request);
MakerChecker::reject($request, null, 'Missing dmin', 'Looks good!');

// Or call directly on the request model
$request->approve();
$request->approve(null, 'admin');
$request->reject(null, 'Not approved');
$request->cancel(); // Only maker can cancel

MakerChecker::request()
    ->toCreate(Post::class, ['title' => 'New Post', 'content' => '...'])
    ->withApprovals(['admin' => 1])
    ->madeBy(auth()->user())
    ->save();

MakerChecker::request()
    ->toUpdate($post, ['title' => 'Updated Title'])
    ->madeBy(auth()->user())
    ->save();

MakerChecker::request()
    ->toDelete($post)
    ->withApprovals(['admin' => 2]) // Require 2 admin approvals
    ->madeBy(auth()->user())
    ->save();

use Moffhub\MakerChecker\Contracts\ExecutableRequest;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class TransferFunds extends ExecutableRequest
{
    public function execute(MakerCheckerRequest $request): void
    {
        $payload = $request->payload;

        // Perform the transfer
        BankService::transfer(
            from: $payload['from_account'],
            to: $payload['to_account'],
            amount: $payload['amount']
        );
    }

    public function uniqueBy(): array
    {
        return ['from_account', 'to_account', 'amount'];
    }

    public function beforeApproval(MakerCheckerRequest $request): void
    {
        // Validate accounts still exist
    }

    public function afterApproval(MakerCheckerRequest $request): void
    {
        // Send notification
    }

    public function onFailure(MakerCheckerRequest $request): void
    {
        // Handle failure
    }
}

MakerChecker::request()
    ->toExecute(TransferFunds::class, [
        'from_account' => 'ACC001',
        'to_account' => 'ACC002',
        'amount' => 5000,
    ])
    ->withApprovals(['finance' => 1, 'manager' => 1])
    ->madeBy(auth()->user())
    ->save();

MakerChecker::request()
    ->toCreate(User::class, $userData)
    ->withApprovals([
        'hr' => 1,        // 1 HR approval
        'admin' => 2,     // 2 Admin approvals
        'manager' => 1,   // 1 Manager approval
    ])
    ->madeBy(auth()->user())
    ->save();

$request->getApprovalCount();        // Total approvals received
$request->getPendingRoles();         // ['admin' => 1, ...] remaining
$request->hasMetApprovalThreshold(); // true/false

// Require approval from a specific user by email
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['[email protected]'])
    ->madeBy(auth()->user())
    ->save();

// Require approval from multiple specific users
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['[email protected]', '[email protected]'])
    ->madeBy(auth()->user())
    ->save();

// Require approval from user by ID
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove([(string) $cfoUser->id])
    ->madeBy(auth()->user())
    ->save();

// Requires 1 admin approval AND approval from the CFO
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->withRoleAndUserApprovals(
        roles: ['admin' => 1],
        users: ['[email protected]']
    )
    ->madeBy(auth()->user())
    ->save();

// This will throw an exception if the user doesn't exist
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['[email protected]'])
    ->madeBy(auth()->user())
    ->save();
// Throws: RequestCouldNotBeInitiated

// Disable validation if needed (not recommended)
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['[email protected]'], validateExistence: false)
    ->madeBy(auth()->user())
    ->save();

$request->>getPendingUsers();         // ['[email protected]', ...] remaining

$request = MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['[email protected]'])
    ->madeBy(auth()->user())
    ->save();

// Only the CFO can approve - other users will get an error
MakerChecker::approve($request, $cfoUser, 'user'); // Works
MakerChecker::approve($request, $otherUser);       // Throws exception

use Moffhub\MakerChecker\Contracts\MakerCheckerConfigurable;
use Moffhub\MakerChecker\Enums\RequestType;

class Post extends Model implements MakerCheckerConfigurable
{
    public static function makerCheckerApprovals(): array
    {
        return [
            'create' => ['editor' => 1],
            'update' => ['editor' => 1],
            'delete' => ['admin' => 1, 'editor' => 1],
        ];
    }

    public static function makerCheckerUniqueFields(): array
    {
        return [
            'create' => ['title', 'slug'],
        ];
    }

    public static function 

'models' => [
    App\Models\User::class => [
        'approvals' => [
            'create' => ['hr' => 1, 'admin' => 1],
            'update' => ['admin' => 1],
            'delete' => ['admin' => 2],
        ],
        'unique_fields' => [
            'create' => ['email'],
        ],
        '> 1],
    'delete' => ['admin' => 2],
    'execute' => ['admin' => 1],
],

// config/maker-checker.php
'config_driver' => 'database',

use Moffhub\MakerChecker\Models\MakerCheckerConfig;

// Create with role-based approvals
MakerCheckerConfig::create([
    'configurable_type' => Post::class,
    'action' => 'delete',
    'approvals' => [
        'roles' => ['admin' => 2],
    ],
    'is_active' => true,
]);

// Create with both role and user approvals
MakerCheckerConfig::create([
    'configurable_type' => Contract::class,
    'action' => 'create',
    'approvals' => [
        'roles' => ['admin' => 1, 'legal' => 1],
        'users' => ['[email protected]', '[email protected]'],
    ],
    'description' => 'High-value contracts 

// config/maker-checker.php
'routes' => [
    'enabled' => true,
    'prefix' => 'api',
    'middleware' => ['api', 'auth:sanctum'],
],

MakerChecker::request()
    ->toCreate(Post::class, $data)
    ->beforeApproval(function ($request) {
        Log::info('About to approve', ['id' => $request->id]);
    })
    ->afterApproval(function ($request) {
        Notification::send($request->maker, new RequestApproved($request));
    })
    ->beforeRejection(function ($request) {
        // Cleanup logic
    })
    ->afterRejection(function ($request) {
        Notification::send($request->maker, new RequestRejected($request));
    })
    ->onFailure(function ($request) {
        Log::error('Request failed', ['id' => $request->id]);
    })
    ->madeBy(auth()->user())
    ->save();

use Moffhub\MakerChecker\Traits\RequiresApproval;

class Transaction extends Model
{
    use RequiresApproval;
}

class Transaction extends Model
{
    use RequiresApproval;

    // Only intercept these actions (default: all)
    protected static array $        'update' => ['finance' => 1],
        'delete' => ['finance' => 1, 'manager' => 1],
    ];
}

// Method 1: Static method (resets after one operation)
Transaction::withoutApproval();
Transaction::create([...]); // Bypassed
Transaction::create([...]); // Requires approval again

// Method 2: Instance methods
$transaction = Transaction::createWithoutApproval([...]);
$transaction->updateWithoutApproval(['amount' => 500]);
$transaction->deleteWithoutApproval();

// Method 3: Callback (recommended for multiple operations)
Transaction::withoutApprovalDo(function () {
    Transaction::create([...]);
    Transaction::create([...]);
    // All operations inside are bypassed
});

$transaction = new Transaction($data);
$saved = $transaction->save();

if (!$saved && Transaction::wasIntercepted()) {
    $request = Transaction::getInterceptedRequest();

    return response()->json([
        'message' => 'Pending approval',
        'request_id' => $request->id,
        'request_code' => $request->code,
    ], 202); // HTTP 202 Accepted
}

// Clear the intercepted state after handling
Transaction::clearInterceptedRequest();

// Enable exception mode
Transaction::throwOnIntercept(true);

try {
    $transaction = Transaction::create($data);
} catch (PendingApprovalException $e) {
    $request = $e->getRequest();
    return response()->json(['request' => $request->toArray()], 202);
}

$transaction = Transaction::find(1);

// Check if there are pending approvals
$transaction->hasPendingApproval(); // Any action
$transaction->hasPendingApproval(RequestType::DELETE); // Specific action

// Get pending approval requests
$pending = $transaction->getPendingApprovals();
$pendingDeletes = $transaction->getPendingApprovals(RequestType::DELETE);

// Set a specific user as the maker
Transaction::setApprovalMaker($adminUser);

// Operations will use $adminUser as the maker
Transaction::create([...]);

// Reset to use auth()->user() again
Transaction::setApprovalMaker(null);

use Moffhub\MakerChecker\Models\MakerCheckerRequest;

// Get requests visible to current user
$requests = MakerCheckerRequest::visibleTo(auth()->user())->get();

// Users with 'view_any_permission' see all requests
// Others see only their own or their team's requests

// config/maker-checker.php
'request_expiration_in_minutes' => 1440, // 24 hours

// app/Console/Kernel.php
$schedule->command('maker-checker:expire-requests')->hourly();

// config/maker-checker.php
'notifications' => [
    'enabled' => true,
    'channels' => ['mail', 'database'], // Notification channels
    'notify_maker' => true,              // Notify maker of approval/rejection
    'sequential' => false,               // See "Sequential Notifications" below
    'user_model' => App\Models\User::class,
    'role_attribute' => 'role',          // Attribute containing user's role
],

// Default behavior: finds users where role = 'admin'
// When request 

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Moffhub\MakerChecker\Contracts\ApproverResolver;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class CustomApproverResolver implements ApproverResolver
{
    public function getApproversForRole(MakerCheckerRequest $request, string $role): Collection
    {
        // Custom logic: Spatie permissions, team filtering, etc.
        return User::role($role)
            ->where('team_id', $request->team_id)
            ->where('id', '!=', $request->maker_id)
            ->get();
    }

    public function getAllApprovers(MakerCheckerRequest $request): Collection
    {
        // Get all users who can approve any role or are specifically =', $request->maker_id)
            ->get();
    }

    public function getApproverByIdentifier(string $identifier): ?Model
    {
        return User::where('email', $identifier)
            ->orWhere('id', $identifier)
            ->first();
    }

    public function userExists(string $identifier): bool
    {
        return $this->getApproverByIdentifier($identifier) !== null;
    }

    public function validateUsersExist(array $userIdentifiers): array
    {
        return array_filter($userIdentifiers, fn($id) => !$this->userExists($id));
    }
}

// Register in AppServiceProvider
$this->app->bind(ApproverResolver::class, CustomApproverResolver::class);

'notifications' => [
    'sequential' => true,
],

// After partial approval, notify next approvers
MakerChecker::approve($request, $user, 'editor');

if ($request->isPartiallyApproved()) {
    MakerChecker::notifyNextApprovers($request);
}

// config/maker-checker.php
'notifications' => [
    'pending_notification' => App\Notifications\CustomPendingNotification::class,
    'approved_notification' => App\Notifications\CustomApprovedNotification::class,
    'rejected_notification' => App\Notifications\CustomRejectedNotification::class,
],

use Illuminate\Notifications\Notification;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class CustomPendingNotification extends Notification
{
    public function __construct(
        public MakerCheckerRequest $request,
        public ?string $role = null
    ) {}

    public function via($notifiable): array
    {
        return ['mail', 'database', 'slack']; // Add any channels
    }

    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Custom: Approval Needed')
            ->line("Please review: {$this->request->description}")
            ->action('Review', url("/approvals/{$this->request->code}"));
    }

    // Add toSlack(), toArray(), etc. as needed
}

// Notify all approvers about a pending request
MakerChecker::notifyApprovers($request);

// Notify with sequential mode (only first role)
MakerChecker::notifyApprovers($request, sequential: true);

// Notify next approvers after partial approval
MakerChecker::notifyNextApprovers($request);

// Access the notification service directly
$service = MakerChecker::notifications();
$service->notifyPendingApproval($request);
$service->notifyRequestApproved($request);
$service->notifyRequestRejected($request);

// config/maker-checker.php
'callbacks' => [
    'on_initiated' => [
        App\MakerChecker\Callbacks\LogNewRequest::class,
        App\MakerChecker\Callbacks\SendSlackNotification::class,
    ],
    'after_approval' => [
        App\MakerChecker\Callbacks\UpdateAuditLog::class,
    ],
    'after_rejection' => [
        App\MakerChecker\Callbacks\NotifyManager::class,
    ],
    'on_failure' => [
        App\MakerChecker\Callbacks\AlertOps::class,
    ],
],

use Moffhub\MakerChecker\Contracts\RequestCallback;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class LogNewRequest implements RequestCallback
{
    public function handle(MakerCheckerRequest $request): void
    {
        Log::info('New approval request', [
            'code' => $request->code,
            'type' => $request->type->value,
            'maker' => $request->maker_id,
        ]);
    }
}

// In a service provider or bootstrap file
MakerChecker::callbacks()
    ->onInitiated(function (MakerCheckerRequest $request) {
        // Request was just created
        Log::info('Request initiated', ['code' => $request->code]);
    })
    ->afterApproval(function (MakerCheckerRequest $request) {
        // Request was fully approved and executed
        Notification::send($request->maker, new RequestCompleted($request));
    })
    ->afterRejection(function (MakerCheckerRequest $request) {
        // Request was rejected
        event(new RequestRejectedEvent($request));
    })
    ->onFailure(function (MakerCheckerRequest $request) {
        // Execution failed
        Alert::critical("Request {$request->code} failed");
    });

// config/maker-checker.php
'routes' => [
    'rate_limit' => env('MAKER_CHECKER_RATE_LIMIT', 60), // requests per minute
],

Route::middleware('throttle:maker-checker')->group(function () {
    // Your custom maker-checker routes
});

// config/maker-checker.php
'audit' => [
    'enabled' => env('MAKER_CHECKER_AUDIT_ENABLED', true),
    'driver' => env('MAKER_CHECKER_AUDIT_DRIVER', 'database'),
    'table_name' => 'maker_checker_audit_logs',
    'log_channel' => null, // Laravel log channel for 'log' driver
],

class User extends Authenticatable implements MakerCheckerUserContract
{
    public function getMakerCheckerTeamId(): ?int
    {
        return $this->team_id;
    }

    // ... other contract methods
}

MakerChecker::request()
    ->toCreate(Invoice::class, $data, teamId: auth()->user()->team_id)
    ->madeBy(auth()->user())
    ->save();

MakerChecker::request()
    ->toCreate(Invoice::class, $data, 

// config/maker-checker.php
'notifications' => [
    'team_scoping' => true,
    'team_attribute' => 'team_id', // attribute on user model
],

MakerCheckerConfig::create([
    'configurable_type' => Invoice::class,
    'action' => 'create',
    'approvals' => ['manager' => 1],
    'team_id' => 42, // Applies only to team 42
    'is_active' => true,
]);

// Users only see requests from their team
$requests = MakerCheckerRequest::visibleTo(auth()->user())->get();

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Moffhub\MakerChecker\Contracts\ApproverResolver;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class SpatieApproverResolver implements ApproverResolver
{
    public function getApproversForRole(MakerCheckerRequest $request, string $role): Collection
    {
        return User::permission("maker-checker.approve.{$role}")
            ->when($request->team_id, fn($q) => $q->where('team_id', $request->team_id))
            ->where('id', '!=', $request->maker_id)
            ->get();
    }

    public function getAllApprovers(MakerCheckerRequest $request): Collection
    {
        $) {
            $query->whereIn('email', $userIdentifiers)
                ->orWhereIn('id', $userIdentifiers);
        })->get();
    }

    public function getApproverByIdentifier(string $identifier): ?Model
    {
        return User::where('email', $identifier)
            ->orWhere('id', $identifier)
            ->first();
    }

    public function userExists(string $identifier): bool
    {
        return $this->getApproverByIdentifier($identifier) !== null;
    }

    public function validateUsersExist(array $userIdentifiers): array
    {
        return array_filter($userIdentifiers, fn($id) => !$this->userExists($id));
    }
}

$this->app->bind(ApproverResolver::class, SpatieApproverResolver::class);

use Moffhub\MakerChecker\Contracts\ExecutableRequest;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class BulkUserImport extends ExecutableRequest
{
    /**
     * Execute the approved action.
     */
    public function execute(MakerCheckerRequest $request): void
    {
        $payload = $request->payload;

        foreach ($payload['users'] as $userData) {
            User::create([
                'name' => $userData['name'],
                'email' => $userData['email'],
                'role' => $userData['role'] ?? 'user',
                'team_id' => $request->team_id,
            ]);
        }
    }

    /**
     * Fields used to determine request uniqueness.
     * Prevents duplicate import requests with the same file hash.
     */
    public function uniqueBy(): array
    {
        return ['file_hash'];
    }

    /**
     * Runs before the request is approved.
     * Use for pre-flight validation.
     */
    public function beforeApproval(MakerCheckerRequest $request): void
    {
        $payload = $request->payload;

        // Verify no duplicate emails in the import
        $emails = array_column($payload['users'], 'email');
        $existing = User::whereIn('email', $emails)->pluck('email');

        if ($existing->isNotEmpty()) {
            throw new \RuntimeException(
                'Import contains existing emails: ' . $existing->implode(', ')
            );
        }
    }

    /**
     * Runs after successful approval and execution.
     */
    public function afterApproval(MakerCheckerRequest $request): void
    {
        $count = count($request->payload['users'] ?? []);
        Log::info("Bulk import completed: {$count} users imported", [
            'request_id' => $request->id,
            'team_id' => $request->team_id,
        ]);
    }

    /**
     * Runs before the request is rejected.
     */
    public function beforeRejection(MakerCheckerRequest $request): void
    {
        // Optional: cleanup temporary files
    }

    /**
     * Runs after the request is rejected.
     */
    public function afterRejection(MakerCheckerRequest $request): void
    {
        Notification::send($request->maker, new ImportRejectedNotification($request));
    }

    /**
     * Runs when execution fails.
     */
    public function onFailure(MakerCheckerRequest $request): void
    {
        Log::error('Bulk import failed', [
            'request_id' => $request->id,
            'exception' => $request->exception,
        ]);
    }
}

MakerChecker::request()
    ->toExecute(BulkUserImport::class, [
        'file_hash' => md5_file($uploadedFile->path()),
        'users' => $parsedUsers,
    ])
    ->withApprovals(['hr_manager' => 1, 'admin' => 1])
    ->madeBy(auth()->user())
    ->save();

// .env
[email protected],[email protected]

class CustomRequest extends \Moffhub\MakerChecker\Models\MakerCheckerRequest
{
    // Your customizations
}

// config/maker-checker.php
'routes' => [
    'rate_limit' => 120, // Increase to 120 per minute
],

'routes' => [
    'rate_limit' => 0, // Disabled
],
bash
php artisan vendor:publish --tag=maker-checker-migrations
php artisan migrate
bash
php artisan vendor:publish --tag=maker-checker-config

GET /api/maker-checker/requests?status=pending&type=create&team_id=1
bash
php artisan vendor:publish --tag=maker-checker-routes