{"id":21437872,"url":"https://github.com/devsrv/laravel-scheduled-model-action","last_synced_at":"2025-06-19T16:42:11.168Z","repository":{"id":43681530,"uuid":"381362576","full_name":"devsrv/laravel-scheduled-model-action","owner":"devsrv","description":"Handle scheduled tasks associated with Eloquent models.","archived":false,"fork":false,"pushed_at":"2023-03-27T12:07:15.000Z","size":70,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-17T15:53:33.713Z","etag":null,"topics":["eloquent","laravel","scheduler","scheduling"],"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/devsrv.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-29T12:48:18.000Z","updated_at":"2022-05-28T14:26:25.000Z","dependencies_parsed_at":"2024-11-17T15:38:07.206Z","dependency_job_id":"55353d5a-cba2-4dff-935e-8fd445e02f61","html_url":"https://github.com/devsrv/laravel-scheduled-model-action","commit_stats":{"total_commits":109,"total_committers":1,"mean_commits":109.0,"dds":0.0,"last_synced_commit":"a73c7dd5e2ab7ee65c301f02b10805db2b1edb5e"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsrv%2Flaravel-scheduled-model-action","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsrv%2Flaravel-scheduled-model-action/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsrv%2Flaravel-scheduled-model-action/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsrv%2Flaravel-scheduled-model-action/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devsrv","download_url":"https://codeload.github.com/devsrv/laravel-scheduled-model-action/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225986082,"owners_count":17555557,"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":["eloquent","laravel","scheduler","scheduling"],"created_at":"2024-11-23T00:30:00.688Z","updated_at":"2024-11-23T00:30:01.208Z","avatar_url":"https://github.com/devsrv.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel Scheduled Action\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/devsrv/laravel-scheduled-action.svg?style=flat-square)](https://packagist.org/packages/devsrv/laravel-scheduled-action)\n[![Total Downloads](https://img.shields.io/packagist/dt/devsrv/laravel-scheduled-action.svg?style=flat-square)](https://packagist.org/packages/devsrv/laravel-scheduled-action)\n[![GitHub Tests Action Status](https://github.com/devsrv/laravel-scheduled-model-action/actions/workflows/test.yaml/badge.svg)](https://github.com/devsrv/laravel-scheduled-model-action/actions/workflows/test.yaml)\n\nHandle scheduled tasks associated with Eloquent models.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://binarymesh.dev/assets/package/actions-create-task11.svg\" width=\"350\" alt=\"create schedule\" /\u003e\n  \u003cimg src=\"https://binarymesh.dev/assets/package/actions-poll-011.svg\" width=\"380\" alt=\"poll schedules\" /\u003e\n\u003c/p\u003e\n\u003cbr/\u003e\n\nFor any scheduled task we can directly use Laravel's [queue](https://laravel.com/docs/8.x/queues) but what if that task needs to be modified in some way before it gets executed?\n\nThis package stores all the tasks that needs to run on a future date \u0026 time and executes each only on the day when it is scheduled to run so that we get the chance to modify the task before it gets executed.\n\nIt uses Laravel's task [scheduling](https://laravel.com/docs/8.x/scheduling) to figure out \u0026 handle the tasks that needs to be run for the current day at the specified time for that task, and sends the task payload to a [receiver class](https://github.com/devsrv/laravel-scheduled-model-action#step---3-) of your app ([configurable](#%EF%B8%8F-publish-config)). So how to perform the task is totally up to you.\n\n## 💡 How it works:\n- This package creates one table `model_actions`\n- Every task has 4 satatus `PENDING` `FINISHED` `CANCELLED` `DISPATCHED`\n- The `scheduledaction:poll` artisan command polls `PENDING` tasks for the present day and passes the tasks payload to your receiver class.\n- Set how often you want the poll to happen and how many tasks needs to be passed to your receiver (the above [example](#%EF%B8%8F-add-scheduled-task-to-appconsolekernelphp) shows 10 per hour)\n- `PENDING` tasks gets run at specified date \u0026 time, remember to mark the task as `FINISHED` or `CANCELLED` based on how it was handled [check example](#step---4-).\n- Most likely you'll use queue to run a task at a specified time so after dispatching to a queued job you might want to set the status as `DISPATCHED`\n\n## Installation\n\n```shell\ncomposer require devsrv/laravel-scheduled-action\n```\n\n## Setup\n\n#### ✔️ publish migrations\n```shell\nphp artisan vendor:publish --provider=\"Devsrv\\ScheduledAction\\ScheduledActionServiceProvider\" --tag=\"migrations\"\n```\n\n#### ✔️ run migrations\n```shell\nphp artisan migrate\n```\n\n#### ✔️ publish config\n```shell\nphp artisan vendor:publish --provider=\"Devsrv\\ScheduledAction\\ScheduledActionServiceProvider\" --tag=\"config\"\n```\n\n```php\n// the class that takes care of how to perform the task, the payload will be passed to this invokable class\nreturn [\n    'receiver' =\u003e \\App\\Http\\AutoAction\\ScheduledActionReceiver::class, 👈 needs to be invokable\n];\n```\n\n#### ✔️ Add Scheduled Task to `app/Console/Kernel.php`\n```php\n$schedule-\u003ecommand('scheduledaction:poll --tasks=10')-\u003ehourly();  // poll pending tasks (10 tasks every hour \u0026 sends payload to your receiver, customize as per your app)\n```\n\n#### ✔️ Use the `HasScheduledAction` trait in your models\n```php\nuse Devsrv\\ScheduledAction\\Traits\\HasScheduledAction;\n\nclass Candidate extends Model\n{\n    use HasFactory, HasScheduledAction;\n    \n    ...\n}\n```\n\n## There are many fluent methods to interact with the scheduled action\n\n\n### Get\n\n```php\nuse Devsrv\\ScheduledAction\\Models\\ModelAction;\nuse Devsrv\\ScheduledAction\\Facades\\Action;\n\n$model = \\App\\Models\\Candidate::find(10);\n\n$model-\u003escheduledActions()-\u003eget();\n$model-\u003escheduledActions()-\u003efinished()-\u003eget();\n$model-\u003escheduledActions()-\u003ecancelled()-\u003efirst();\n$model-\u003escheduledActions()-\u003epending()-\u003eget();\n$model-\u003escheduledActions()-\u003edispatched()-\u003epaginate();\n$model-\u003escheduledActions()-\u003epending()-\u003etoActBetweenTime(Carbon::createFromTimeString('14:00:00'), Carbon::createFromTimeString('16:30:00'))-\u003eget();\n$model-\u003escheduledActions()-\u003epending()-\u003ewhereExtraProperty('prefers', 'mail')-\u003eget();\n$model-\u003escheduledActions()-\u003ewhereExtraProperty('channel', 'slack')-\u003eget();\n$model-\u003escheduledActions()-\u003ewhereExtraProperties(['prefers' =\u003e 'mail', 'applicant' =\u003e 27])-\u003eget();\n$model-\u003escheduledActions()-\u003ewherePropertyContains('languages', ['en', 'de'])-\u003eget();\n$model-\u003escheduledActions()-\u003efirst()-\u003eisPending();\n$model-\u003escheduledActions()-\u003efirst()-\u003egetExtraProperty('customProperty');\n\n$task = ModelAction::find(1);\n\n$task-\u003egetExtraProperty('customProperty');\n$task-\u003eact_time;\n$task-\u003eaction;\n$task-\u003eactionable; \t// associated model\n\n$task-\u003eisPending;\t    // bool\n$task-\u003eisFinished; \t    // bool\n$task-\u003eisDispatched; \t    // bool\n$task-\u003eisCancelled; \t    // bool\n$task-\u003eisRecurring; \t    // bool\n\nModelAction::finished()-\u003eget();\nModelAction::forModel($modlel)-\u003epending()-\u003eget();\nModelAction::forClass(Candidate::class)-\u003eget();\nModelAction::whereAction('EMAIL')-\u003eget();\nModelAction::forClass(Candidate::class)-\u003emodelId(10)-\u003eget();\nModelAction::modelIdIn([10, 11, 12])-\u003eget();\nModelAction::forModel($modlel)-\u003ewhereProperty('mailable', \\App\\Mail\\RejectMail::class)-\u003eget();\nModelAction::forModel($modlel)-\u003ewhereProperties(['type' =\u003e 'info', 'applicant' =\u003e 27])-\u003eget();\nModelAction::wherePropertyContains('languages', ['en', 'de'])-\u003eget();\nModelAction::where('properties-\u003etype', 'success')-\u003eget();\n\n\\Devsrv\\ScheduledAction\\Facades\\Action::needsToRunOn(now()-\u003etomorrow(), 10, 'asc');\n\\Devsrv\\ScheduledAction\\Facades\\Action::needsToRunToday(3);\n```\n\n### Create\n\n```php\n$action = $model-\u003escheduledActions()-\u003ecreate([\n    'action' =\u003e 'EMAIL',\n    'properties' =\u003e ['color' =\u003e 'silver'],\n    'status' =\u003e \\Devsrv\\ScheduledAction\\Enums\\Status::PENDING,\n    'act_date' =\u003e now(),\n    'act_time' =\u003e now()-\u003esetHour(11),\n]);\n\n$model-\u003escheduledActions()-\u003ecreateMany([]);\n\nModelAction::for(Candidate::find(10))\n    -\u003eactWith('EMAIL')\n    -\u003eactAt(Carbon::tomorrow()-\u003esetHour(11))    // date + time will be set together\n    -\u003esetExtraProperties(['foo' =\u003e 'bar'])\n    -\u003ecreateSchedule();\n\nModelAction::for($model)\n    -\u003eactWith('EMAIL')\n    -\u003eactDate($actDate)\n    -\u003eactTime(Carbon::createFromTimeString('20:00:00'))\n    -\u003esetExtraProperties(['foo' =\u003e 'bar'])\n    -\u003ecreateSchedule();\n\nModelAction::for($model)-\u003eactWith('EMAIL')-\u003eactTime($carbon)-\u003easDispatched()-\u003ecreateSchedule();\n```\n\n### Update\n```php\n\n$action-\u003esetPending()-\u003esave();\n$action-\u003esetCancelled()-\u003esave();\n$action-\u003esetDispatched()-\u003esave();\n$action-\u003esetFinished()-\u003esave();\t\t\t\t\t\t\t// default sets finished_at as now()\n$action-\u003esetFinished($carbon)-\u003esave();\t\t\t\t\t\t// set a finished_at time\n$action-\u003emergeExtraProperties(['key' =\u003e 'val'])-\u003esave();  \t\t\t// merges extra properties\n$action-\u003ewithExtraProperties([])-\u003esave();                   \t\t\t// overwrites existing\n$action-\u003esetPending()-\u003esetActAt($actAt)-\u003esave();\n$action-\u003esetActAt($carbon)-\u003esave();\t\t\t\t\t\t// date and time will be extracted\n$action-\u003esetActDate($carbon)-\u003esave();\t\t\t\t\t\t// only date will be used\n$action-\u003esetActTime($carbon)-\u003esave();\t\t\t\t\t\t// only time will be used\n```\n\n## Example\n\n\u003e imagine a job portal where an application is auto approved after 3 days of apply unless it was auto rejected by some qualifier checks by bot, during that period the admin may override the application status or prevent the auto mail sending or modify the mail content\n\n#### Step - 1 :\n\u003cdetails\u003e\n\u003csummary\u003eSome event happend and a task is created to execute on future day \u0026 time\u003c/summary\u003e\n\n```php\nModelAction::for($application)\n-\u003eactWith('MAIL')\n-\u003eactAt($inThreeDays)\n-\u003esetExtraProperties([\n  'mailable' =\u003e ApproveApplication::class,\n  'template' =\u003e $template\n])\n-\u003ecreateSchedule();\n```\n\u003c/details\u003e\n\n#### Step - 2 :\n\u003cdetails\u003e\n\u003csummary\u003eadmin decides to alter the task\u003c/summary\u003e\n\n```php\npublic function modifyScheduledTask() {\n    $this-\u003evalidate();\n\n    $this-\u003etask\n        -\u003esetActDate(Carbon::createFromFormat('m/d/Y', $this-\u003eact_date))\n        -\u003esetActTime(Carbon::createFromTimeString($this-\u003eact_time))\n        -\u003emergeExtraProperties([\n            'template' =\u003e $this-\u003etemplateid,\n            'extra_data' =\u003e $this-\u003erole\n        ])\n        -\u003esave();\n\n    $this-\u003einfo('schedule updated');\n}\n    \npublic function cancelSchedule() {\n    $this-\u003etask-\u003esetCancelled()-\u003esave();\n    $this-\u003einfo('schedule cancelled');\n}\n```\n\u003c/details\u003e\n\n#### Step - 3 :\n\u003cdetails\u003e\n\u003csummary\u003ereceiver class gets task payload \u0026 passes the task to classes based on task action (for this example sending email)\u003c/summary\u003e\n\n```php\n\u003c?php\n\nnamespace App\\Http\\AutoAction;\n\nuse Facades\\App\\Http\\Services\\AutoAction\\Mailer;\n\nclass ScheduledActionReceiver\n{\n    public function __invoke($tasks)\n    {\n        foreach ($tasks as $task) {\n            match($task-\u003eaction) {\n                'MAIL'    =\u003e MailTaskHandler::handle($task),\n                ...\n\n                default   =\u003e activity()-\u003elog('auto action task unhandled')\n            };\n        }\n    }\n}\n```\n\u003c/details\u003e\n\n#### Step - 4 :\n\u003cdetails\u003e\n\u003csummary\u003eemail sending task payload gets received via previous receiver class and mail is sent\u003c/summary\u003e\n\n```php\nclass MailTaskHandler\n{\n\tpublic function handle($task) {\n        [$user, $mailable, $extras, $time] = $this-\u003eextractData($task);\n\n        Mail::to($user)\n        -\u003elater(\n            Carbon::now()-\u003esetTimeFromTimeString($time),\n            new $mailable($user, $extras)\n        );\n\n        $task-\u003esetFinished()-\u003esave();\t// 👈 marking the task finished here though it is actually not, because for mail there is no easy way to execute this code after that mail is actually sent\n    }\n\n    private function extractData($task) {\n        $model = $task-\u003eactionable;\n        $user = null;\n\n        $mailable = $task-\u003egetExtraProperty('mailable');\n        $template = $task-\u003egetExtraProperty('template');\n\n        $extras = [];\n\n        if($mailable === ApproveApplication::class) {\n            $extras['role'] = $task-\u003egetExtraProperty('role');\n\n            $user = $model-\u003eapplicant;\n        }\n\n        $time = $task-\u003eact_time;\n\n        return [$user, $mailable, $template, $extras, $time];\n    }\n}\n```\n\u003c/details\u003e\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n\n## 👋🏼 Say Hi! \nLeave a ⭐ if you find this package useful 👍🏼,\ndon't forget to let me know in [Twitter](https://twitter.com/srvrksh)  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevsrv%2Flaravel-scheduled-model-action","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevsrv%2Flaravel-scheduled-model-action","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevsrv%2Flaravel-scheduled-model-action/lists"}