Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Cron и очереди

Cron — планировщик Drupal: он периодически запускает служебную работу всех модулей — очистку, проверку обновлений, индексацию поиска. Очередь — список отложенных задач, которые выполняются понемногу в фоне. Вместе они позволяют не делать долгую работу, пока посетитель ждёт страницу.

Задача по расписанию

Свою работу в планировщик добавляет хук hook_cron в файле blog.module:

// web/modules/custom/blog/blog.module
 
/**
 * Implements hook_cron().
 */
function blog_cron(): void
{
    \Drupal::database()->delete('blog_subscribers')
        ->condition('created', strtotime('-2 years'), '<')
        ->execute();
 
    \Drupal::logger('blog')->notice('Старые подписки удалены.');
}

При каждом запуске планировщика функция удаляет подписки старше двух лет и пишет строку в журнал.

Запуск

vendor/bin/drush cr
vendor/bin/drush cron
vendor/bin/drush ws --count=5

Вторая команда запускает планировщик вручную, третья показывает последние записи журнала — среди них будет «Старые подписки удалены.».

На рабочем сервере планировщик запускают по расписанию операционной системы, например каждые 15 минут:

*/15 * * * * cd /var/www/site && vendor/bin/drush cron

Строку добавляют в таблицу заданий сервера командой crontab -e. Без неё Drupal запускает cron сам, вместе с запросами посетителей, раз в три часа — это настраивается на странице «Конфигурация → Система → Cron».

Зачем очередь

Хук blog_cron() должен работать быстро: пока он выполняется, остальные модули ждут. Если нужно обработать тысячи записей — например, уведомить всех подписчиков о новой статье, — задачи складывают в очередь, а планировщик разбирает её частями.

Добавление в очередь

Положим задачу в очередь, когда создаётся статья:

// web/modules/custom/blog/blog.module
 
use Drupal\node\NodeInterface;
 
/**
 * Implements hook_ENTITY_TYPE_insert() for node.
 */
function blog_node_insert(NodeInterface $node): void
{
    if ($node->bundle() !== 'article') {
        return;
    }
 
    $emails = \Drupal::database()->select('blog_subscribers', 's')
        ->fields('s', ['email'])
        ->execute()
        ->fetchCol();
 
    $queue = \Drupal::queue('blog_notify');
 
    foreach ($emails as $email) {
        $queue->createItem(['email' => $email, 'nid' => $node->id()]);
    }
}

\Drupal::queue('blog_notify') возвращает очередь с таким именем, createItem() кладёт в неё одну задачу — любой массив данных. Сохранение статьи остаётся быстрым: письма здесь не отправляются.

Обработчик

Задачи выполняет плагин QueueWorker. Его папка — src/Plugin/QueueWorker, а id совпадает с именем очереди:

// web/modules/custom/blog/src/Plugin/QueueWorker/NotifyWorker.php
namespace Drupal\blog\Plugin\QueueWorker;
 
use Drupal\Core\Queue\Attribute\QueueWorker;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
 
#[QueueWorker(
    id: 'blog_notify',
    title: new TranslatableMarkup('Уведомления подписчикам'),
    cron: ['time' => 30],
)]
class NotifyWorker extends QueueWorkerBase
{
    public function processItem($data): void
    {
        \Drupal::logger('blog')->notice('Уведомление для @email о статье @nid.', [
            '@email' => $data['email'],
            '@nid' => $data['nid'],
        ]);
    }
}

processItem() получает одну задачу. Здесь он только пишет в журнал — на этом месте была бы отправка письма. Запись cron: ['time' => 30] разрешает планировщику тратить на эту очередь до 30 секунд за запуск; оставшиеся задачи дождутся следующего.

Проверка

vendor/bin/drush cr
vendor/bin/drush queue:list
vendor/bin/drush queue:run blog_notify

Создайте статью, затем выполните команды. queue:list покажет очередь blog_notify и число задач в ней, queue:run выполнит их сразу, не дожидаясь cron.

Частая ошибка

Если processItem() бросает исключение, задача остаётся в очереди и будет повторена при следующем запуске. Это полезно при временном сбое, но задача с постоянной ошибкой — например, несуществующий адрес — будет повторяться бесконечно. Перехватывайте ожидаемые ошибки внутри обработчика и записывайте их в журнал, а исключение оставляйте для случаев, когда повтор действительно поможет.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.