eonx-com/schedule-bundle
Brings Laravel's command scheduling to Symfony: register scheduled commands via autoconfigured schedule providers with the full Laravel scheduling API, then run them with the schedule:run console command.
Architecture fit. The bundle is a narrow Symfony adapter over Laravel's console scheduling subsystem: one ScheduleProviderInterface, one fluent ScheduleInterface, one console entry point (schedule:run). For a TPM this matters less as an architecture question than as a scope question — it defines the boundary of what "scheduling" means in the product. In-scope: cadence definition, argument passing to commands, overlap locking (setMaxLockTime), and provider discovery via Symfony autoconfigure. Out-of-scope and left to us: execution visibility, retry/backoff semantics, alerting, distributed coordination, and the cron/heartbeat that must actually invoke the runner. That division is clean and honest, but it means any roadmap item phrased as "reliable scheduled jobs" is only ~40% addressed by adopting this bundle. Plan and staff the remainder explicitly.
Integration feasibility. Two commands and one config line — composer require, register ScheduleBundle::class in config/bundles.php, implement a provider. The absence of a Symfony Flex recipe is the first real signal: no automated install, no default config, no recipe-driven upgrade path. That is a proxy for "never reached ecosystem integration," and it tells me the maintenance cost curve is worse than a typical bundle's. The second feasibility constraint is transitive dependency coupling: the bundle pulls illuminate/* scheduling components into a Symfony graph, so version ranges for PHP, symfony/framework-bundle, symfony/dependency-injection, and illuminate/support must all intersect. There is no published compatibility matrix, so this must be established empirically before any commitment is made. Treat feasibility as unproven until smoke-tested, not "trivially easy because it's small."
Technical risk. High, and predominantly governance risk rather than engineering risk. The repository is archived and [READ-ONLY], with zero stars, zero dependents, and README-level documentation. Concretely that means no CVE response, no PHP/Symfony version bumps, no bug triage, and no production signal that edge cases (locking across hosts, timezone handling, withoutOverlapping, runInBackground, exit-code propagation) behave outside the author's proof of concept — the README's own examples are poc:hello-world. For a component whose failure mode is silent non-execution of business-critical work, an archived dependency is a dependency-review finding, not a technical detail. The mitigating factor is small surface area: providers implement a single method and each ->command() call maps to a Symfony Scheduler RecurringMessage or a crontab line, so exit cost is genuinely low. That reversibility is the strategic reason this is worth a timeboxed evaluation at all.
Key questions. Which PHP / Symfony / illuminate/scheduling versions are supported, and where is that recorded other than the code? How is setMaxLockTime backed — cache, filesystem, lock file — and does that backing work across more than one host? What happens if autoconfigure is disabled for a service that implements ScheduleProviderInterface — how loudly does registration fail? What does the DSL actually cover versus what we need (when/skip, between, environments, output redirection, exit-code handling)? Who becomes the named internal owner if we fork, and what is the upgrade trigger — a Symfony major, a PHP EOL, or a CVE in illuminate/*? Has Symfony's first-party Scheduler component (native since 6.3) been scored on the same criteria? And the sequencing question that gates everything: do we have run-history and failure alerting today, or would we be shipping an invisible scheduler?
Stack fit. This only fits a Symfony codebase that specifically wants Laravel's ergonomics and is willing to carry Laravel framework components to get them. Where the org runs Laravel and Symfony side by side, or is transitioning engineers between the two, the mental-model transfer is real value — that is a velocity and hiring argument, not just a syntax argument. For a Symfony-native team the fit is weaker: foreign idioms plus a transitive dependency for what is largely fluent sugar. Product positioning should be honest — this is a developer-experience play with an operational cost, and the cost is carried by whoever owns the fork.
Migration path. The realistic decision is not "adopt this bundle." It is one of three, and the TPM's job is to force the choice into a documented artifact rather than let it be decided by whoever writes the first PR:
illuminate/* coupling, first-party support, cron-expression messaging. Default recommendation absent a specific counter-argument.illuminate/* versions, namespace it internally, assign an owner, add to the dependency-review register. Justified only by a concrete need for this exact DSL now.Note that exit cost is low in all three directions, which argues for timeboxing the evaluation rather than extending the decision cycle. A two-week spike against the Symfony Scheduler scorecard is cheaper than three more meetings.
Compatibility. Verify before committing anything to a roadmap: PHP constraint, symfony/framework-bundle and symfony/dependency-injection ranges, transitive illuminate/* versions. Because there is no recipe and no visible CI matrix, this is empirical work — composer why-not against each constraint in a throwaway branch, then a smoke test that the autoconfigure path actually discovers a provider. Two specific traps to put in the acceptance criteria: silently unregistered providers (app-level autoconfigure: false yields an empty schedule with no error), and lock backend portability if we run more than one app instance. Both are "green in staging, broken in prod" shapes, so require a negative test for each.
Sequencing. Gate the work so no production dependency is taken before the observability story exists.
schedule:run processes.Do not sequence step 5 ahead of step 2. A scheduler nobody can see is a scheduler nobody will trust, and trust is the actual deliverable.
Maintenance. The steady-state cost is not zero and it is not the bundle's — it is ours. Archived software means every future PHP upgrade, Symfony major, or illuminate/* security advisory becomes an internal ticket with no upstream to escalate to. Estimate the carrying cost as "one small owned fork" rather than "one composer dependency," and budget accordingly: an owner, a pinned version, a documented patch process, and a line in the dependency-review register with a re-review date. If we cannot name that owner this sprint, we should not take the dependency this quarter.
Support. There is no vendor, no maintainer, and no community. Support is entirely internal, which means the escalation path is our on-call, and the runbook has to be written from scratch because the README is the whole documentation set. Practical consequence: the first person paged at 03:00 for a missed nightly job will be someone reading Laravel's 5.8 scheduling docs to figure out why setMaxLockTime didn't release a lock. That is a real ramp cost and should be priced into the estimate, not discovered.
Scaling. The bundle is single-runner-shaped. It gives per-task overlap protection but not distributed coordination, queue backing, or sharding. At modest scale — one host, or a small fleet with a shared lock backend — that is fine. Beyond that, several runners will either duplicate work or contend on locks, and the bundle offers no help. The honest framing for stakeholders: this is a crontab replacement, not a workflow engine. If the roadmap includes high-volume or must-order job orchestration, the correct long-term answer is a queue-backed scheduler (Symfony Messenger with a scheduler transport, or an equivalent), and this bundle is a stepping stone of limited duration.
Failure modes. Rank them by how quietly they fail, because silent failure is the expensive kind:
setMaxLockTime bounds this, but only if set deliberately per task; default it in the provider template rather than trusting reviewers to remember.command('x', ['-v']) behaving differently than expected across illuminate versions; low severity individually, but it erodes confidence in the schedule definition as the source of truth.Ramp-up. For engineers with Laravel background, near-zero — the DSL is familiar and the provider pattern is idiomatic enough. For Symfony-native engineers, the ramp is small but real: foreign idioms plus an unmaintained dependency they may reasonably object to in review. Two things reduce friction materially: a provider template with sane defaults baked in (max lock time, logging, environment guards), and a one-page decision record explaining why this package and not the native Scheduler. Without the record, the same debate recurs in every PR. Plan for a half-day of onboarding, plus the ongoing tax of explaining an archived dependency to auditors and new joiners.
Recommendation for the roadmap. Fund a two-week, timeboxed spike: prototype one real job, prove discovery and overlap locking, and score against Symfony's native Scheduler. Set the default outcome as native Scheduler or a small internal fork, with this package as the reference for API ergonomics. Gate production adoption on an observability prerequisite — run history, absence alerting, and a lock-leak runbook — because the bundle schedules commands but does not, and will not, tell you when they did not run. Success metric at the end of the cycle is not "bundle installed" but "scheduled jobs moved out of crontab, visible, alerting, and owned."
How can I help you explore Laravel packages today?