ite/cron-bundle
ite/cron-bundle is a PHP package for defining, scheduling, and managing recurring cron jobs. It lets you configure scheduled tasks and run them reliably without manual crontab edits, simplifying background job automation.
Know what you're adopting first. ite/cron-bundle is a Symfony2-era bundle with a single star and no dependents. Treat it as a small, readable piece of glue code rather than a maintained platform dependency — budget an hour to read the source before wiring it in, and assume you may end up vendoring or forking it.
Install and register. With Composer (or the legacy deps file if you're truly on Symfony 2.0/2.1):
composer require ite/cron-bundle
Then enable it in app/AppKernel.php:
new Ite\CronBundle\IteCronBundle(),
Check composer.json for the PHP and Symfony version constraints — a bundle this old will often cap at symfony/symfony: ~2.x and an old PHP minor, which is the single biggest blocker to actually using it.
Look here first, in this order.
README.md — likely the only documentation; expect a config sample and one console command example.DependencyInjection/Configuration.php — the authoritative list of config keys. This is where you learn the real schema, not the README.Resources/config/services.xml (or .yml) — the service IDs and tags you'll reference in your own config.Command/ — the runner command you'll invoke from the system crontab.Cron/, Parser/, Schedule/ — the expression parsing and job execution logic.First use case: schedule one existing console command. The intended workflow is almost certainly: declare jobs in config.yml under an ite_cron: (or similarly named) key, each with a cron expression and a target, then add a single system crontab entry like:
* * * * * cd /path/to/app && php app/console ite:cron:run --env=prod >> app/logs/cron.log 2>&1
The bundle then decides, per minute, which of your configured jobs are due. Verify the exact command name with php app/console list | grep -i cron — don't trust the README here.
Config-driven job declaration. The core pattern for this class of bundle is a YAML map of jobs, each with an expression plus an action. Expect something shaped like:
# app/config/config.yml
ite_cron:
jobs:
nightly_cleanup:
expression: "0 3 * * *"
command: "app:cleanup --force"
reindex:
expression: "*/15 * * * *"
service: "app.search.indexer"
method: "reindex"
Confirm the actual keys against Configuration.php; the two common action styles are console command strings (simplest, gives you a real exit code) and service + method calls (faster, no process fork, but you own error handling).
Prefer commands over service calls for anything non-trivial. Wrapping work in a Symfony command means you get --env, --verbose, memory limits, and exit codes for free, and you can run the job manually the exact same way cron does. Reserve direct service invocation for tiny, in-process tasks.
Integrate with a real scheduler, don't invent one. Run the bundle's runner once per minute from system cron (or systemd timers) and let it fan out. Do not add one crontab line per job — that defeats the purpose and scatters configuration across two systems.
Layer your own wrapper for operational concerns. Because this bundle likely won't give you locking, retries, or structured logging, put them in a thin decorator around your job services:
class LockingJob
{
public function __construct(private $inner, private LockFactory $locks, private LoggerInterface $logger) {}
public function run(): void
{
$lock = $this->locks->createLock('cron.' . get_class($this->inner), 900);
if (!$lock->acquire()) {
$this->logger->warning('Skipped, still running', ['job' => get_class($this->inner)]);
return;
}
$start = microtime(true);
try {
$this->inner->run();
} finally {
$this->logger->info('Cron job finished', [
'job' => get_class($this->inner),
'duration' => microtime(true) - $start,
]);
$lock->release();
}
}
}
symfony/lock is not available on Symfony 2.x — in that era you'd use a flock() on a file or a SELECT ... FOR UPDATE row. Either way, the pattern is the same: every job must be re-entrant-safe or locked.
Per-environment configuration. Jobs that hit external APIs or send mail should not run in dev. Split config:
# config_dev.yml
ite_cron:
jobs: ~ # or an explicit empty map, depending on how the extension merges
Verify the extension's merge behavior — a bundle that overwrites rather than merges will force you to duplicate the whole tree.
Make jobs observable from day one. Since the bundle is unmaintained, you want to be able to drop it later without losing history. Log to a dedicated channel, and record start/finish/duration so a replacement implementation reads the same data.
Plan the exit ramp. Keep job logic in your own AppBundle command or service classes, and let the bundle be purely a scheduler that points at them. If you do that, replacing ite/cron-bundle with cron/cron-bundle or a plain crontab of commands is a config change, not a rewrite.
The package is effectively abandoned. One star, zero dependents, Symfony2 EOL'd in 2019. Before adopting: check the last commit date, whether it installs on your PHP version, and whether any of its code uses APIs removed in PHP 7.2+ (each(), mcrypt, dynamic properties, create_function). If it's unmaintained and untested, vendor it into your repo as an application bundle rather than depending on Packagist — you'll want to patch it, and you won't get upstream fixes anyway.
Cron expression parsing is the highest-risk part. Small cron libraries frequently get these wrong:
OR semantics (standard cron: if both are restricted, the job runs when either matches — most naive parsers use AND).* (5-20/3 vs. */3).7 vs 0 for Sunday, and named months/days (JAN, MON).Write a unit test that asserts a handful of expressions resolve to the expected timestamps, using DateTime fixtures. This is cheap insurance and also documents behavior for the next developer.
Timezones. PHP's default timezone and the server's cron timezone frequently disagree. Pick one — ideally set date.timezone explicitly in php.ini — and make sure the bundle's "is this due?" check uses the same zone as the crontab that invokes it. DST transitions are a classic source of double-runs and skipped runs.
Overlapping executions. If a job takes longer than its interval, the next minute's runner will start it again. Nothing in a bundle this size will stop that for you. Add locking (see above) or a "last run" check.
Silent failures. The runner may swallow exceptions from service-style jobs and still exit 0. If you rely on monitoring that alerts on non-zero exit, you'll never hear about failures. Either wrap in try/catch-and-rethrow, or emit a distinct log line and alert on that.
Memory and long processes. The runner is typically a single PHP process that may execute several jobs in sequence. Anything leaking (Doctrine's entity manager is the usual suspect) compounds. For heavy jobs, prefer the console-command action style so each gets a fresh process; call $em->clear() between batches in service-style jobs.
Debugging a "job didn't run". Diagnose in this order:
grep CRON /var/log/syslog, or check your redirect log.)-v/--dry-run flag if one exists — if not, add one, it's ten lines and saves hours.Extension points worth knowing. Look for a tagged-service mechanism — many bundles of this era let you tag services with something like ite_cron.job to register jobs without touching YAML. That's usually a cleaner integration than a giant config file, and it composes with DI. Also check for a dispatcher/event hook you can subscribe to for pre/post-run logging without forking.
Configuration key names are not guaranteed. Any sample you find online (including this one) is a guess derived from conventions. DependencyInjection/Configuration.php is the only truth. Read it before writing a single line of YAML, and run php app/console config:dump-reference ite_cron to confirm.
Have a replacement candidate in mind. cron/cron-bundle (actively maintained, Symfony 4+) or a plain crontab where each entry is php bin/console app:job-name are both lower-risk. The second option has zero dependencies and is often the right answer for a handful of jobs — the bundle only earns its place if you have many jobs that change frequently and you want them in version-controlled config.
How can I help you explore Laravel packages today?