Approval is a Laravel package that provides a simple way to approve new Model data before it is persisted.
You can install the package via composer:
composer require cjmellor/approval
You can publish and run the migrations with:
php artisan vendor:publish --tag="approval-migrations"
php artisan migrate
You can publish the config file with:
php artisan vendor:publish --tag="approval-config"
This is the contents of the published config file:
return [
'approval' => [
/**
* The approval polymorphic pivot name
*
* Default: 'approvalable'
*/
'approval_pivot' => 'approvalable',
],
];
The config allows you to change the polymorphic pivot name. It should end with able
though.
Note
The package utilises Enums, so both PHP >= 8.1 and Laravel 10 must be used.
Note This package does not approve/deny the data for you, it just stores the new/amended data into the database. It is up to you to decide how you implement a function to approve or deny the Model.
Add the MustBeApproved
trait to your Model and now the data will be stored in an approvals
table, ready for you to approve or deny.
For example, you add it to a Post
Model and each time a Post is created or updated, all the dirty data will be stored in the database as JSON for you to do something with it.
<?php
use Cjmellor\Approval\Concerns\MustBeApproved;
class Post extends Model
{
use MustBeApproved;
// ...
}
All Models using the Trait will now be stored in a new table -- approvals
. This is a polymorphic relationship.
Here is some info about the columns in the approvals
table:
approvalable_type
=> The class name of the Model that the approval is for
approvalable_id
=> The ID of the Model that the approval is for
state
=> The state of the approval. This uses an Enum class. This column is cast to an ApprovalStatus
Enum class
new_data
=> All the fields created or updated in the Model. This is a JSON column. This column is cast to the AsArrayObject
Cast
original_data
=> All the fields in the Model before they were updated. This is a JSON column. This column is cast to the AsArrayObject
Cast
rolled_back_at
=> A timestamp of when this was last rolled back to its original state
audited_at
=> The ID of the User who set the state
foreign_key
=> A foreign key to the Model that the approval is for
If you want to check if the Model data will be bypassed, use the isApprovalBypassed
method.
return $model->isApprovalBypassed();
Note
It is recommended to read the below section on how foreign keys work in this package.
Important
By default, the foreign key will always be user_id
because this is the most common foreign key used in Laravel.
If you create a new Model directly via the Model, e.g.
Post::create(['title' => 'Some Title']);
be sure to also add the foreign key to the Model, e.g.
Post::create(['title' => 'Some Title', 'user_id' => 1]);
Now when the Model is sent for approval, the foreign key will be stored in the foreign_key
column.
Your Model might not use the user_id
as the foreign key, so you can customise it by adding this method to your Model:
public function getApprovalForeignKeyName(): string
{
return 'author_id';
}
The package comes with some helper methods for the Builder, utilising a custom scope - ApprovalStateScope
By default, all queries to the approvals
table will return all the Models' no matter the state.
There are three methods to help you retrieve the state of the Approval.
<?php
use App\Models\Approval;
Approval::approved()->get();
Approval::rejected()->get();
Approval::pending()->count();
You can also set a state for an approval:
<?php
use App\Models\Approval;
Approval::where('id', 1)->approve();
Approval::where('id', 2)->reject();
Approval::where('id', 3)->postpone();
In the event you need to reset a state, you can use the withAnyState
helper.
Conditional helper methods are used, so you can set the state of an Approval when a condition is met.
$approval->approveIf(true);
$approval->rejectIf(false);
$approval->postponeIf(true);
$approval->approveUnless(false);
$approval->rejectUnless(true);
$approval->postponeUnless(false);
Once a Model's state has been changed, an event will be fired.
ModelApproved::class
ModelPostponed::class
ModelRejected::class
By default, once you approve a Model, it will be inserted into the database.
If you don't want to persist to the database on approval, set a false
flag on the approve
method.
Approval::find(1)->approve(persist: false);
If you need to roll back an approval, you can use the rollback
method.
Note
By default, a Rollback will bypass been added back to the approvals
table
Approval::first()->rollback();
This will revert the data and set the state to pending
and touch the rolled_back_at
timestamp, so you have a record of when it was rolled back.
If you want a Rollback to be re-approved, pass the bypass
parameter as false
to the rollback
method
Approval::first()->rollback(bypass: false); // default is true
A roll-back can be conditional, so you can roll back an approval if a condition is met.
Approval::first()->rollback(fn () => true);
When a Model has been rolled back, a ModelRolledBack
event will be fired with the Approval Model that was rolled back, as well as the User that rolled it back.
// ModelRolledBackEvent::class
public Model $approval,
public Authenticatable|null $user,
If you don't want Model data to be approved, you can bypass it with the withoutApproval
method.
$model->withoutApproval()->update(['title' => 'Some Title']);
By default, all attributes of the model will go through the approval process, however if you only wish certain attributes to go through this process, you can specify them using the approvalAttributes
property in your model.
<?php
use Cjmellor\Approval\Concerns\MustBeApproved;
class Post extends Model
{
use MustBeApproved;
protected array $approvalAttributes = ['name'];
// ...
}
In this example, only the name attribute of this model will go through the approval process, all mutations on other attributes will bypass the approval process.
If you omit the approvalAttributes
property from your model, all attributes will go through the approval process.
composer test
Please see CHANGELOG for more information on what has changed recently.
Please open a PR with as much detail as possible about what you're trying to achieve.
The MIT Licence (MIT). Please see Licence File for more information.