fzengin19/laravel-approval
Composer 安装命令:
composer require fzengin19/laravel-approval
包简介
Laravel için onay sistemi paketi
README 文档
README
A comprehensive approval system package for PHP 8.3+ and Laravel 10+. It provides a flexible way to manage approval statuses for your models. Developed with a TDD approach, with reliable code and high test coverage.
✨ Features
- 🚀 Easy Integration: Integrate your models with approval system by just adding a trait.
- ⚙️ Flexible Configuration: Two different modes (insert/upsert) and customizable settings.
- 💡 Configurable Initial State: Define a default status for models that haven't been through an approval process yet.
- 🧠 Smart Rejection Handling: Normalizes unknown rejection reasons to
otherwhile preserving the original text in the comment. - 🔍 Global Scope: Automatically show only approved records with configurable behavior.
- 🔔 Comprehensive Event System: 6 events with clear before/after transition timing and
causedBypayloads. - 📊 Statistics Service: Built-in statistics calculation with percentages and model-specific data.
- 🎭 Facade Support: Static API for easy usage with full IDE support.
- 🖥️ Artisan Commands: View statistics via CLI with table formatting.
- 🔧 Repository Pattern: Clean data access layer with insert/upsert modes.
- 🛡️ Validation System: Input validation with business rule enforcement.
- 🌐 Webhook Support: Configurable webhook endpoints for external integrations.
- 🎯 Model-specific Configuration: Override settings per model with inheritance.
- 🔄 Auto Pending: Automatically set models to pending status on creation.
- 🔗 Polymorphic Approval Audit Trail: Approval records keep a polymorphic
causerrelation to the configured user model. - ✨ Modern PHP: Utilizes PHP 8.3+ features and enums for type-safe statuses.
- 📈 Performance Optimized: Indexed database fields and efficient queries.
🚀 Quick Start
1. Installation
composer require fzengin19/laravel-approval
2. Publish and Migrate
php artisan vendor:publish --provider="LaravelApproval\LaravelApprovalServiceProvider"
php artisan migrate
3. Add Trait to Your Model
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use LaravelApproval\Contracts\ApprovableInterface; use LaravelApproval\Traits\Approvable; class Post extends Model implements ApprovableInterface { use HasFactory, Approvable; protected $fillable = [ 'title', 'content', ]; }
4. Start Using
use LaravelApproval\Enums\ApprovalStatus; use LaravelApproval\Facades\Approval; // Create a post $post = Post::create([ 'title' => 'My First Post', 'content' => 'This is my content...', ]); // Set to pending $post->setPending(1); // 1 = user ID // Approve $post->approve(1); // Reject with reason $post->reject(1, 'spam', 'This is spam content'); // Check status $post->isApproved(); // true $post->isPending(); // false $post->isRejected(); // false $post->getApprovalStatus(); // Returns ApprovalStatus::APPROVED enum case // Use query scopes $approvedPosts = Post::approved()->get(); $pendingPosts = Post::pending()->get(); $rejectedPosts = Post::rejected()->get(); // Get statistics $stats = Approval::getStatistics(Post::class);
That's it. Your model now has full approval functionality.
📦 Installation
Requirements
- PHP 8.3 or higher
- Laravel 10 or higher
Install via Composer
composer require fzengin19/laravel-approval
Publish Configuration
# Publish migrations and config php artisan vendor:publish --provider="LaravelApproval\LaravelApprovalServiceProvider" # Or publish only config php artisan vendor:publish --tag="laravel-approval-config"
Run Migrations
php artisan migrate
⚙️ Configuration
The published config file (config/approvals.php) contains comprehensive configuration options. Here are the key sections:
return [ // The user model that is responsible for approval actions. 'user_model' => config('auth.providers.users.model'), 'default' => [ // Core Settings 'mode' => 'insert', // 'insert' or 'upsert' 'auto_pending_on_create' => false, // Automatically create a pending approval on model creation 'show_only_approved_by_default' => false, // When the global scope is registered, show only approved records by default 'auto_scope' => true, // Global master switch for registering the approval scope 'default_status_for_unaudited' => null, // Default status for models with no approval record. Can be: null, 'pending', 'approved', 'rejected'. // Event Settings 'events_enabled' => true, // Enable event system 'events_logging' => true, // Log events 'events_webhooks_enabled' => false, // Enable webhooks 'events_webhooks_endpoints' => [], // Webhook endpoints 'events_custom_actions' => [ // Invokable class strings resolved through the container 'model_approved' => [ // Example: \App\ApprovalActions\HandleModelApproved::class ], // ... other events ], // Rejection Settings 'rejection_reasons' => [ 'inappropriate_content' => 'Inappropriate Content', 'spam' => 'Spam', 'other' => 'Other', ], ], 'models' => [ // Model specific settings (override defaults) 'App\Models\Post' => [ 'mode' => 'upsert', 'auto_pending_on_create' => true, ], ], ];
allow_custom_reasons is kept for backward compatibility in the config shape, but unknown reasons are still normalized to other.
Note: Approval statuses are managed by the LaravelApproval\Enums\ApprovalStatus enum:
ApprovalStatus::PENDINGApprovalStatus::APPROVEDApprovalStatus::REJECTED
📖 Usage Guide
Basic Operations
use LaravelApproval\Enums\ApprovalStatus; // Create a model $post = Post::create(['title' => 'New Post']); // Check current status // By default, a model with no approval record has a `null` status. $post->isPending(); // false $post->isApproved(); // false $post->isRejected(); // false $post->getApprovalStatus(); // null // You can change this behavior with `default_status_for_unaudited` config. // If config is set to 'pending': // config(['approvals.default.default_status_for_unaudited' => 'pending']); // $post->isPending(); // true, returns true // $post->getApprovalStatus(); // ApprovalStatus::PENDING, returns enum case // Set to pending $post->setPending(1); // 1 = approving user ID // Approve $post->approve(1); // Reject with predefined reason $post->reject(1, 'spam', 'Additional details'); // Reject with an unknown reason (stored as `other` and folded into the comment) $post->reject(1, 'Custom reason', 'Additional details'); // Get approval history $allApprovals = $post->approvals; // All approval records (MorphMany) $latestApproval = $post->latestApproval; // Current approval record (MorphOne) // Get who caused the approval $causer = $post->latestApproval->causer; // Returns the configured user model instance via the polymorphic causer relation
Query Scopes
// Get only approved posts $approvedPosts = Post::approved()->get(); // Get only pending posts $pendingPosts = Post::pending()->get(); // Get only rejected posts $rejectedPosts = Post::rejected()->get(); // Get with approval status (eager load latest approval) $posts = Post::withApprovalStatus()->get(); // Include unapproved posts (when global scope is active) $allPosts = Post::withUnapproved()->get(); // Check if global scope is active $posts = Post::all(); // Only approved if global scope is enabled
Modes
Insert Mode (Default)
Creates a new approval record for each status change. Ideal for history tracking and audit trails.
Configure in your config/approvals.php:
'default' => [ 'mode' => 'insert', // Default mode // ... other settings ],
Usage:
$post->setPending(1); // New record $post->approve(1); // New record $post->reject(1); // New record // Total: 3 records (full history)
Upsert Mode
Updates existing approval record. Ideal for keeping single record and current status only.
Configure in your config/approvals.php:
'default' => [ 'mode' => 'upsert', // Or set per model // ... other settings ],
Usage:
$post->setPending(1); // New record $post->approve(1); // Update existing record $post->reject(1); // Update existing record // Total: 1 record (current status only)
Note: Mode can be configured per model in the config file.
Smart Rejection Handling
The reject() method stores configured reasons directly and normalizes unknown reasons to other:
// Using predefined reason $post->reject(1, 'spam', 'Additional details'); // Result: rejection_reason = 'spam', rejection_comment = 'Additional details' // Using an unknown reason $post->reject(1, 'copyright_violation', 'Image belongs to Getty Images'); // Result: rejection_reason = 'other', rejection_comment = 'copyright_violation - Image belongs to Getty Images'
Validation Rules:
- reason field: maximum 255 characters (string field limit)
- comment field: no length limit (TEXT field)
Global Scope
auto_scope and show_only_approved_by_default do different jobs:
auto_scopeis the global master switch that registers the package scope on approvable models.show_only_approved_by_defaultcontrols whether that scope filters a given model down to approved records by default.
When both are enabled, only approved records are visible by default:
// Only approved posts (when global scope is enabled) $posts = Post::all(); // To see all posts (bypass global scope) $allPosts = Post::withUnapproved()->get(); // Check whether this model is configured to hide unapproved rows by default $showOnlyApproved = config('approvals.models.' . Post::class . '.show_only_approved_by_default', config('approvals.default.show_only_approved_by_default', false));
The scope is registered for approvable models only when approvals.default.auto_scope is enabled:
'default' => [ 'auto_scope' => true, // Register the global scope for approvable models // ... other settings ],
If auto_scope is disabled, you can still apply the scope manually when needed:
use LaravelApproval\Scopes\ApprovableScope; $approvedPosts = Post::withGlobalScope('approvable', new ApprovableScope)->get();
Auto Pending
Automatically set to pending status when model is created. Configure in your config/approvals.php:
'default' => [ 'auto_pending_on_create' => false, // Set to true to enable globally // ... other settings ], 'models' => [ 'App\Models\Post' => [ 'auto_pending_on_create' => true, // Enable for specific model // ... other settings ], ],
Usage:
$post = Post::create(['title' => 'Test']); // Will automatically be in pending status if auto_pending_on_create is enabled
Model-Specific Configuration
Override default settings for specific models in your config/approvals.php. Only specify the settings you want to override:
'models' => [ 'App\Models\Post' => [ 'mode' => 'upsert', // This model uses upsert mode 'auto_pending_on_create' => true, // Auto pending for this model 'show_only_approved_by_default' => true, // Global scope active for this model 'events_enabled' => false, // No events for this model 'rejection_reasons' => [ // Custom rejection reasons 'inappropriate_content' => 'Inappropriate Content', 'spam' => 'Spam', 'duplicate' => 'Duplicate', 'incomplete' => 'Incomplete', 'copyright_violation' => 'Copyright Violation', 'other' => 'Other', ], ], 'App\Models\Comment' => [ 'mode' => 'insert', // This model uses insert mode 'auto_pending_on_create' => false, // No auto pending for this model 'events_enabled' => true, // Events enabled for this model 'events_logging' => false, // No logging for this model 'rejection_reasons' => [ // Different rejection reasons 'spam' => 'Spam', 'harassment' => 'Harassment', 'inappropriate' => 'Inappropriate', 'offensive' => 'Offensive', 'other' => 'Other', ], ], 'App\Models\Product' => [ 'mode' => 'upsert', 'auto_pending_on_create' => true, 'events_webhooks_enabled' => true, // Webhooks enabled for this model 'events_webhooks_endpoints' => [ [ 'url' => 'https://api.example.com/webhooks/product-approval', 'headers' => ['Authorization' => 'Bearer token'], 'events' => ['model_approved', 'model_rejected'], ], ], 'rejection_reasons' => [ 'inappropriate_content' => 'Inappropriate Content', 'spam' => 'Spam', 'duplicate' => 'Duplicate', 'incomplete' => 'Incomplete', 'pricing_violation' => 'Pricing Violation', 'category_mismatch' => 'Category Mismatch', 'other' => 'Other', ], ], ],
🔔 Events
Listen to events on status changes. The package provides 6 different events with rich context:
Available Events
Before persistence:
LaravelApproval\Events\ModelApprovingLaravelApproval\Events\ModelRejecting
After persistence:
LaravelApproval\Events\ModelApprovedLaravelApproval\Events\ModelRejectedLaravelApproval\Events\ModelSettingPendingLaravelApproval\Events\ModelPending
ModelSettingPending is dispatched after the pending approval record is saved and immediately before ModelPending.
Event Usage
use Illuminate\Support\Facades\Event; use LaravelApproval\Events\ModelApproved; use LaravelApproval\Events\ModelRejected; Event::listen(ModelApproved::class, function (ModelApproved $event) { // Access event properties directly (they are public readonly) $model = $event->model; $approval = $event->approval; $causedBy = $event->causedBy; $context = $event->context; // Actions to take when approved \Log::info("Model approved: " . $model->id); }); Event::listen(ModelRejected::class, function (ModelRejected $event) { $model = $event->model; $approval = $event->approval; // Actions to take when rejected \Log::info('Model rejected', [ 'model' => get_class($model), 'caused_by' => $event->causedBy, 'reason' => $event->reason, 'comment' => $event->comment, ]); });
Event Configuration
Configure events globally or per model in your config/approvals.php:
'default' => [ // Event Settings 'events_enabled' => true, // Enable event system 'events_logging' => true, // Log events 'events_webhooks_enabled' => false, // Enable webhooks 'events_webhooks_endpoints' => [], // Webhook endpoints 'events_custom_actions' => [ // Custom event actions 'model_approved' => [ // Example: \App\ApprovalActions\HandleModelApproved::class ], 'model_rejected' => [ // Example: \App\ApprovalActions\HandleModelRejected::class ], 'model_pending' => [ // Example: \App\ApprovalActions\HandleModelPending::class ], 'model_approving' => [ // Example: \App\ApprovalActions\HandleModelApproving::class ], 'model_rejecting' => [ // Example: \App\ApprovalActions\HandleModelRejecting::class ], 'model_setting_pending' => [ // Example: \App\ApprovalActions\HandleModelSettingPending::class ], ], // ... other settings ], 'models' => [ 'App\Models\Post' => [ 'events_enabled' => false, // No events for this model // ... other settings ], 'App\Models\Comment' => [ 'events_enabled' => true, 'events_logging' => false, // No logging for this model // ... other settings ], 'App\Models\Product' => [ 'events_webhooks_enabled' => true, // Webhooks enabled 'events_webhooks_endpoints' => [ [ 'url' => 'https://api.example.com/webhooks/product-approval', 'headers' => ['Authorization' => 'Bearer token'], 'events' => ['model_approved', 'model_rejected'], ], ], 'rejection_reasons' => [ 'inappropriate_content' => 'Inappropriate Content', 'spam' => 'Spam', 'duplicate' => 'Duplicate', 'incomplete' => 'Incomplete', 'pricing_violation' => 'Pricing Violation', 'category_mismatch' => 'Category Mismatch', 'other' => 'Other', ], // ... other settings ], ],
Configurable actions can be invokable classes:
Closures are not config-cache-safe, so use class strings here instead.
<?php namespace App\ApprovalActions; use LaravelApproval\Events\ModelApproved; final class HandleModelApproved { public function __invoke(ModelApproved $event): void { // Handle the approval event here. } }
Event Properties
Event payloads are intentionally not identical across all six events:
- All events expose
model,causedBy,context, andmetadata. - Post-save events expose
approval:ModelApproved,ModelRejected,ModelSettingPending, andModelPending. - Rejection events expose
reason:ModelRejectingandModelRejected. - Comment-bearing events expose
comment: approving, approved, rejecting, rejected, setting pending, and pending.
🎭 Facade Usage
use LaravelApproval\Facades\Approval; // Assuming $post is an Approvable model instance // and $user is the user performing the action. // Approve model Approval::approve($post, $user->id); // Reject model Approval::reject($post, $user->id, 'Invalid content', 'Description'); // Set to pending status Approval::setPending($post, $user->id); // Get statistics for specific model $stats = Approval::getStatistics(\App\Models\Post::class); // [ // 'total' => 10, // 'approved' => 7, // 'pending' => 2, // 'rejected' => 1, // 'approved_percentage' => 70.0, // 'pending_percentage' => 20.0, // 'rejected_percentage' => 10.0, // ] // Get all statistics for configured models $allStats = Approval::getAllStatistics(); // [ // 'App\Models\Post' => [...], // 'App\Models\Comment' => [...], // 'App\Models\Product' => [...], // ] // Passing a model instance to getModelStatistics() uses its class. $sameStats = Approval::getModelStatistics($post);
🖥️ Artisan Commands
# Statistics for all configured models php artisan approval:status # Statistics for specific model php artisan approval:status --model="App\Models\Post"
Command Output Examples
All Models Statistics: +-------------------+-------+----------+--------+----------+-------------+ | Model | Total | Approved | Pending| Rejected | Approved % | +-------------------+-------+----------+--------+----------+-------------+ | App\Models\Post | 100 | 75 | 15 | 10 | 75.00% | | App\Models\Comment| 50 | 40 | 5 | 5 | 80.00% | +-------------------+-------+----------+--------+----------+-------------+
Single Model Statistics: +----------+-------+------------+ | Metric | Count | Percentage | +----------+-------+------------+ | Total | 100 | 100% | | Approved | 75 | 75.00% | | Pending | 15 | 15.00% | | Rejected | 10 | 10.00% | +----------+-------+------------+
## 🏗️ Architecture Overview
### Package Structure
src/
├── Core/ # Core business logic
│ ├── ApprovalManager.php # Main approval logic
│ ├── ApprovalRepository.php # Data access layer
│ ├── ApprovalValidator.php # Validation logic
│ └── ApprovalEventDispatcher.php # Event dispatching
├── Models/ # Database models
│ └── Approval.php # Approval model
├── Traits/ # Model traits
│ ├── Approvable.php # Main trait (composite)
│ ├── HasApprovals.php # Relationship management
│ ├── ApprovalScopes.php # Query scopes
│ └── ApprovalActions.php # Action methods
├── Events/ # Event classes
│ ├── ModelApproved.php # Post-approval event
│ ├── ModelRejected.php # Post-rejection event
│ └── ... # Other events
├── Listeners/ # Event listeners
│ ├── BaseApprovalListener.php # Base listener
│ ├── HandleModelApproved.php # Approval handler
│ └── ... # Other handlers
├── Services/ # Service classes
│ ├── ApprovalService.php # Main service
│ └── StatisticsService.php # Statistics service
├── Facades/ # Facade classes
│ └── Approval.php # Main facade
├── Commands/ # Artisan commands
│ └── ApprovalStatusCommand.php # Status command
├── Scopes/ # Query scopes
│ └── ApprovableScope.php # Global scope
├── Contracts/ # Interfaces
│ └── ApprovableInterface.php # Main interface
└── Exceptions/ # Custom exceptions
├── ApprovalException.php
└── UnauthorizedApprovalException.php
### Design Patterns
- **Repository Pattern**: Clean data access layer
- **Service Pattern**: Business logic encapsulation
- **Event-Driven Architecture**: Loose coupling via events
- **Trait Composition**: Modular functionality
- **Facade Pattern**: Simplified API access
- **Observer Pattern**: Event listening system
### Data Flow
1. **Model Action** → ApprovalActions trait
2. **Validation** → ApprovalValidator
3. **Business Logic** → ApprovalManager
4. **Data Persistence** → ApprovalRepository
5. **Event Dispatching** → Event system
6. **Response** → Model with updated status
## 🧪 Testing
```bash
composer test
Test Coverage
The package includes comprehensive tests with high code coverage, ensuring reliability and stability.
- Unit Tests: Individual component testing
- Integration Tests: End-to-end workflow testing
- Feature Tests: Complete feature testing
- Database Tests: Migration and factory testing
- Architecture Tests: Enforcing clean architecture rules using Pest Arch
Test Structure
tests/
├── ArchTest.php # Architecture tests
├── Commands/ # Artisan command tests
├── Core/ # Core service tests
├── Events/ # Event class tests
├── Exceptions/ # Exception tests
├── Facades/ # Facade tests
├── Integration/ # Integration tests
├── Listeners/ # Listener tests
├── Models/ # Model tests
├── Services/ # Service tests
├── Traits/ # Trait tests
├── ExampleTest.php
└── TestCase.php
📝 Changelog
Please see CHANGELOG for more information on what has changed recently.
🤝 Contributing
Please see CONTRIBUTING for details.
🔒 Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
👨💻 Credits
📄 License
This package is licensed under the MIT License. See LICENSE.md for details.
fzengin19/laravel-approval 适用场景与选型建议
fzengin19/laravel-approval 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 0 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 01 月 08 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「workflow」 「laravel」 「approval」 「onay」 「fzengin19」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 fzengin19/laravel-approval 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 fzengin19/laravel-approval 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 fzengin19/laravel-approval 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Workflow for NetCommons Plugin
Workflow logger
CakePHP plugin for approval workflow - users propose changes, admins approve or reject before publishing
Approval Workflow Engine for Filament
Operational approvals engine for Laravel applications.
Pitisha package help you add approval process in your application with multi-level approvals
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 23
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-01-08