From c6e6190b0043aa6ec7fe5f0ccdedab6573969cc0 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 May 2026 14:02:31 +0300 Subject: [PATCH] Add background tasks cookbook recipe --- src/.vitepress/config.js | 3 +- src/cookbook/background-tasks-and-cron.md | 190 ++++++++++++++++++++++ src/cookbook/index.md | 1 + 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 src/cookbook/background-tasks-and-cron.md diff --git a/src/.vitepress/config.js b/src/.vitepress/config.js index 120bc15b..3f4cfe62 100644 --- a/src/.vitepress/config.js +++ b/src/.vitepress/config.js @@ -205,7 +205,8 @@ export default { {text: 'Making HTTP Requests', link: '/cookbook/making-http-requests'}, {text: 'Using htmx for Partial Page Reloads', link: '/cookbook/using-htmx-for-partial-reloads'}, {text: 'Disabling CSRF Protection', link: '/cookbook/disabling-csrf-protection'}, - {text: 'Sentry Integration', link: '/cookbook/sentry-integration'} + {text: 'Sentry Integration', link: '/cookbook/sentry-integration'}, + {text: 'Running Background Tasks and Cron Jobs', link: '/cookbook/background-tasks-and-cron'} ] }, { diff --git a/src/cookbook/background-tasks-and-cron.md b/src/cookbook/background-tasks-and-cron.md new file mode 100644 index 00000000..90094ddd --- /dev/null +++ b/src/cookbook/background-tasks-and-cron.md @@ -0,0 +1,190 @@ +# Running background tasks and cron jobs + +Use console commands for scheduled maintenance work: sending digests, expiring old records, syncing data, or dispatching +queued jobs. The [Console applications](../guide/tutorial/console-applications.md) guide shows how to create a command; +this recipe focuses on running such commands reliably from cron or systemd. + +## Create an idempotent command + +The command should be safe to run again after a failure. Store progress in your database, use unique keys where possible, +and avoid assuming that the previous run finished. + +The following command uses a lock file to prevent overlapping runs and returns meaningful exit codes: + +```php +lockFile, 'c'); + + if ($lock === false) { + $io->error("Cannot open lock file \"{$this->lockFile}\"."); + return ExitCode::CANTCREAT; + } + + $locked = false; + + try { + if (!flock($lock, LOCK_EX | LOCK_NB)) { + $io->warning('Another send-digests run is still active.'); + return ExitCode::OK; + } + + $locked = true; + $count = $this->digestSender->sendDueDigests(); + + $this->logger->info('Sent due digests.', ['count' => $count]); + $io->success(sprintf('Sent %d digest messages.', $count)); + + return ExitCode::OK; + } catch (Throwable $e) { + $this->logger->error('Sending due digests failed.', ['exception' => $e]); + $io->error($e->getMessage()); + + return ExitCode::TEMPFAIL; + } finally { + if ($locked) { + flock($lock, LOCK_UN); + } + + fclose($lock); + } + } +} +``` + +The domain service keeps application logic outside the command: + +```php + Console\SendDigestsCommand::class, +]; +``` + +Bind `DigestSender` to your concrete service in DI. If you need a different lock path in production, configure the +`lockFile` constructor argument in a DI file or an environment-specific config file. + +## Run from cron + +Use absolute paths, set the application environment explicitly, and redirect output to a log destination: + +```txt +* * * * * cd /var/www/example.com/current && APP_ENV=prod php ./yii app:send-digests --no-interaction >> runtime/logs/cron.log 2>&1 +``` + +For Docker Compose deployments, run the command in the application service: + +```txt +* * * * * cd /var/www/example.com/current && docker compose exec -T php php ./yii app:send-digests --no-interaction >> runtime/logs/cron.log 2>&1 +``` + +Cron starts commands with a minimal environment. Do not rely on your interactive shell profile. Put required environment +variables into the crontab, a wrapper script, systemd unit, container environment, or Yii environment files. + +## Run from systemd timers + +For Linux servers using systemd, create a service unit: + +```ini +[Unit] +Description=Send due Yii digests + +[Service] +Type=oneshot +User=www-data +WorkingDirectory=/var/www/example.com/current +Environment=APP_ENV=prod +ExecStart=/usr/bin/php ./yii app:send-digests --no-interaction +``` + +Create the timer: + +```ini +[Unit] +Description=Run Yii digest sender every minute + +[Timer] +OnCalendar=*:0/1 +Persistent=true + +[Install] +WantedBy=timers.target +``` + +Systemd records stdout, stderr, and the exit code in the journal: + +```shell +systemctl status yii-send-digests.service +journalctl -u yii-send-digests.service +``` + +## Locking strategy + +Use one locking mechanism per command. A lock inside the command is portable and works the same from cron, systemd, +Docker, and manual runs. An external lock is also fine when you want operations staff to control it without changing PHP +code: + +```txt +* * * * * cd /var/www/example.com/current && flock -n runtime/send-digests.lock php ./yii app:send-digests --no-interaction +``` + +For multi-server deployments, a local file lock only protects one server. Use a database row, advisory database lock, +Redis lock, or a queue worker with a single consumer when the same job may run on multiple instances. + +## Failure behavior + +Return `0` when there was nothing to do or another copy is already running. Return a non-zero code when work failed and +an operator or scheduler should notice. + +Let the command throw or return a non-zero exit code for infrastructure problems such as unavailable database, queue, or +mail transport. Log enough context to diagnose the failed item, but avoid logging secrets and full personal data. + +For long-running commands, configure logging so records are flushed promptly. The logging guide explains +[flush and export intervals](../guide/runtime/logging.md#flushing-and-exporting-messages). diff --git a/src/cookbook/index.md b/src/cookbook/index.md index efddda87..6ffa36aa 100644 --- a/src/cookbook/index.md +++ b/src/cookbook/index.md @@ -17,6 +17,7 @@ This book conforms to the [Terms of Yii Documentation](https://www.yiiframework. - [Using htmx for partial page reloads](using-htmx-for-partial-reloads.md) - [Disabling CSRF protection](disabling-csrf-protection.md) - [Sentry integration](sentry-integration.md) +- [Running background tasks and cron jobs](background-tasks-and-cron.md) - [Working on Windows](working-on-windows.md) - [Opening files directly in PhpStorm](opening-files-in-phpstorm.md) - [Configuring Xdebug](configuring-xdebug.md)