Возникла простая задача, которая легко решается на php, но никак не получается решить её на twig.
Нужно взять определённую дату формата Y-m-d и определить разницу с другой датой. С этим хорошо справляется функция Diff, но почему-то в twig она не хочет работать, да и в документации twig ничего про неё нет.
Подскажите как вычесть разницу между датами в Drupal, есть рабочий пример кода на twig?
Вводные:
две даты - одна y-m-d, вторая 'now'
ищем разницу и выводим в годах и месяцах, дни опускаем
или округляем
Нашли на форуме пример задачи - не сработало:
https://stackoverflow.com/questions/15657687/twig-date-difference
У нас логика работы закладывается такая: если мы хотим, чтобы стаж сотрудника на сайте был всегда актуален, нужно вписать его стаж из справки в ПФР (лет и месяцев), далее указать дату получения этой правки и уже на основе это даты прибавлять в начале каждого месяца плюс один месяц к указанному стажу.
Комментарии
Вот тут обсуждалось что-то очень похожее, может подойдёт?
По ссылке пример использования встроенного в PHP класса DateTime, что поддерживается Twig'ом (вроде бы).
Но вы используете объекты даты/времени Друпала, судя по всему. А это как бы "другое".
По сути вопроса. Я бы написал расширение Twig для подобных вычислений, если уж необходимо это делать именно в шаблоне. А ещё проще это делать в хуке препроцесса.
Задачи сделать именно в шаблоне нет, т.к. как уже понятно, что это не Drupal way. Изначально думали, что это быстрая не долгая процедура в пару строк, поэтому хотели на twig сделать такое. Сейчас в .theme файле делаем следующее:
<?php
//Делаем правильное склонение для вывода даты (год, года, лет...)
function MY_THEME_preprocess_node(&$variables) {
//Выгружаем переменные ноды, если не пустые
if (isset($variables['node'])) {
$node = $variables['node'];
//Делаем переменную с именем типа ноды
$nodetype = $node->bundle();
//Проверяем на нужный нам тип ноды (сотрудник), в котором есть нужные нам поля
if ($nodetype == "worker"){
//Дата справки - присваиваем нужное значение из поля и делаем его строкой
$pfr_date = $node->field_worker_pfr_date->getString();
//Далее присваеваем переменные числам стажа (лет и месяцев общего стажа и стажа по специальности)
$duration_years = $node->field_worker_duration_years[0]->value;
$duration_months = $node->field_worker_duration_months[0]->value;
$duration_years_spec = $node->field_worker_duration_years_spec[0]->value;
$duration_months_spec = $node->field_worker_duration_month_spec[0]->value;
function num_word($value, $words, $show = true){
$num = $value % 100;
if ($num > 19) { $num = $num % 10; }
$out = ($show) ? $value . ' ' : '';
switch ($num) {
case 1: $out .= $words[0]; break;
case 2:
case 3:
case 4: $out .= $words[1]; break;
default: $out .= $words[2]; break;
}
return $out;
}
//Считаем количество лет и месяцев, возвращаем строку с результатом
function countYearsAndMonths($date, $yearsToAdd, $monthsToAdd) {
$today = new DateTime();
$dateObj = DateTime::createFromFormat('Y-m-d', $date);
$interval = $dateObj->diff($today);
$years = $interval->y;
$months = $interval->m;
$totalYears = $years + $yearsToAdd;
$totalMonths = $months + $monthsToAdd;
if ($totalMonths >= 12) {
$totalYears += floor($totalMonths / 12);
$totalMonths = $totalMonths % 12;
}
return num_word($totalYears, array('год', 'года', 'лет')).' '.num_word($totalMonths, array('месяц', 'месяца', 'месяцев'));
//Присваиваем результат функции глобальной переменной
$variables['stag'] = countYearsAndMonths($pfr_date, $duration_years, $duration_months);
$variables['stag_spec'] = countYearsAndMonths($pfr_date, $duration_years_spec, $duration_months_spec);
}
}
}
}
?>
Вы это имели ввиду? Как сделать функцию twig, чтобы мы не присваивали значение глобальной переменной для вывода, а делали расчёт в шаблоне подставляя нужные значения? Или нужно это в какую-то переменную выносить для каждой ноды и потом подставлять готовое значение по nid?
Мы также хотим это не только внутри шаблона ноды выводить, но и во вьюсе, где на странице может быть до 50 значений, которые как-то будут просчитываться. Думали в шаблоне views выводить цифры и подставлять в функцию - насколько это тру вэй?
Да, это.
Зависит от контекста шаблона - т.е. каким набором сущностей он манипулирует. Если шаблон служит только для вывода одной конкретной сущности - можно смело вычислять переменные на препроцессе и писать в глобальную перменную. Область видимости переменных этого шаблона будет исключительно в контексте этой сущности. Большинство шаблонов Друпала, собственно, так и работают.
Однако, если это какой-то хитрый шаблон для вывода ряда сущностей за раз - тут да, нужна какая-то функция калькуляции из шаблона. Хотя, собственно, помимо препроцессов есть ещё некоторые хуки сущностей, где можно создать виртуальные поля при выводе отдельной ноды/сущности (например,
hook_entity_view
). Такие "динамические" поля тоже доступны как обычные поля внутри Twig'а.Если нужна кулькуляция именно внутри шаблона - озвучу предложение изучить расширения Twig (Twig Extensions). По сути, это абстрактный класс, объявляющий дополнительные кастомные функции для вызова изнутри Twig'а. Можете сами погуглить, но вот, например, первое навскидку из поиска:
https://www.hashbangcode.com/article/drupal-9-creating-custom-twig-funct...
Кстати, эта возможность также документирована в оригинальной документации шаблонизатора Twig. Тоже можно почитать для лучшего понимания всего механизма.
Я напомню, что у Views также есть хуки препроцессов, где можно творить всё, что угодно с рядами данных. Но если вам проще делать это из Twig'а - выше написал уже о расширениях Twig.
Спасибо больше за обратную связь. Решил вопрос благодаря Twig Extensions, проще говоря написал модуль,который добавляет в twig функцию (расширение), при обращении к которой работает нужная мне логика подсчёта. Мне кажется для моей задачи это наиболее подходящий вариант.
В админке все выглядит вот так:
В коде ноды типа материала "Сотрудник" вывожу так:
<?php
{# Проверяем на пустоту поле даты #}
{% if node.field_worker_pfr_date is not empty %}
{# Если дата не пустая, создаём переменные с нужным значением, которое посчитает наша функция #}
{% set stag_result = workerexperience(node.field_worker_pfr_date.value, node.field_worker_duration_years.value, node.field_worker_duration_months.value, 1) %}
{% set stag_spec_result = workerexperience(node.field_worker_pfr_date.value, node.field_worker_duration_years_spec.value, node.field_worker_duration_month_spec.value, node.field_worker_duration_spec_now.value) %}
Стаж работы общий:
{{ stag_result }}
{% if stag_spec_result %}
Стаж работы по специальности:
{{ stag_spec_result }}
{% endif %}
{% endif %}
?>Готовый результат выглядит так:
Логика работы:
Для добавления информации сотрудник запрашивает справку на госуслугах о своём стаже (Сведения о состоянии индивидуального лицевого счета в ПФР), справка выдаётся за 10 минут. Далее модератор вводит дату её выдачи и сам стаж из справки в формате год-месяц.
Согласно требованиям к сайту вузов и всяких обр. учреждений, стаж нужно указывать как общий, так и отдельно по специальности. Поэтому есть ещё два поля, в которые сотрудник отдельно добавляет этот вид стажа. Посчитать его может только он сам. Также предусмотрена галочка на случай, если текущая работа сотрудника также должна учитываться как работа по специальности - тогда стаж этого типа тоже будет прибавляться. После добавления информации считаем сколько прошло времени от даты справки до текущей даты и прибавляем это значение к указанном стужу. Выводим, также не забывая про правильное склонение год, года, лет.
Имена полей в моём примере:
field_worker_pfr_date - Дата выдачи справки о стаже (поле даты)
field_worker_duration_years - Общий стаж лет (целое число)
field_worker_duration_months - Общий стаж месяцев (целое число)
field_worker_duration_years_spec - Стаж лет по специальности (целое число)
field_worker_duration_month_spec - Стаж месяцев по специальности (целое число)
field_worker_duration_spec_now - Работает сейчас по специальности или нет? (чекбокс, булево значение)
Чтобы написать расширение, я взял за основу стандартный пример, который есть в модулях ядра специально для этих нужд: /core/modules/system/tests/modules/twig_extension_test и перенес к себе в custom модули с нужным названием. Далее туда вставил свою функцию и теперь все работает как мне нужно.
Единственное, подскажите как убрать ненужный функционал?
1) Как убрать из этого кода ненужную мне возможность отдельного шаблона? (папка templates)
2) Как убрать ненужную мне возможность создания пути вида "/dpsh-employee-experience/function"? Как понял за это отвечает файл routing.yml - достаточно просто удалить его?
Модуль: https://disk.yandex.ru/d/8JvW3AGNcpfa7A (знаю, что в git было бы удобнее)
Ответ в следующем: нужно удалить route-файл и папку templates, больше ничего делать не нужно
Вам не нужно делать никаких функций twig. Ваша задача - вывести значение поля особым способом, соответственно решать эту задачу нужно с помощью создания форматтера поля и никак иначе. Так вы сможете использовать его и в сущностях, и во вьюсах.
Кстати, да! Это наиболее гибкий подход. Вообще не подумал об этом. Форматтер удобнее во всех смыслах, конечно.
Единственное "но": если нужно в шаблоне параллельно выводить и оригинальное значение без калькуляции, то это уже некоторая проблема. Навскидку не помню, можно ли в Twig'e вывести поле с другим форматтером.
Почитал: https://www.codimth.com/blog/web/drupal/how-create-custom-field-formatte...
Все-таки в моём случае не поле, а несколько полей. Как понял форматтер - это способ вывода одного поля, т.е. то что я выбираю в настройках режима отображения. Я могу добавлять эти режимы, либо просто могу переопределить шаблон того или иного поля для решения подобных задач?
У вас там два поля даты. Но выводите же вы их по раздельности. Поэтому форматтер как раз подходит.
Дано: дата рождения работника, поле field_per_bdate
Требуется вывести полный возраст работника по состоянию на 31.12.2023
Вывести столбцом (полем) во вьюсе.
Решение: во вьюсе в настройке поля field_per_bdate в графе "Перезаписать результат" поместить следующий twig-скрипт:
{% set startDate = field_per_bdate_1__value %}
{% set endDate = "2023-12-31" %}
{% set difference = endDate|date('U') - startDate|date('U') %}
{% set differenceYears = (difference / 60 / 60 / 24 / 365.25)|round(0, 'floor') %}
{{ differenceYears }}