{"id":16954733,"url":"https://github.com/vtsykun/cron-bundle","last_synced_at":"2025-03-17T08:37:26.314Z","repository":{"id":57030745,"uuid":"233403817","full_name":"vtsykun/cron-bundle","owner":"vtsykun","description":":clock3: Docker friendly Symfony Cron Bundle for handling scheduled tasks consistently, parallel or via message queue","archived":false,"fork":false,"pushed_at":"2024-03-29T23:21:19.000Z","size":232,"stargazers_count":18,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-27T21:37:44.028Z","etag":null,"topics":["cron-bundle","cron-jobs","hacktoberfest","lock","message-queue","scheduled-tasks","symfony-messenger"],"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/vtsykun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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":"2020-01-12T14:18:36.000Z","updated_at":"2024-06-19T13:58:50.000Z","dependencies_parsed_at":"2024-03-24T00:27:01.793Z","dependency_job_id":"79fafab6-63e6-463a-a289-6b60ce7b8a0e","html_url":"https://github.com/vtsykun/cron-bundle","commit_stats":{"total_commits":32,"total_committers":3,"mean_commits":"10.666666666666666","dds":0.4375,"last_synced_commit":"8be098eefabee8e8f4c222feb0eb144a033f1677"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vtsykun%2Fcron-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vtsykun%2Fcron-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vtsykun%2Fcron-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vtsykun%2Fcron-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vtsykun","download_url":"https://codeload.github.com/vtsykun/cron-bundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243852499,"owners_count":20358271,"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":["cron-bundle","cron-jobs","hacktoberfest","lock","message-queue","scheduled-tasks","symfony-messenger"],"created_at":"2024-10-13T22:10:25.510Z","updated_at":"2025-03-17T08:37:26.018Z","avatar_url":"https://github.com/vtsykun.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Okvpn - Cron Bundle\n\nThis bundle provides interfaces for registering and handle scheduled tasks within your Symfony application.\n\n[![Latest Stable Version](https://poser.okvpn.org/okvpn/cron-bundle/v/stable)](https://packagist.org/packages/okvpn/cron-bundle)\n[![Total Downloads](https://poser.okvpn.org/okvpn/cron-bundle/downloads)](https://packagist.org/packages/okvpn/cron-bundle) \n[![Latest Unstable Version](https://poser.okvpn.org/okvpn/cron-bundle/v/unstable)](https://packagist.org/packages/okvpn/cron-bundle) \n[![License](https://poser.okvpn.org/okvpn/cron-bundle/license)](https://packagist.org/packages/okvpn/cron-bundle)\n\n## Purpose\nThis is a simpler alternative of existing cron bundle without doctrine deps.\nHere also added support middleware for customization handling cron jobs across a cluster install: \n(Send jobs to message queue, like Symfony Messenger; locking; etc.).\nThis allows to limit the number of parallel running processes and prioritized it.\n\nFeatures\n--------\n\n- Not need doctrine/database.\n- Docker friendly, runs as background command without `crond`.\n- Schedule tasks with one-millisecond precision.\n- More ways to randomize crons with `@random 3600` and `jitter`.\n- Integration with Symfony Messenger.\n- Load a cron job from a different storage (config.yml, tagged services, commands).\n- Support many engines to run cron (in parallel process, message queue, consistently).\n- Support many types of cron handlers/command: (services, symfony commands, UNIX shell commands).\n- Can be used along with timers, subscriber and async I/O with React EventLoop, like Redis subscriber [clue/redis-react](https://github.com/clue/reactphp-redis).\n- Middleware and customization.\n\n## Table of Contents\n\n - [Install](#install)\n - [Commands](#commands)\n - [Registration a new scheduled task](#registration-a-new-scheduled-task)\n - [Configuration](#full-configuration-reference)\n - [Symfony Messenger Integration](#handle-cron-jobs-via-symfony-messenger)\n - [Your own Scheduled Tasks Loader](#your-own-scheduled-tasks-loaders)\n - [Handling cron jobs across a cluster](#handling-cron-jobs-across-a-cluster-or-custom-message-queue)\n - [Use ReactPHP EventLoop](#use-reactphp-eventloop)\n\nInstall\n------\n\nInstall using composer:\n\n```\ncomposer require okvpn/cron-bundle\n```\n\nFor Symfony 4+ add bundle to `config/bundles.php`\n\n```php\n\u003c?php\nreturn [\n    ... //  bundles\n    Okvpn\\Bundle\\CronBundle\\OkvpnCronBundle::class =\u003e ['all' =\u003e true],\n]\n```\n\n##  Quick Usage \n\nYou can use `AsCron` or `AsPeriodicTask` attribute for autoconfigure.\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace App\\Service;\n\nuse Okvpn\\Bundle\\CronBundle\\Attribute\\AsCron;\nuse Okvpn\\Bundle\\CronBundle\\Attribute\\AsPeriodicTask;\n\n#[AsCron('*/5 * * * *', messenger: true)]\nclass SyncAppWorker\n{\n    public function __invoke(array $arguments = []): void\n    {\n        // code\n    }\n}\n\n#[AsCron('*/10 * * * *', jitter: 60)]\nclass Sync2AppWorker { /* ... */ } // Run each 10 minutes with 60 sec random delay \n\n#[AsCron('@random 3600')]\nclass Sync3AppWorker { /* ... */ } // Run with random 0-3600 sec \n\n#[AsPeriodicTask('30 seconds', jitter: 5)]\nclass Sync4AppWorker { /* ... */ } // Run each 30 sec with 5 sec random delay.\n```\n\n## Commands\n\nRuns the current cron schedule\n\n```\n# Add this line to system crontab and execute each minute.\nphp bin/console okvpn:cron \n\n# Run cron scheduler every minute without exiting.\nphp bin/console okvpn:cron --demand \n\n# Run cron scheduler for specific groups.\nphp bin/console okvpn:cron --group  \n```\n\nDebug and execute cron jobs manually and show list\n\n```\nphp bin/console okvpn:debug:cron \n\nphp bin/console okvpn:debug:cron --execute-one=7\n```\n\n![debug](docs/image1.png)\n\n#### Dry run cron tasks.\n\n```\nphp bin/console okvpn:cron --dry-run --demand -vvv\n```\n\n![debug](docs/img2.png)\n\n### Cron Expression\n\nA CRON expression syntax was take from lib [dragonmantank/cron-expressions](https://github.com/dragonmantank/cron-expression#cron-expressions)\n\nAlso, it was extent with `@random` to avoid running things at midnight or once an hour at XX:00.\nMost people do so and same services have traffic peaks every hour.\n\nExamples:\n```\n*/5 * * * * - every 5 min \n0 1 * * 0 - at 1 am every Sunday\n@random 3600 # where 3600 - parameter lambda in the Poisson distribution, if it will run each seconds. Here, the avg probability period is 1 hour.\n```\n\n#### First way. Install system crontab\n\nTo regularly run a set of commands from your application, configure your system to run the \noro:cron command every minute. On UNIX-based systems, you can simply set up a crontab entry for this:\n\n```\n*/1 * * * * /path/to/php /path/to/bin/console okvpn:cron --env=prod \u003e /dev/null\n```\n\n#### Second way. Using supervisor\n\nSetup Supervisor to run cron on demand.\n\n```\nsudo apt -y --no-install-recommends install supervisor\n```\n\nCreate a new supervisor configuration.\n\n```\nsudo vim /etc/supervisor/conf.d/app_cron.conf\n```\nAdd the following lines to the file.\n\n```\n[program:app-cron]\ncommand=/path/to/bin/console okvpn:cron --env=prod --demand\nprocess_name=%(program_name)s_%(process_num)02d\nnumprocs=1\nautostart=true\nautorestart=true\nstartsecs=0\nredirect_stderr=true\npriority=1\nuser=www-data\n```\n\n## Registration a new scheduled task\n\nTo add a new scheduled task you can use tag `okvpn.cron` or using `autoconfigure`\nwith interface `Okvpn\\Bundle\\CronBundle\\CronSubscriberInterface`.\n\n#### Services.\n\n```php\n\u003c?php\n\nnamespace App\\Cron;\n\nuse Okvpn\\Bundle\\CronBundle\\CronSubscriberInterface;\n\nclass MyCron implements CronSubscriberInterface // implements is not required, but helpful if yor are use autoconfigure\n{\n    public function __invoke(array $arguments = [])\n    {\n        // processing...\n    }\n\n    public static function getCronExpression(): string\n    {\n        return '*/10 * * * *';\n    }\n}\n```\n\nIf you use the default configuration, the corresponding service will be automatically registered thanks to `autoconfigure`. \nTo declare the service explicitly you can use the following snippet:\n\n```yaml\nservices:\n    App\\Cron\\MyCron:\n        tags:\n            - { name: okvpn.cron, cron: '*/5 * * * *' }\n    \n\n    App\\Cron\\SmsNotificationHandler:\n        tags:\n            - { name: okvpn.cron, cron: '*/5 * * * *', lock: true, async: true }\n\n```\n\nPossible options to configure with tags are:\n\n- `cron` -  A cron expression, if empty, the command will run always.\n- `lock` -  Prevent to run the command again, if prev. command is not finished yet. Possible value: `true`, `{name: lock1, ttl: 300}`.\nTo use this option need to install symfony [lock component](https://symfony.com/doc/4.4/components/lock.html) \n- `async` - Run command in the new process without blocking main thread.\n- `arguments` - Array of arguments, used to run symfony console commands or pass arguments to handler. \n- `priority` - Sorting priority.\n- `group` - Group name, see Cron Grouping section.\n- `jitter` - Random delay 0-60 sec\n- `interval` - Run periodic tasks by interval. Examples `10`, `10 seconds`, `1 day`.\n- `messenger` - Send jobs into Messenger Bus. Default `false`. You also can specify transport here `{routing: async}`,\nsee [Symfony Routing Messages to a Transport](https://symfony.com/doc/current/messenger.html#routing-messages-to-a-transport) \n\n#### Symfony console command\n\n```yaml\nservices:\n    App\\Command\\DowloadOrdersCommand:\n        tags:\n            - { name: console.command }\n            - { name: okvpn.cron, cron: '*/5 * * * *' }\n```\n\n#### Via configuration / shell commands\n\n```yaml\nokvpn_cron:\n  tasks:\n    -\n      command: \"php %kernel_project.dir%/bin/console cache:clear --env=prod\" # Shell command \n      cron: \"0 0 * * *\"\n    -\n      command: \"bash /root/renew.sh \u003e /root/renew.txt\" # Shell command\n      group: root # Filtering by group. You can run `bin/console okvpn:cron --group=root` under the root user \n      cron: \"0 0 * * *\"\n    -\n      command: 'App\\Cron\\YouServiceName' # Your service name\n      cron: \"0 0 * * *\"\n    -\n      command: 'app:cron:sync-amazon-orders' # Your symfony console command name\n      cron: \"*/30 * * * *\"\n      async: true\n      arguments: { '--transport': 15 } # command arguments or options\n      jitter: 60 # 60 sec random delay \n\n    -\n      command: 'app:cron:wrfda-grib2' # run the command with 20 sec interval and 10 sec random delay \n      interval: \"20 seconds\"\n      jitter: 10\n```\n\n## Full Configuration Reference\n\n```yaml\n# Your config file\nokvpn_cron:\n    lock_factory: ~ # The Service to create lock. Default lock.factory, see Symfony Lock component.\n    timezone: ~ # default timezone, like Europe/Minsk. if null will use php.ini default\n    messenger:\n        enable: false # Enable symfony messenger\n        \n    # Default options allow to add define default policy for all tasks, \n    # For example to always run commands with locking and asynchronously\n    default_policy:\n        async: true # Default false\n        lock: true # Default false\n        messenger: true # Handle all jobs with symfony messenger bus.\n    \n    # Stamps it's markers that will add to each tasks.\n    with_stamps:\n        - 'Packagist\\WebBundle\\Cron\\WorkerStamp'\n    \n    # service name for run cron in demand (Okvpn\\Bundle\\CronBundle\\Runner\\ScheduleLoopInterface)\n    loop_engine: ~\n\n    tasks: # Defined tasks via configuration\n      - \n        command: 'app:noaa:gfs-grib-download'\n        cron: '34,45 */6 * * *'\n        messenger: { routing: lowpriority } # See Messenger configuration\n        lock: true\n        arguments: { '--transport': '0p25' }\n        # Here you can also add other custom options and create your own middleware.\n      -\n        command: \"bash /root/renew.sh \u003e /root/renew.txt\" # Shell command\n        group: root # Group filter. You can run `bin/console okvpn:cron --group=root` under the root user \n        cron: \"0 0 * * *\"\n```\n\n## Handle Cron Jobs via Symfony Messenger \n\nTo limit the number of parallel running processes you can handle the cron jobs in the queue using Symfony Messenger.\n\n1. Install Symfony Messenger\n2. Enable default route for cron job\n\n```yaml\n# config/packages/messenger.yaml\nframework:\n    messenger:\n        transports:\n            async: \"%env(MESSENGER_TRANSPORT_DSN)%\"\n            lowpriority: \"%env(MESSENGER_TRANSPORT_LOW_DSN)%\"\n\n        routing:\n            # async is whatever name you gave your transport above\n            'Okvpn\\Bundle\\CronBundle\\Messenger\\CronMessage':  async\n```\n\n3. Enable Messenger for cron.\n\n```yaml\n# config/packages/cron.yaml\nokvpn_cron:\n    # Required. messenger middleware is disable\n    messenger:\n        enable: true\n\n    # Optional\n    default_options:\n        messenger: true # For handle all cron jobs with messenger\n    \n    # Optional \n    tasks:\n        - \n            command: 'app:noaa:gfs-grib-download'\n            cron: '34,45 */6 * * *'\n#           messenger: true # OR\n            messenger: { routing: lowpriority } # Send to lowpriority transport\n```\n\nMore information how to use [messenger here](https://symfony.com/doc/current/messenger.html)\n\n## Your own Scheduled Tasks Loaders\n\nYou can create custom tasks loaders, see example\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace Packagist\\WebBundle;\n\nuse Okvpn\\Bundle\\CronBundle\\Loader\\ScheduleLoaderInterface;\nuse Okvpn\\Bundle\\CronBundle\\Model\\ScheduleEnvelope;\nuse Okvpn\\Bundle\\CronBundle\\Model;\n\nclass DoctrineCronLoader implements ScheduleLoaderInterface\n{\n    /**\n     * @inheritDoc\n     */\n    public function getSchedules(array $options = []): iterable\n    {\n        // ... get active cron from database/etc.\n\n        yield new ScheduleEnvelope(\n            'yor_service_command_name', // A service name, (object must be have a __invoke method)\n            // !!! Important. You must mark this service with tag `okvpn.cron_service` to add into our service locator.\n            new Model\\ScheduleStamp('*/5 * * * *'), // Cron expression\n            new Model\\LockStamp('yor_service_command_name'), // If you want to use locking\n            new Model\\AsyncStamp() // If you want to run asynchronously\n        );\n\n        yield new ScheduleEnvelope(\n            'app:cron:sync-amazon-orders', // Symfony console\n            new Model\\ScheduleStamp('*/5 * * * *'), // Cron expression\n            new Model\\LockStamp('sync-amazon-orders_1'), // If you want to use locking\n            new Model\\ArgumentsStamp(['--integration' =\u003e 1]), // Command arguments\n            new Model\\AsyncStamp() // If you want to run asynchronously\n        );\n\n        yield new ScheduleEnvelope(\n            'ls -l', // shell command\n            new Model\\ScheduleStamp('*/10 * * * *'),  // Cron expression\n            new Model\\ShellStamp(['timeout'=\u003e 300]), // Run command as shell\n        );\n\n        // ...\n    }\n}\n\n```\n\nAnd register your loader.\n\n```yaml\nservices:\n    Packagist\\WebBundle\\DoctrineCronLoader:\n        tags: [okvpn_cron.loader]\n\n```\n\n## Handling cron jobs across a cluster or custom message queue \n\nYou can use the cron `$group` to split many scheduled tasks between clusters, see example:\n\n```php\n\u003c?php declare(strict_types=1);\nnamespace App\\Cron;\n\nclass EntityCronLoader implements ScheduleLoaderInterface\n{\n    public function getSchedules(array $options = []): iterable\n    {\n        if (!\\in_array($group = $options['group'] ?? 'default', ['default', 'all_chunk']) \u0026\u0026 !\\str_starts_with($group, 'chunk_')) {\n            return;\n        }\n        \n        $chunkId = str_replace('chunk_', '', $group)\n        foreach ($this-\u003eregistry-\u003egetAllRepos($chunkId) as $name =\u003e $repo) {\n            $expr = '@random ' . $this-\u003egetSyncInterval($repo);\n            \n            yield new ScheduleEnvelope(\n                'sync:mirrors',\n                new Model\\ScheduleStamp($expr),\n                new WorkerStamp(true),\n                new Model\\ArgumentsStamp(['mirror' =\u003e $name,])\n            );\n        }\n    }\n}\n```\n\n```\n[program:app-cron]\ncommand=/path/to/bin/console okvpn:cron --env=prod --demand --group=chunk_%process_num%\nprocess_name=%(program_name)s_%(process_num)02d\nnumprocs=1\n```\n\nSee example of customization \n[one](https://github.com/vtsykun/packeton/tree/master/src/Cron/WorkerMiddleware.php), \n[two](https://github.com/vtsykun/packeton/tree/master/src/Cron/CronWorker.php)\n\n\n## Use ReactPHP EventLoop\n\nYou can add your own periodic tasks directly to `Loop`. \nThe bundle uses a simple wrapper `Okvpn\\Bundle\\CronBundle\\Runner\\ScheduleLoopInterface` for the library `react/event-loop`\n\n```php\n\u003c?php\nuse Okvpn\\Bundle\\CronBundle\\Event\\LoopEvent;\nuse Okvpn\\Bundle\\CronBundle\\Runner\\TimerStorage;\nuse Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener;\n\nclass CronStartListener\n{\n    #[AsEventListener('loopInit')]\n    public function loopInit(LoopEvent $event): void\n    {\n        $dataDogS = $this-\u003egetDDog();\n        $event-\u003egetLoop()-\u003eaddPeriodicTimer(6.0, static function () use ($dataDogS) {\n            $dataDogS-\u003eset('crond', getmypid());\n        });\n    }\n}\n```\n\n#### Configure ReactPHP adapter\n\nNeed to install [react/event-loop](https://github.com/reactphp/event-loop) if you want to use with async I/O, for example for handle websockets, redis.\n\n```\ncomposer req react/event-loop \n```\n\n```yaml\n# Add file to config/packages/*\nokvpn_cron:\n    loop_engine: okvpn_cron.react_loop # service name\n```\n\n```php\n\u003c?php\nuse Okvpn\\Bundle\\CronBundle\\Event\\LoopEvent;\nuse Okvpn\\Bundle\\CronBundle\\Runner\\TimerStorage;\nuse Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener;\nuse Okvpn\\Bundle\\CronBundle\\Runner\\TimerStorage;\nuse React\\EventLoop\\Loop;\n\nclass CronStartListener\n{\n    public function __construct(\n        private TimerStorage $timers,\n    ) {\n    }\n\n    #[AsEventListener('loopInit')]\n    public function loopInit(LoopEvent $event): void\n    {\n        $redis = new RedisClient('127.0.0.1:6379');\n        $timers = $this-\u003etimers;\n        $loop = $event-\u003egetLoop();\n\n        $redis-\u003eon('message', static function (string $channel, string $payload) use ($timers, $loop) {\n            [$command, $args] = unserialize($payload);\n            if ($timers-\u003ehasTimer($envelope = $timers-\u003efind($command, $args))) {\n                [$timer] = $timers-\u003egetTimer($envelope);\n                $loop-\u003efutureTick($timer);\n            }\n        });\n\n        Loop::addPeriodicTimer(5.0, static function () use ($redis) {\n            $redis-\u003eping();\n        });\n    }\n}\n```\n\nLicense\n-------\n\nMIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvtsykun%2Fcron-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvtsykun%2Fcron-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvtsykun%2Fcron-bundle/lists"}