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

Bundle Laravel Package

php-quartz/bundle

PHP Quartz Bundle brings Quartz-style job scheduling to Laravel and PHP applications, giving you a structured way to define, schedule, and manage recurring tasks. It helps organize background jobs and time-based workflows while reducing reliance on hand-written cron entries.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else: read the state of the package. php-quartz/bundle is archived, read-only, last released 2019-06-10, 2 stars, 0 dependents. That means no PHP 8 support, no Symfony 5/6 support, no security fixes, and effectively no community answers when something breaks. Treat this as "vendor a small library" rather than "install a dependency."

  1. Verify compatibility before writing code. Open composer.json on the default branch and check the require block: the php constraint and the symfony/* constraints tell you immediately whether you can use it at all. A 2019 bundle will typically sit around PHP 7.x and Symfony 3.4/4.x. If you're on Symfony 6+, stop here and read the migration note in Implementation Patterns instead.
  2. Fork, don't depend on upstream. Since the repo is read-only, add it as a VCS repository pointing at your fork, or copy the source into src/ as an internal bundle. Pin an exact commit — there will never be a patch release.
  3. Register the bundle the way your Symfony version expects (config/bundles.php for Symfony 4+, AppKernel::registerBundles() for 3.x).
  4. Find the config root. Read DependencyInjection/Configuration.php — it is the authoritative documentation for this package, more so than the README. The bundle alias defined there (something like php_quartz) is the YAML key you'll use in config/packages/.
  5. Locate the console command. Look in Command/. This is your entry point for actually running schedules; the bundle's whole value proposition is that it wires that command to a container-managed scheduler.
  6. First use case — one job, one cron trigger, one log line. Define a job class with an execute()-style method, register it as a service, point a cron trigger at it in config, then run the console command in the foreground to confirm it fires. Don't start with a daemon, a persistent store, or multiple schedulers.

Where to look first, in order: DependencyInjection/Configuration.php → Resources/config/*.xml|yaml (shows which services and tags exist) → Command/ → the core php-quartz/* packages it wraps (that's where job/trigger/store semantics actually live).

Implementation Patterns

Config-driven registration (the intended path). The bundle's selling point is that schedules live in configuration and job logic lives in services. Keep that split: job classes should be plain services with no scheduling knowledge, and the schedule should be declarative. This makes jobs trivially unit-testable and lets you change cadence without touching code.

Service-tag registration (the escape hatch). For jobs that need constructor dependencies or dynamic scheduling, use the container tag the bundle defines for jobs/triggers (confirm the tag name in Resources/config/) and register via services.yaml. Prefer this over config keys when your schedule is computed rather than literal.

Trigger selection. Quartz semantics give you two families:

Trigger Use for
Cron Calendar-shaped schedules ("weekdays at 02:00")
Simple/interval "Every N seconds/minutes", fixed-repeat counts, one-shot delayed jobs

Cron triggers are where Quartz earns its keep over a plain sleep loop — day-of-week and month fields, misfire handling, and explicit timezones.

Running it. Two operating modes, and you should decide explicitly which one you're in:

  • Long-running daemon under supervisor/systemd, using the bundle's console command. Needed for sub-minute granularity and for interval triggers.
  • Cron-invoked one-shot from the system crontab. Simpler operationally: no memory growth, no stale Doctrine connections, no orphaned workers.

Most teams end up with the daemon for fine-grained work and a system cron heartbeat that restarts it if it dies.

Integration with the rest of the app. Jobs are container services, so they compose normally: inject repositories, mailers, HTTP clients. If you're doing heavy work, have the job enqueue a Symfony Messenger message rather than doing the work inline — that gives you retries, failure transport, and idempotency handling you'd otherwise have to build on top of Quartz listeners.

Cross-cutting concerns via listeners. Use the bundle's listener extension point for structured logging (job name, start/end, duration, outcome), metrics emission, and alerting on failure. Do not put retry logic in every job class — centralize it in a listener or a base job class so behaviour is uniform.

Testing. Because scheduling is config and logic is a service, unit tests instantiate the job directly and call its execute method — no scheduler involved. For integration tests, build the scheduler in-process with a RAM store and fire triggers manually rather than sleeping. Never write a test that depends on wall-clock time passing.

Migration path. If you're on a modern Symfony, symfony/scheduler (6.3+) plus Messenger covers the same ground with first-party support: #[AsCronTask], RecurringMessage, and a messenger:consume worker you already run. Port the schedules and the job bodies — those translate cleanly — and drop the Quartz layer entirely. Given this package is archived with zero dependents, planning that exit before you build on it is not pessimism, it's the correct engineering call.

Gotchas and Tips

The compatibility cliff is the real gotcha. A 2019 bundle almost certainly uses APIs removed in Symfony 5 (ContainerAwareCommand, getRootNode() without TreeBuilder, DefinitionDecorator, direct ContainerBuilder service manipulation). Even if Composer resolves, container compilation may fail with opaque "service not found" errors. Run bin/console cache:clear and bin/console debug:container early to surface DI problems before they hide inside a command that silently does nothing.

Duplicate execution across app servers is the #1 production bug with any in-process scheduler. If your daemon runs on three web nodes, every job fires three times. Quartz-style schedulers only solve this if you wire a clustered/persistent job store with row locking; the default RAM store definitely does not. If you can't configure a persistent store, run the scheduler on exactly one node (dedicated role, leader election, or a single supervisor group) and let the other nodes do only web traffic.

RAM store loses everything on restart. Misfire and recovery semantics only mean anything with a persistent JobStore. Confirm what store the bundle wires by default in Resources/config/ — with in-memory storage, a crash mid-job leaves no record and no retry.

Long-running workers leak. In daemon mode, PHP accumulates memory and your Doctrine EntityManager will eventually close after an exception. Clear the EM per job (or per N jobs), and restart the process on a schedule. Check whether the command exposes a --clear-entity-managers-style flag; if not, Doctrine\ORM\EntityManagerInterface::clear() from a job listener is the workaround.

Timezone and DST. Cron triggers evaluate in a specific timezone. If your PHP date.timezone, your database, and the trigger's timezone disagree, "daily at midnight" drifts or double-fires around DST transitions. Set the trigger timezone explicitly rather than relying on defaults, and test the spring-forward/fall-back boundaries if the schedule is business-critical.

Overlap and misfire policy. A job that takes longer than its interval will either pile up threads or silently skip. Decide per-job: forbid concurrent execution (blocking), or allow it with an idempotency guard. This is the setting most teams forget until a slow query causes a thundering herd.

Job identity matters. Jobs and triggers are keyed by name/group in Quartz semantics. Renaming a job class or its config key can orphan the old trigger in a persistent store or create a duplicate. Treat job names as stable identifiers once deployed.

Debugging a "nothing happens" schedule. In order: (1) does debug:container show your job service and is it public/tagged? (2) is the trigger actually registered — dump the scheduler's job/trigger list from a listener at startup? (3) is the timezone what you think it is? (4) is the daemon even running? Add a startup listener that logs every registered trigger and its next-fire time; it will save hours.

Extension points worth knowing. Custom triggers, custom job factories, and scheduler listeners are the intended customization surface. If you need behaviour the bundle doesn't expose (a retry decorator, a distributed lock, a dead-letter path), implement it as a listener wrapping job execution rather than patching the bundle — your fork will never get upstream fixes, so keep the diff from upstream as small as possible.

Watch the config key namespace. Symfony bundles silently ignore unknown config keys in some setups, or throw on them in others depending on TreeBuilder usage. If a scheduling change has no effect, verify the key is actually being read by checking the compiled container in var/cache/dev/.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
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