From debcd0c7ac02c2eee8da15b24f598d90523dedaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=97=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD?= Date: Tue, 14 Jul 2026 20:48:55 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B5=D0=B4=D0=B8=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=20modify=20=D0=B4=D0=BB=D1=8F=20=D0=B2?= =?UTF-8?q?=D1=81=D0=B5=D1=85=20=D1=8D=D0=BF=D0=BE=D1=85=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D0=B2=20=D0=B0=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Тип возврата родного modify различается между стабами PHP 8.1 и 8.3+ (union с false против чистого момента), из-за чего строгий анализ не проходил одновременно на краях матрицы: на новых стабах union объявлялся избыточным, на старых был обязателен. Сырой вызов проведён через рефлексию с mixed-результатом и обязательной проверкой на выходе: обе ветки провала честно достижимы и покрыты на любой версии, путь не горячий. Мутационное тестирование: 202 из 202 без выживших. --- src/TimeShift.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/TimeShift.php b/src/TimeShift.php index fce8b61..8a2f53b 100644 --- a/src/TimeShift.php +++ b/src/TimeShift.php @@ -7,6 +7,7 @@ use CloudCastle\Clock\Exception\InvalidClockValueException; use DateMalformedStringException; use DateTimeImmutable; +use ReflectionMethod; /** * Применение строки-модификатора к моменту с полной валидацией. @@ -46,7 +47,7 @@ public static function apply(DateTimeImmutable $moment, string $modifier): DateT throw InvalidClockValueException::invalidModifier($modifier); } - if ($shifted === false) { + if (!$shifted instanceof DateTimeImmutable) { throw InvalidClockValueException::invalidModifier($modifier); } @@ -54,15 +55,18 @@ public static function apply(DateTimeImmutable $moment, string $modifier): DateT } /** - * Сырой вызов `modify()` с широкой сигнатурой. + * Сырой вызов `modify()` с непрозрачным для анализаторов результатом. * - * До PHP 8.3 провал возвращает `false`, с 8.3 — бросает - * {@see \DateMalformedStringException}; широкий тип возврата делает - * обработку обоих каналов корректной для анализаторов на всей - * матрице PHP 8.1–8.5. + * Тип возврата родного `modify()` различается между эпохами стабов + * (до PHP 8.3 — `DateTimeImmutable|false`, с 8.3 — только момент, + * хотя tentative-совместимые наследники могут вернуть `false` на любой + * версии), поэтому прямой вызов не проходит строгий анализ одновременно + * на всей матрице 8.1–8.5. Рефлексия возвращает `mixed`: вызывающий + * обязан проверить результат, и ветка провала честно достижима везде. + * Путь не горячий — сюда попадают только строковые модификаторы. */ - private static function rawModify(DateTimeImmutable $moment, string $modifier): DateTimeImmutable|false + private static function rawModify(DateTimeImmutable $moment, string $modifier): mixed { - return $moment->modify($modifier); + return (new ReflectionMethod($moment, 'modify'))->invoke($moment, $modifier); } }