fzengin19 / laravel-approval
Laravel iΓ§in onay sistemi paketi
Fund package maintenance!
:vendor_name
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
Language:HTML
pkg:composer/fzengin19/laravel-approval
Requires
- php: ^8.3
- illuminate/contracts: ^10.0||^11.0||^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^2.9||^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0.0||^9.0.0||^8.22.0
- pestphp/pest: ^2.0||^3.0
- pestphp/pest-plugin-arch: ^2.5||^3.0
- pestphp/pest-plugin-laravel: ^2.0||^3.0
- phpstan/extension-installer: ^1.3||^2.0
- phpstan/phpstan-deprecation-rules: ^1.1||^2.0
- phpstan/phpstan-phpunit: ^1.3||^2.0
- spatie/laravel-ray: ^1.35
This package is auto-updated.
Last update: 2026-01-08 11:12:54 UTC
README
A comprehensive approval system package for Laravel. Provides a powerful and flexible solution for managing approval statuses of your models. Developed with TDD approach, reliable code with 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: Intelligent categorization of rejection reasons with predefined and custom options.
- π Global Scope: Automatically show only approved records with configurable behavior.
- π Comprehensive Event System: 6 different events (pre/post status changes) using modern PHP 8.1+ features.
- π 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 Causer: Approval actions can be caused by any model, not just Users.
- β¨ Modern PHP: Utilizes modern PHP features like 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; // 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.1 or higher
- Laravel 9 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, // Auto pending when model is created 'show_only_approved_by_default' => false, // Is global scope active? 'auto_scope' => true, // Automatically add global 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' => [ // Custom event actions 'model_approved' => [ // Example: function($event) { /* custom logic */ } ], // ... other events ], // Rejection Settings 'allow_custom_reasons' => false, // Allow custom rejection reasons '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, ], ], ];
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 custom reason (automatically categorized as 'other' if not in predefined list) $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 User model instance (or other causer model)
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 intelligently handles rejection reasons with automatic categorization:
// Using predefined reason $post->reject(1, 'spam', 'Additional details'); // Result: rejection_reason = 'spam', rejection_comment = 'Additional details' // Using custom reason (when allowed) $post->reject(1, 'copyright_violation', 'Image belongs to Getty Images'); // Result: rejection_reason = 'copyright_violation', rejection_comment = 'Image belongs to Getty Images' // Using custom reason (when not allowed) $post->reject(1, 'custom_reason', 'Custom rejection reason'); // Result: rejection_reason = 'other', rejection_comment = 'custom_reason - Custom rejection reason'
Validation Rules:
- reason field: maximum 255 characters (string field limit)
- comment field: no length limit (TEXT field)
- Custom reasons can be enabled/disabled per model
Custom Rejection Reasons
Control whether custom rejection reasons are allowed in your config/approvals.php:
'default' => [ 'allow_custom_reasons' => false, // Set to true to allow globally // ... other settings ], 'models' => [ 'App\Models\Post' => [ 'allow_custom_reasons' => true, // Allow custom reasons for Post model // ... other settings ], 'App\Models\Comment' => [ 'allow_custom_reasons' => false, // Only predefined reasons for Comment model // ... other settings ], ],
Global Scope
When global scope is active, 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 if global scope is active for this model $showOnlyApproved = config('approvals.models.' . Post::class . '.show_only_approved_by_default', config('approvals.default.show_only_approved_by_default', false));
Auto Scope Configuration
Control whether the global scope is automatically applied in your config/approvals.php:
'default' => [ 'auto_scope' => true, // Set to false to disable globally // ... other settings ], 'models' => [ 'App\Models\Post' => [ 'auto_scope' => false, // Disable for specific model // ... other settings ], ],
Or manually apply the scope 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 'allow_custom_reasons' => true, // Allow custom rejection reasons '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 'allow_custom_reasons' => false, // Only predefined reasons '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
Pre-events (Triggered before status change):
LaravelApproval\Events\ModelApprovingLaravelApproval\Events\ModelRejectingLaravelApproval\Events\ModelSettingPending
Post-events (Triggered after status change):
LaravelApproval\Events\ModelApprovedLaravelApproval\Events\ModelRejectedLaravelApproval\Events\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; $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), '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: function(ModelApproved $event) { /* custom logic */ } ], 'model_rejected' => [ // Example: function(ModelRejected $event) { /* custom logic */ } ], 'model_pending' => [ // Example: function(ModelPending $event) { /* custom logic */ } ], 'model_approving' => [ // Example: function(ModelApproving $event) { /* custom logic */ } ], 'model_rejecting' => [ // Example: function(ModelRejecting $event) { /* custom logic */ } ], 'model_setting_pending' => [ // Example: function(ModelSettingPending $event) { /* custom logic */ } ], ], // ... 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 ], ],
Event Properties
Each event object contains public readonly properties for easy access:
model: The model that triggered the event (ApprovableInterface&Model)approval: The approval record (Approval)reason: Rejection reason (?string, if applicable)comment: Rejection comment (?string, if applicable)userId: User ID who performed the action (?int)context: Additional context data (array)
π 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' => [...], // ]
π₯οΈ 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
The MIT License (MIT). Please see License File for more information. bunu incele ve tΓΌrkΓ§e olarak bana anlat deΔerlendir