Null Object - поведінка за замовчуванням
Замінює null безпечним об'єктом із тим самим інтерфейсом і "порожньою" поведінкою. Прибирає перевірки на null - і де він може приховати справжню проблему.
Почніть вводити, щоб шукати по статтях, вакансіях, компаніях, проєктах, ресурсах, пакетах, подіях і користувачах.
diffForHumans() з Carbon є майже в кожному Laravel-проєкті: «2 години тому» читається краще за повну дату. Але в нього є поведінка, про яку мало хто думає: він не округлює, а відкидає залишок. Подія 47 годин тому для нього - «1 день тому». На цю деталь нагадав Джоел Клермонт (Joel Clermont) у Mastering Laravel; нижче - як це працює і що з цим робити, з рядками, які Carbon справді повертає українською.
Carbon рахує різницю, бере найбільшу ненульову одиницю і показує тільки її. 47 годин - це 1 день і 23 години, тож лишається «1 день»:
now()->subHours(47)->diffForHumans();
// 1 день тому
now()->subHours(2)->subMinutes(59)->diffForHumans();
// 2 години тому
Тобто 2 години 59 хвилин - це «2 години», а не «3». Для часу коментаря чи посту така грубість нікому не шкодить. А от у рядку «Останній бекап: 1 день тому» на адмінці вона приховує, що бекапу насправді майже дві доби. Те саме з «останньою невдалою задачею» в черзі чи «останнім входом» користувача.
ROUND і CEILЯкщо потрібна одна одиниця, але чесна, передайте опцію округлення. Усі параметри йдуть масивом у перший аргумент (див. документацію Carbon):
use Carbon\CarbonInterface;
now()->subHours(47)->diffForHumans(['options' => CarbonInterface::ROUND]);
// 2 дні тому
now()->subHours(35)->diffForHumans(['options' => CarbonInterface::ROUND]);
// 1 день тому
now()->subHours(25)->diffForHumans(['options' => CarbonInterface::CEIL]);
// 2 дні тому
ROUND округлює до найближчого: 36 годин уже стають «2 дні», 35 ще лишаються «1 день». CEIL округлює завжди вгору - зручно там, де недооцінка гірша за переоцінку: «скільки часу без бекапу» краще трохи перебільшити, ніж применшити.
parts, short і joinКоли одна одиниця замало, попросіть кілька:
now()->subHours(47)->diffForHumans(['parts' => 2]);
// 1 день 23 години тому
now()->subDays(3)->subHours(5)->diffForHumans(['parts' => 3]);
// 3 дні 5 годин тому
parts - це максимум одиниць: якщо хвилин нуль, третьої частини не буде. Для таблиць і бейджів, де місця мало, є short:
now()->subHours(2)->subMinutes(30)->diffForHumans([
'parts' => 2,
'short' => true,
]);
// 2г 30хв тому
Опція join задає, чим склеювати частини. 'join' => true бере сполучник із перекладу локалі, а рядок чи масив [роздільник, останній роздільник] - ваш власний:
now()->subDays(3)->subHours(5)->subMinutes(7)->diffForHumans([
'parts' => 3,
'join' => [', ', ' і '],
]);
// 3 дні, 5 годин і 7 хвилин тому
Laravel передає Carbon свою локаль сам: пакет nesbot/carbon реєструє сервіс-провайдер, який при старті бере config('app.locale'), а потім слухає подію LocaleUpdated. Тому достатньо 'locale' => 'uk' у config/app.php або app()->setLocale('uk') у middleware. Поза Laravel - Carbon::setLocale('uk') або ->locale('uk') на конкретному об'єкті.
Відмінки українська локаль Carbon 3 обробляє правильно: «1 годину тому», «21 годину тому», «2 години тому», «5 годин тому». Є й CarbonInterface::ONE_DAY_WORDS, з яким різниця в один день стає «вчора».
Одна несподіванка - у 'join' => true. Сполучник у файлі перекладу uk записано латинською i, а не українською і:
now()->subHours(2)->subMinutes(30)->diffForHumans([
'parts' => 2,
'join' => true,
]);
// 2 години i 30 хвилин тому <- тут латинська "i"
На око різниці немає, але пошук сторінкою, перевірка правопису і точне порівняння рядків у тесті її помітять. Якщо вам потрібен сполучник, передайте його явно масивом, як у прикладі вище. (Перевірено на Carbon 3.15.1.)
Відносний час залежить від «зараз», тож без замороженого годинника тест на нього або нестабільний, або нічого не перевіряє. У Laravel для цього є travelTo() (або Carbon::setTestNow() поза тестовим класом Laravel):
use Carbon\CarbonInterface;
use Illuminate\Support\Carbon;
public function test_last_backup_is_rounded(): void
{
$this->travelTo(Carbon::parse('2026-10-30 12:00'));
app()->setLocale('uk');
$finishedAt = now()->subHours(47);
$this->assertSame(
'2 дні тому',
$finishedAt->diffForHumans(['options' => CarbonInterface::ROUND]),
);
}
Такий тест ловить і зміну опцій, і сюрпризи при оновленні Carbon, бо файли перекладів у ньому теж змінюються.
ROUND або CEIL, залежно від того, яка помилка дешевша.'parts' => 2 разом із short.Перед тим як додати diffForHumans() у новий екран, варто на секунду спитати себе, що побачить людина, яка прийде туди з'ясовувати, коли щось востаннє спрацювало.
Замінює null безпечним об'єктом із тим самим інтерфейсом і "порожньою" поведінкою. Прибирає перевірки на null - і де він може приховати справжню проблему.
Пропускає дані через серію кроків, де кожен трансформує вхід і передає далі. Готовий Illuminate\Pipeline\Pipeline та його зв'язок із middleware.
Senior PHP-розробник та архітектор для розвитку й модернізації платформи публічних закупівель з фінансовими процесами та інтеграціями. Роль поєднує проєктування складних backend-систем, рефакторинг legacy-компонентів і безпосередню розробку на PHP 8+ із Symfony або Laravel. Ключові вимоги: 10+ років PHP-розробки, 5+ років у FinTech, активне використання AI-інструментів у розробці, досвід REST API, Redis/RabbitMQ, Docker і CI/CD.
Розробник бекенду на PHP, який інтегрує внутрішні та зовнішні системи, проєктує й оптимізує складні SQL-запити та бере участь у плануванні архітектури сервісів. Потрібен досвід комерційної розробки на PHP 7.x/8.x від 4 років, робота з Symfony та/або Laravel, REST API, PostgreSQL/MySQL, MongoDB/Redis, RabbitMQ та Elasticsearch, а також глибоке знання OOP, SOLID і проєктування індексів.
Розробка та підтримка backend-сервісів на PHP: новий функціонал, REST API, інтеграції зі сторонніми сервісами, фонові завдання та черги. Ключовий фокус на PostgreSQL: проєктування схем, оптимізація складних SQL-запитів і індексів, аналіз продуктивності через EXPLAIN ANALYZE. Вимоги: від 4 років комерційного досвіду з PHP 8.x, фреймворки Laravel, Symfony або Yii2, робота зі складною бізнес-логікою, тести та Code Review.