{"id":17875717,"url":"https://github.com/cjmellor/approval","last_synced_at":"2025-04-14T23:19:24.589Z","repository":{"id":37853182,"uuid":"488754444","full_name":"cjmellor/approval","owner":"cjmellor","description":"Approve new Model data before it is persisted","archived":false,"fork":false,"pushed_at":"2025-04-14T09:49:02.000Z","size":314,"stargazers_count":353,"open_issues_count":0,"forks_count":24,"subscribers_count":4,"default_branch":"2.x","last_synced_at":"2025-04-14T23:19:10.959Z","etag":null,"topics":["approval","approve","eloquent","laravel","laravel-package"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cjmellor.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-04T21:58:17.000Z","updated_at":"2025-04-14T09:49:05.000Z","dependencies_parsed_at":"2023-02-12T16:45:35.608Z","dependency_job_id":"0be67072-ea02-4039-bff4-83b01856607c","html_url":"https://github.com/cjmellor/approval","commit_stats":{"total_commits":120,"total_committers":9,"mean_commits":"13.333333333333334","dds":0.3833333333333333,"last_synced_commit":"c09d09a08acfcb965efc542cb7aee54c04a04b05"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":"spatie/package-skeleton-laravel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjmellor%2Fapproval","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjmellor%2Fapproval/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjmellor%2Fapproval/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cjmellor%2Fapproval/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cjmellor","download_url":"https://codeload.github.com/cjmellor/approval/tar.gz/refs/heads/2.x","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248975330,"owners_count":21192210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["approval","approve","eloquent","laravel","laravel-package"],"created_at":"2024-10-28T11:24:50.520Z","updated_at":"2025-04-14T23:19:24.559Z","avatar_url":"https://github.com/cjmellor.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Latest Version on Packagist](https://img.shields.io/packagist/v/cjmellor/approval?color=rgb%2856%20189%20248%29\u0026label=release\u0026style=for-the-badge)](https://packagist.org/packages/cjmellor/approval)\n[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/cjmellor/approval/run-pest.yml?branch=main\u0026label=tests\u0026style=for-the-badge\u0026color=rgb%28134%20239%20128%29)](https://github.com/cjmellor/approval/actions?query=workflow%3Arun-tests+branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/cjmellor/approval.svg?color=rgb%28249%20115%2022%29\u0026style=for-the-badge)](https://packagist.org/packages/cjmellor/approval)\n![Packagist PHP Version](https://img.shields.io/packagist/dependency-v/cjmellor/approval/php?color=rgb%28165%20180%20252%29\u0026logo=php\u0026logoColor=rgb%28165%20180%20252%29\u0026style=for-the-badge)\n![Laravel Version](\u003chttps://img.shields.io/badge/laravel-^11-rgb(235%2068%2050)?style=for-the-badge\u0026logo=laravel\u003e)\n\nApproval is a Laravel package that provides a simple way to approve new Model data before it is persisted.\n\n![](https://banners.beyondco.de/Approval.png?theme=light\u0026packageManager=composer+require\u0026packageName=cjmellor%2Fapproval\u0026pattern=brickWall\u0026style=style_2\u0026description=Approve+new+Model+data+before+it+is+persisted\u0026md=1\u0026showWatermark=0\u0026fontSize=100px\u0026images=check-circle\u0026widths=300\u0026heights=300)\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require cjmellor/approval\n```\n\nYou can publish and run the migrations with:\n\n```bash\nphp artisan vendor:publish --tag=\"approval-migrations\"\nphp artisan migrate\n```\n\n## Upgrading from v1\n\nIf you're upgrading from v1.x to v2.x, please follow the [detailed upgrade guide](UPGRADE.md) to ensure a smooth transition. Version 2 introduces database schema changes that require running specific commands in the correct order.\n\nYou can publish the config file with:\n\n```bash\nphp artisan vendor:publish --tag=\"approval-config\"\n```\n\nThis is the contents of the published config file:\n\n```php\nreturn [\n    'approval' =\u003e [\n        /**\n         * The approval polymorphic pivot name\n         *\n         * Default: 'approvalable'\n         */\n        'approval_pivot' =\u003e 'approvalable',\n    ],\n];\n```\n\nThe config allows you to change the polymorphic pivot name. It should end with `able` though.\n\n## Usage\n\n\u003e [!NOTE]\n\u003e 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.\n\nAdd the `MustBeApproved` trait to your Model and now the data will be stored in an `approvals` table, ready for you to approve or deny.\n\nFor 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.\n\n```php\n\u003c?php\n\nuse Cjmellor\\Approval\\Concerns\\MustBeApproved;\n\nclass Post extends Model\n{\n    use MustBeApproved;\n\n    // ...\n}\n```\n\nAll Models using the Trait will now be stored in a new table -- `approvals`. This is a polymorphic relationship.\n\nHere is some info about the columns in the `approvals` table:\n\n`approvalable_type` =\u003e The class name of the Model that the approval is for\n\n`approvalable_id` =\u003e The ID of the Model that the approval is for\n\n`state` =\u003e The state of the approval. This uses an Enum class. This column is cast to an `ApprovalStatus` Enum class\n\n`new_data` =\u003e All the fields created or updated in the Model. This is a JSON column. This column is cast to the `AsArrayObject` [Cast](https://laravel.com/docs/9.x/eloquent-mutators#array-object-and-collection-casting)\n\n`original_data` =\u003e All the fields in the Model before they were updated. This is a JSON column. This column is cast to the `AsArrayObject` [Cast](https://laravel.com/docs/9.x/eloquent-mutators#array-object-and-collection-casting)\n\n`rolled_back_at` =\u003e A timestamp of when this was last rolled back to its original state\n\n`audited_at` =\u003e The ID of the User who set the state\n\n`foreign_key` =\u003e A foreign key to the Model that the approval is for\n\n`creator_id` =\u003e The ID of the model who requested the approval\n\n`creator_type` =\u003e The class name of the model who requested the approval\n\n### Bypassing Approval Check\n\nIf you want to check if the Model data will be bypassed, use the `isApprovalBypassed` method.\n\n```php\nreturn $model-\u003eisApprovalBypassed();\n```\n\n### Foreign Keys for New Models\n\n\u003e [!NOTE]\n\u003e It is recommended to read the below section on how foreign keys work in this package.\n\n\u003e [!IMPORTANT]\n\u003e By default, the foreign key will always be `user_id` because this is the most common foreign key used in Laravel.\n\nIf you create a new Model directly via the Model, e.g.\n\n```php\nPost::create(['title' =\u003e 'Some Title']);\n```\n\nbe sure to also add the foreign key to the Model, e.g.\n\n```php\nPost::create(['title' =\u003e 'Some Title', 'user_id' =\u003e 1]);\n```\n\nNow when the Model is sent for approval, the foreign key will be stored in the `foreign_key` column.\n\n### Customise the Foreign Key\n\nYour Model might not use the `user_id` as the foreign key, so you can customise it by adding this method to your Model:\n\n```php\npublic function getApprovalForeignKeyName(): string\n{\n    return 'author_id';\n}\n```\n\n## Scopes\n\nThe package comes with some helper methods for the Builder, utilising a custom scope - `ApprovalStateScope`\n\nBy default, all queries to the `approvals` table will return all the Models' no matter the state.\n\nThere are three methods to help you retrieve the state of the Approval.\n\n```php\n\u003c?php\n\nuse App\\Models\\Approval;\n\nApproval::approved()-\u003eget();\nApproval::rejected()-\u003eget();\nApproval::pending()-\u003ecount();\n```\n\nYou can also set a state for an approval:\n\n```php\n\u003c?php\n\nuse App\\Models\\Approval;\n\nApproval::where('id', 1)-\u003eapprove();\nApproval::where('id', 2)-\u003ereject();\nApproval::where('id', 3)-\u003epostpone();\n```\n\nIn the event you need to reset a state, you can use the `withAnyState` helper.\n\n### Helpers\n\nConditional helper methods are used, so you can set the state of an Approval when a condition is met.\n\n```php\n$approval-\u003eapproveIf(true);\n$approval-\u003erejectIf(false);\n$approval-\u003epostponeIf(true);\n\n$approval-\u003eapproveUnless(false);\n$approval-\u003erejectUnless(true);\n$approval-\u003epostponeUnless(false);\n```\n\n### Requestor Functionality\n\nThe package includes methods to work with the creator/requestor of an approval:\n\n```php\n// Get the requestor (creator) of the approval\n$requestor = $approval-\u003erequestor;\n```\n\n```php\n// Filter approvals by requestor\n$userApprovals = Approval::requestedBy($user)-\u003eget();\n```\n\n```php\n// Check if an approval was requested by a specific user\nif ($approval-\u003ewasRequestedBy($user)) {\n    // Do something\n}\n```\n\n### Events\n\nOnce a Model's state has been changed, an event will be fired.\n\n```php\n- ModelApproved::class\n- ModelPostponed::class\n- ModelRejected::class\n- ApprovalCreated::class\n```\n\n### Configurable Approval States\n\nThe package allows you to define custom approval states beyond the default set (`Pending`, `Approved`, `Rejected`).\n\n#### Configuring Custom States\n\nDefine your custom states in the `config/approval.php` file:\n\n```php\n'states' =\u003e [\n    'pending' =\u003e [\n        'name' =\u003e 'Pending',\n        'default' =\u003e true,\n    ],\n    'approved' =\u003e [\n        'name' =\u003e 'Approved',\n    ],\n    'rejected' =\u003e [\n        'name' =\u003e 'Rejected',\n    ],\n    'in_review' =\u003e [\n        'name' =\u003e 'In Review',\n    ],\n    'needs_info' =\u003e [\n        'name' =\u003e 'Needs Clarification',\n    ],\n],\n```\n\n#### Using Custom States\n\nYou can set any configured state on an approval:\n\n```php\n// Set a custom state\n$approval-\u003esetState('in_review');\n\n// Check the current state\n$currentState = $approval-\u003egetState();\n```\n\n#### Querying by State\n\nThe package provides a flexible way to query approvals by any state:\n\n```php\n// Query approvals with a specific state\n$inReviewApprovals = Approval::whereState('in_review')-\u003eget();\n\n// The standard scopes still work for the default states\n$pendingApprovals = Approval::pending()-\u003eget();\n```\n\nStandard states (`pending`, `approved`, `rejected`) continue to work with all existing methods, ensuring backward compatibility.\n\n## Rollbacks\n\nIf you need to roll back an approval, you can use the `rollback` method.\n\n\u003e [!NOTE]\n\u003e By default, a Rollback will bypass been added back to the `approvals` table\n\n```php\nApproval::first()-\u003erollback();\n```\n\nThis 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.\n\nIf you want a Rollback to be re-approved, pass the `bypass` parameter as `false` to the `rollback` method\n\n```php\nApproval::first()-\u003erollback(bypass: false); // default is true\n```\n\n### Conditional Rollbacks\n\nA roll-back can be conditional, so you can roll back an approval if a condition is met.\n\n```php\nApproval::first()-\u003erollback(fn () =\u003e true);\n```\n\n### Events\n\nWhen 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.\n\n```php\n// ModelRolledBackEvent::class\n\npublic Model $approval,\npublic Authenticatable|null $user,\n```\n\n## Time-Based Approvals\n\nThe package supports automatic actions for approvals that aren't completed within a set time frame.\n\n### Setting Expiration Times\n\nYou can set an expiration time on any approval:\n\n```php\n// Set expiration in hours (most common)\nApproval::find(1)-\u003eexpiresIn(hours: 24);\n\n// Set expiration in minutes\nApproval::find(1)-\u003eexpiresIn(minutes: 30);\n\n// Set expiration in days\nApproval::find(1)-\u003eexpiresIn(days: 7);\n\n// Set specific expiration datetime\nApproval::find(1)-\u003eexpiresIn(datetime: now()-\u003eaddWeek());\n```\n\n### Automatic Actions\n\nYou can define what happens when an approval expires:\n\n```php\n// Automatically reject when expired\nApproval::find(1)-\u003eexpiresIn(hours: 48)-\u003ethenReject();\n\n// Automatically postpone (set to pending) when expired\nApproval::find(1)-\u003eexpiresIn(hours: 48)-\u003ethenPostpone();\n\n// Use a custom action through event listeners\nApproval::find(1)-\u003eexpiresIn(hours: 48)-\u003ethenDo(function($approval) {\n    // This callback is for documentation only\n    // Implement an event listener for ApprovalExpired event\n});\n```\n\n### Processing Expired Approvals\n\nTo process expired approvals, add this command to your scheduler:\n\n```php\n// In App\\Console\\Kernel.php\nprotected function schedule(Schedule $schedule)\n{\n    $schedule-\u003ecommand('approval:process-expired')-\u003eeveryMinute();\n}\n```\n\n### Querying Expirations\n\nYou can query approvals based on their expiration status:\n\n```php\n// Get all expired approvals\nApproval::expired()-\u003eget();\n\n// Get all non-expired approvals (including those with no expiration)\nApproval::notExpired()-\u003eget();\n\n// Get all approvals that have an expiration set\nApproval::hasExpiration()-\u003eget();\n\n// Check if a specific approval is expired\n$approval-\u003eisExpired();\n```\n\n### Events\n\nWhen an approval expires and is processed, these events are fired:\n\n- `ApprovalExpired`: Fired for all expired approvals\n- Followed by the specific action event (`ModelRejected`, `ModelSetPending`, etc.)\n\n## Disable Approvals\n\nIf you don't want Model data to be approved, you can bypass it with the `withoutApproval` method.\n\n```php\n$model-\u003ewithoutApproval()-\u003eupdate(['title' =\u003e 'Some Title']);\n```\n\n## Specify Approvable Attributes\n\nBy 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.\n\n```php\n\u003c?php\n\nuse Cjmellor\\Approval\\Concerns\\MustBeApproved;\n\nclass Post extends Model\n{\n    use MustBeApproved;\n\n    protected array $approvalAttributes = ['name'];\n\n    // ...\n}\n```\n\nIn 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.\n\nIf you omit the `approvalAttributes` property from your model, all attributes will go through the approval process.\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease open a PR with as much detail as possible about what you're trying to achieve.\n\n## Credits\n\n-   [Chris Mellor](https://github.com/cjmellor)\n\n## License\n\nThe MIT Licence (MIT). Please see [Licence File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcjmellor%2Fapproval","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcjmellor%2Fapproval","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcjmellor%2Fapproval/lists"}