Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony Scheduled Task Bundle Laravel Package

halloverden/symfony-scheduled-task-bundle

A Symfony bundle for defining and running scheduled tasks using Symfony's scheduler and messenger components, with attribute-based task configuration and a clean integration into your existing Symfony application.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, low-adoption bundle (5 stars, 0 dependents), so treat the README as the intended contract and confirm every class name against src/ before you build on it.

  1. Install alongside Messenger (the bundle's whole premise is that cron tasks are dispatched as messages, not executed inline):

    composer require halloverden/symfony-scheduled-task-bundle
    composer require symfony/messenger
    
  2. Register the bundle in config/bundles.php if Flex doesn't do it for you. Then check config/packages/ for a bundle-specific config file — you'll typically need to point it at a transport name and, if the bundle supports it, a task/tag locator.

  3. Read in this order: README.md → src/ top level (look for the task contract, the cron attribute/annotation, and the console command) → DependencyInjection/ for the config tree. The bundle is small enough to read end to end in an hour, and you should, because there is essentially no ecosystem documentation, blog posts, or Stack Overflow coverage to fall back on.

  4. First use case — the smallest useful slice: pick one repetitive job you already have (cache warm, stale-record cleanup, digest email), express it as a message class, attach a cron expression, and write a normal Messenger handler for it. Then run the bundle's dispatch command under a system cron entry (* * * * *) and let a separate messenger:consume worker do the actual work. Verify by watching the transport (messenger:stats or your transport's queue table) rather than assuming dispatch happened.

If you can't get the minimal slice working in an afternoon, that's your signal to stop and evaluate Symfony's native Scheduler component (6.3+) instead — it covers the same ground with first-party support.

Implementation Patterns

Separate "tick" from "work." The healthy mental model is: the cron entry (or a long-running scheduler loop) only decides and dispatches; a Messenger worker executes. Keep the message class a pure DTO — schedule ID, run timestamp, any parameters — and keep all business logic in the handler. This makes tasks retryable, observable, and testable independently of cron.

Define schedules declaratively, per task. Depending on what the bundle exposes, you'll either put the expression on the message class (attribute/annotation, e.g. #[Cron('0 */2 * * *')]) or in bundle YAML. Prefer the attribute form: the schedule lives next to the task, diffs are obvious in review, and you avoid a config file that silently drifts from the class it references. Whatever the bundle supports, standardize on one style across the codebase.

Route scheduled tasks to their own transport. Give scheduled messages a dedicated transport/queue (e.g. async_scheduled) rather than sharing the default. Benefits: you can scale those workers separately, you can pause the queue during an incident without stalling user-facing async work, and backlog is easier to reason about.

Guard against overlap. A */5 cron with a handler that sometimes takes 8 minutes will pile up. Add a lock (symfony/lock) keyed on the task ID at the top of the handler, or configure a per-transport worker concurrency of 1 for that queue. Don't assume the bundle does this for you — a dispatcher-only design almost certainly doesn't.

Make handlers idempotent. Messenger retries on failure by default. A handler that sends a digest email will send it twice on a transient SMTP error. Use a run-ID / dedupe table, or mark the message with a deterministic ID and check-before-do.

Testing workflow. Unit-test the handler like any other message handler (call __invoke with a constructed message). For the schedule itself, assert on the cron expressions you declared — a table-driven test that parses each task's expression and checks isDue() at fixed timestamps catches the classic 0 0 * * * vs 0 0 * * 0 mistakes and timezone surprises.

Integration tip — compose, don't rebuild. If the bundle exposes an event or a "task was dispatched" hook, subscribe to it for metrics/logging. If it doesn't, wrap your message dispatch in a thin decorator so you get the same observability without forking. Pair the bundle with symfony/monolog structured logging keyed on task ID — you'll need it.

Migration path. Symfony's Scheduler component (#[AsSchedule], RecurringMessage::cron()) is the canonical modern answer. Structure your tasks so the message classes and handlers are bundle-agnostic; then swapping the scheduling layer later is a config change, not a rewrite.

Gotchas and Tips

  • Maturity is the headline risk. 5 stars and zero dependents means no community safety net: expect sparse docs, no answered issues, and a real chance the package is unmaintained. Pin the exact version in composer.json, read the source before relying on any behavior, and keep a fork in mind. Budget for owning it.
  • Check Symfony/Messenger version constraints first. A tiny bundle often pins symfony/messenger to a narrow range. If you're on a recent Symfony, the constraint may block installation or force an old Messenger — in which case native Scheduler is strictly better.
  • Cron timezone is a classic footgun. Cron expressions are evaluated in some timezone — verify whether it's PHP's date_default_timezone_set, the server's, or a bundle config value. DST transitions produce skipped or doubled runs at 02:00–03:00; schedule nightly jobs at an odd hour (e.g. 03:17) to sidestep it.
  • Two schedulers = duplicate dispatch. If you run the dispatch command on more than one host (or a supervisor with numprocs>1), every task fires N times. Either run the dispatcher on exactly one node, or wrap dispatch in a distributed lock. This is the single most common production failure for cron→queue designs.
  • No --dry-run is a warning sign. If the dispatch command can't list what it would dispatch, add that yourself early — it's the fastest debugging tool you'll have. bin/console <cmd> -vvv plus a log line per evaluated task saves hours.
  • Failures hide in the transport, not the scheduler. A failed task looks like a successful dispatch. Get comfortable with messenger:failed:show, messenger:failed:retry, and your transport's failed-message table before you go live. Set up an alert on failed-message count.
  • Config quirks: the bundle's config tree may accept a task list by service ID or by tag. If it's tag-based, remember tags need autoconfigure or explicit tags: entries — a missing tag means a silently unscheduled task with no error. Add a smoke test or a debug command assertion that your expected task count matches reality.
  • Extension points to look for: custom cron-expression parser (usually dragonmantank/cron-expression), a task locator/registry you can decorate, and any dispatched-event you can hook. Decorating the registry is the cleanest way to add per-task enable/disable flags, metrics, or a "run now" admin action without touching vendor code.
  • Exit criteria. Define now what would make you drop the bundle: no release in 12 months, an unfixed issue that blocks you, or a Symfony major upgrade it doesn't support. Write that down; small dependencies tend to rot quietly.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console