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

Cron Laravel Package

ibexa/cron

Exposes cron/cron for Ibexa and Symfony through the simple ibexa:cron:run command. Tag existing Symfony commands as cron jobs with schedules, optional categories, and custom options, then run them from system cron. A lightweight alternative to cron-bundle.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and verify the wire-up first.

composer require ibexa/cron
bin/console list ibexa:cron        # confirm the command is registered
bin/console ibexa:cron:run --help  # confirm the actual options on your version

Look here first, in this order:

  1. src/Command/ (or src/) — the package is small enough that the entrypoint command is the documentation. Read the execute() method: it tells you exactly which tagged commands are collected, how they are filtered by category, and whether failures abort the loop.
  2. The DI extension / compiler pass — this is where the ibexa.cron.job tag is consumed and the tag attributes (name, schedule, category) are read. If a tag looks ignored, the bug is almost always here.
  3. The cron/cron dependency itself — Cron\CronExpression::factory($schedule)->isDue() is the whole matching engine. Everything surprising about timing comes from that library, not from Ibexa.

First use case — the one that pays for the install: an existing Symfony command that already works from the CLI, run on a schedule without writing a crontab entry per command.

#[AsCommand(name: 'app:sync-prices', description: 'Sync prices from ERP')]
class SyncPricesCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // ...
        return Command::SUCCESS;
    }
}

Tag it in config/services.yaml:

services:
    App\Command\SyncPricesCommand:
        tags:
            - {
                name: 'ibexa.cron.job',
                schedule: '*/15 * * * *',
                category: 'default',
              }

Then a single system crontab line, on every machine that should run jobs:

* * * * * /usr/bin/php /var/www/app/bin/console ibexa:cron:run --category=default >> /var/log/app-cron.log 2>&1

Note the deviation from the README example: log to a file instead of /dev/null. You will spend hours debugging without it.


Implementation Patterns

Treat the package as a dispatcher, not a scheduler. Its job is "at this minute, which tagged commands are due, and run them." Anything heavier belongs in Messenger or a Symfony Scheduler.

Thin-command pattern (recommended). Keep the scheduled command trivial — parse input, dispatch a message, exit. Do the work in a handler.

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $this->bus->dispatch(new SyncPricesMessage());
    return Command::SUCCESS;
}

This keeps the cron tick short, gives you retries and transport-level concurrency control, and makes it painless to move the job off the cron path later.

Category as environment/role boundary. The category attribute is the package's only real knob, so use it deliberately:

  • default — lightweight, per-node, idempotent jobs (cache warmers, health pings).
  • batch — heavy or stateful work; registered on exactly one node's crontab.
  • infrequent — hourly/daily jobs, on a separate crontab line so a runaway batch job can't starve them.

Because each category requires its own system crontab entry, keep the number small. Two or three categories is plenty; ten is an operational mess.

Expression style. Use cron/cron's supported syntax and stick to expressions a Linux admin can read: * * * * *, */5 * * * *, 0 3 * * *, 0 3 * * 1-5. Alias forms like @daily and hash-based H are the kind of thing you should verify against your pinned version rather than assume.

Integration tip — one config block per job, next to the command. Rather than a central crons.yaml, tag each command in the file where it's declared (or in a dedicated config/services/cron.yaml that imports nothing else). When you delete a command, its schedule dies with it. Centralized schedules rot.

Prefer idempotent jobs. Since there's no lock and no state tracking in this package, a job that can safely run twice is worth more than a job that needs exactly-once semantics.

Verify registration in CI. A wrong attribute name or a typo'd category fails silently at runtime — the job just never runs. Add a smoke check:

bin/console debug:container --tag=ibexa.cron.job

and assert the expected command IDs are present. This is the single highest-value test you can write around this package.


Gotchas and Tips

No locking — this is the big one. The README is explicit that cluster support and a lock system are future work. Consequences:

  • A job that takes longer than its interval will overlap with itself on the next tick. If the job isn't safe to run concurrently, add your own lock (symfony/lock, a DB advisory lock, or a flock wrapper in the crontab line).
  • Running ibexa:cron:run on multiple nodes means every node runs every default job. Don't scale horizontally into this without external coordination.
  • The README's own multi-node note ("run on all nodes vs one at a time") describes intent, not shipped behavior. Verify against the source of the version you pin.

Silent no-ops cost the most time. A job doesn't run when:

  • the tag name isn't byte-for-byte ibexa.cron.job;
  • the attribute is written as expression/cron instead of schedule;
  • the category in the tag doesn't match the --category you pass (both default to default, so a typo on one side means never);
  • the container wasn't rebuilt after you edited services.yaml — always bin/console cache:clear (in dev, the compiled container is cached).

Debug in this order: debug:container --tag=ibexa.cron.job → run the command directly (bin/console app:sync-prices) → run bin/console ibexa:cron:run --category=... -vvv at a minute where the expression should match.

Timezone mismatch. cron/cron evaluates against PHP's default timezone; system cron uses the system's. On a container with TZ=UTC and an app configured to Europe/Warsaw, a 0 3 * * * job fires at the wrong local hour. Pin date.timezone in php.ini or set TZ on the container.

One-minute granularity is a hard floor. Your crontab runs every minute; the package matches the current minute. Sub-minute schedules don't exist here, and a tick that takes >60s silently swallows the next minute's jobs.

Don't point /dev/null at it. The README example discards output. Redirect to a log, and consider --no-interaction (or -n) plus set -e-style discipline in the wrapper script so a PHP fatal doesn't go unnoticed.

Exit codes and failure isolation. Check the source for whether a failing job stops the remaining jobs in the batch. Don't assume other jobs still run — if isolation isn't there, order your tags so the critical jobs are least likely to be starved, or run critical jobs in their own category.

Not a cron-bundle replacement — don't expect the features. No admin UI, no job history, no retries, no per-job output capture, no "last run" state. If you need visibility, wrap the command with your own logging listener or emit metrics from inside the job.

Don't double-schedule. If you already use symfony/scheduler or Messenger's Schedule attributes, pick one mechanism per job. Two schedulers firing the same command is the classic post-migration outage.

Extension points. The runner is a normal Symfony command and the collection is built from tagged services, so:

  • decorate or wrap the ibexa:cron:run command to add timing/logging around invocation;
  • build your own thin wrapper command that calls the underlying services if you need pre/post hooks;
  • for anything the package doesn't do (locking, history, retries), implement it around the job rather than forking the package — it's small and you'll want to track upstream.

License and maintenance risk. GitHub reports NOASSERTION, which means the license wasn't auto-detected — open the LICENSE file and confirm it's acceptable to your legal team before shipping (Ibexa packages are typically GPL-family, which matters for proprietary distributions). Combined with 3 stars and zero known dependents, treat this as thin, unproven infrastructure: pin an exact version in composer.json, read the source of the version you pin, and keep a documented fallback (plain crontab lines, or symfony/scheduler) so you can migrate in an afternoon if it's abandoned.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route