bytespin/console-command-scheduler-bundle
ByteSpin Console Command Scheduler is a Symfony 6.3+ bundle for scheduling console commands via Messenger Scheduler, logging execution time, duration and return codes to database and log files, with an EasyAdmin interface and extendable email notifications.
Framework mismatch is the first finding. The brief frames this as a "Laravel/PHP package." It is not. bytespin/console-command-scheduler-bundle is a Symfony bundle (PHP 8.2+, Symfony 6.3+, Doctrine ORM, EasyAdmin, Messenger/Scheduler). If the target application is Laravel, this is a non-starter: there is zero reusable surface. The scheduling model (RecurringMessage::every / ::cron), the service container wiring, the entity mapping, and the admin controllers are all Symfony idioms. A Laravel port would be a from-scratch build against Schedule::command(), so this package should be treated purely as a design reference. The assessment below assumes a Symfony estate; if that assumption is wrong, stop here.
Architecture fit (Symfony). The bundle is a thin persistence-and-admin facade over Symfony's native Scheduler and Messenger components. That is the right shape: it does not invent a scheduler, it stores RecurringMessage definitions in Doctrine and exposes CRUD. Conceptually clean, single responsibility. Two architectural frictions remain:
Scheduler and SchedulerLog entities live in vendor/bytespin/.../src/Entity and are mapped directly into the host application's entity manager. This couples your Doctrine metadata, your schema, and your migrations to a third-party vendor path. It also forces a project-wide config change: auto_mapping: true must be disabled and every entity in the app remapped. In a multi-entity-manager application that is a nontrivial blast radius.DashboardController shipped. The README implies the admin surface is optional in principle (core scheduling is the events + Messenger path), but this is unverified and should be spiked before committing.Integration feasibility: medium-to-hard. There is no Symfony Flex recipe. Installation is a manual checklist: composer require, manual registration in config/bundles.php, run bytespin:configure-console-command-scheduler, hand-verify doctrine.yaml mappings, then doctrine:schema:update --force. Note a documentation defect worth catching in a 30-second Packagist check: the README's install command says bytespin/console-commande-scheduler-bundle (with an "e", French spelling) while the repository is console-command-scheduler-bundle. Confirm the actual package name before writing it into a build.
Technical risk factors:
doctrine:schema:update --force, the README explicitly warns you to run it on updates, and it even self-contradicts on which versions changed the schema ("1.0.13 requires schema update" vs. "last version that includes schema modifications is 1.1.0"). Using schema:update --force in a controlled production pipeline is a policy violation in most shops.Credible positives. The 2.1 reliability work is genuinely above average for a solo beta bundle and signals the author has been burned in production: class-level validation that rejects a cron expression typed into the interval field, per-entry fault isolation with a SchedulerEntryFaultedEvent, an invalid badge in the admin list, and a bytespin:scheduler:lint command that exits non-zero. That last one is the single most valuable feature here — it turns a runtime class of failure into a CI gate.
Key questions before adoption: Is the estate Symfony 6.3+/7.x, or Laravel? Does the app already run Messenger with a persistent worker and a transport? Does it already use EasyAdmin, and does core scheduling work headless if we don't want it? Can we replace schema:update --force with generated migrations against the vendor entities, and will those entities survive an upgrade? Is the maintainer responsive, and what is the stated BC policy for a 1.x beta?
Stack fit gate. Proceed only if: PHP 8.2+, Symfony 6.3+ (validate 7.x in a spike), Doctrine ORM with a single primary entity manager (or willingness to remap all entities), Messenger configured with a transport, and supervisor/systemd available for a long-running consumer. If the app is API-only and has no EasyAdmin, budget a spike to confirm the admin layer is detachable before assuming it is.
Migration path by starting point:
Schedule::command() — do not attempt a port. Re-baseline as a Symfony-native scheduler or stay on Laravel's scheduler.Sequencing:
bytespin:configure-console-command-scheduler, schedule one trivial command, run messenger:consume scheduler_scheduler -vv, confirm the log row and admin view. This validates the entire wiring in under a day.composer.json and record it. Beta software should never float.migrations/, review them, and commit. Add a CI gate that fails if doctrine:schema:validate detects drift. Explicitly ban schema:update --force outside local/CI.bytespin_console_command_scheduler.yaml), including an explicit process_only_last_missed_run decision — do not inherit a default by accident.bytespin:scheduler:lint in CI and in the deploy pipeline; a SchedulerEntryFaultedEvent listener that pages your on-call channel.DashboardController.Tag entity plus resolve_target_entities. Treat as a later phase; it adds schema surface for cosmetics.Compatibility caveats. No Flex recipe means no auto-generated config and no guaranteed upgrade path — treat every composer update as a manual schema-review event. Version 1.1.0 was the last schema-changing release, so pinning at or above that avoids cross-major schema jumps, but verify against the changelog each bump.
Vendor strategy. Given the vendor-mapped entities and absent migrations, evaluate forking into an internal namespace or vendoring a pinned copy so schema changes enter your repo as reviewable diffs rather than as vendor/ surprises.
Maintenance. Ongoing cost is dominated by the missing recipe. Every upgrade is a manual checklist item ("do not forget to update the database schema"), which is exactly the kind of instruction that becomes an incident when it is forgotten. Mitigate with a written runbook entry, the schema-drift CI gate, and a policy that bundle upgrades ship in the same PR as their generated migration. Assume one to two days of engineering per year simply tracking upstream, plus the initial integration.
Support. Upstream support is a single maintainer's GitHub issue queue with no SLA. Operationally, this package must have a named internal owner. Treat bug reports as something you file as a courtesy, not as a dependency in your incident plan.
Scaling. Three separate vectors:
messenger:consume scheduler_scheduler is a serial bottleneck. The per-task messenger queue field is the correct escape hatch: route slow ETL-ish commands to a dedicated queue so they cannot block the default scheduler queue. Use it deliberately, not as a post-incident patch.No DB Log on chatty high-frequency commands and schedule a pruning job on SchedulerLog from day one.Failure modes, ranked:
messenger:consume is not running, nothing executes and nothing complains. This is the primary operational risk. Mitigate with an external heartbeat: alert if no SchedulerLog row has been written within N minutes, where N is well below your most frequent schedule's interval.SchedulerEntryFaultedEvent, but the entry still silently stops running. The lint command plus the fault listener together close this; either one alone does not.getManagerForClass() will misbehave if auto_mapping: true returns or if entities are mapped to multiple managers. Lock this with a config test.Ramp-up. Operators ramp fast: the EasyAdmin screens are self-describing (disabled, command, arguments, type, frequency, from/until, log file, no-DB-log, queue), and the log viewer needs no training. Engineers need moderate ramp: the wiring is invisible in code and lives entirely in bundles.php, doctrine.yaml, the config YAML, and a supervisor unit. Estimate half a day for a Symfony-fluent developer to reach a working demo, and one to two additional days to make it production-safe (migrations, worker supervision, heartbeat alerting, CI lint, fault listener).
Exit cost: moderate and bounded. Everything underneath — Messenger, Scheduler, Doctrine — is standard Symfony. The bundle's persistent footprint is two tables and a config file. Replacing it with native Symfony Scheduler YAML plus a small custom admin is a realistic escape hatch, which is the strongest argument for adopting it: the lock-in is shallow even if the vendor relationship fails.
Recommendation. Adopt only in a Symfony 6.3+/7.x application that already runs Messenger with a supervised worker and already uses EasyAdmin. Enforce a pinned version, owned migrations, CI lint, a fault-event listener, and a worker heartbeat. Do not adopt it in a Laravel application, and do not adopt it without the heartbeat — the silent-worker-down failure mode is the one that will burn you.
How can I help you explore Laravel packages today?