From 8af7a8dea27bde8a384f03600198ba1694e171c1 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Wed, 15 Oct 2025 17:59:06 +0900 Subject: [PATCH 1/2] =?UTF-8?q?LocalDate=20=E5=80=A4=E3=82=AA=E3=83=96?= =?UTF-8?q?=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=81=AE=20min/max=20?= =?UTF-8?q?=E3=82=92=E3=82=AA=E3=83=BC=E3=83=90=E3=83=BC=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=89=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B=20Fixes=20#65?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DateTime/LocalDate.php | 98 ++++++++++++++++++++++++--- tests/Unit/DateTime/LocalDateTest.php | 19 ++++++ 2 files changed, 107 insertions(+), 10 deletions(-) diff --git a/src/DateTime/LocalDate.php b/src/DateTime/LocalDate.php index 3ae367e..cc16b12 100644 --- a/src/DateTime/LocalDate.php +++ b/src/DateTime/LocalDate.php @@ -31,11 +31,75 @@ */ final public const int MIN_YEAR = -9999; + /** + * NOTE: 実装クラスでのオーバーライド用メソッド + */ + protected static function minYear(): int + { + return self::MIN_YEAR; + } + + final public const int MIN_MONTH = 1; + + /** + * NOTE: 実装クラスでのオーバーライド用メソッド + * + * @return int<1,12> + */ + protected static function minMonth(): int + { + return self::MIN_MONTH; + } + + final public const int MIN_DAY = 1; + + /** + * NOTE: 実装クラスでのオーバーライド用メソッド + * + * @return int<1,31> + */ + protected static function minDay(): int + { + return self::MIN_DAY; + } + /** * The maximum supported year for instances of `LocalDate`. */ final public const int MAX_YEAR = 9999; + /** + * NOTE: 実装クラスでのオーバーライド用メソッド + */ + protected static function maxYear(): int + { + return self::MAX_YEAR; + } + + final public const int MAX_MONTH = 12; + + /** + * NOTE: 実装クラスでのオーバーライド用メソッド + * + * @return int<1,12> + */ + protected static function maxMonth(): int + { + return self::MAX_MONTH; + } + + final public const int MAX_DAY = 31; + + /** + * NOTE: 実装クラスでのオーバーライド用メソッド + * + * @return int<1,31> + */ + protected static function maxDay(): int + { + return self::MAX_DAY; + } + /** * The number of days from year zero to year 1970. */ @@ -162,7 +226,12 @@ final public static function now(DateTimeZone $timeZone = new DateTimeZone('Asia final public static function max(): static { - return static::of(self::MAX_YEAR, 12, 31); + return static::of(static::maxYear(), static::maxMonth(), static::maxDay()); + } + + final public static function min(): static + { + return static::of(static::minYear(), static::minMonth(), static::minDay()); } /** @@ -216,14 +285,17 @@ final public static function ofEpochDay(int $epochDay): static */ final protected static function isValidYear(int $year): Result { - if ($year < self::MIN_YEAR || $year > self::MAX_YEAR) { + $minYear = static::minYear() > self::MIN_YEAR ? static::minYear() : self::MIN_YEAR; + $maxYear = static::maxYear() < self::MAX_YEAR ? static::maxYear() : self::MAX_YEAR; + + if ($year < $minYear || $year > $maxYear) { return Result\err( ValueObjectError::dateTime()->invalidRange( className: static::class, attributeName: '年', value: (string)$year, - minValue: (string)self::MIN_YEAR, - maxValue: (string)self::MAX_YEAR, + minValue: (string)$minYear, + maxValue: (string)$maxYear, ) ); } @@ -237,14 +309,17 @@ className: static::class, */ final protected static function isValidMonth(int $month): Result { - if ($month < 1 || $month > 12) { + $minMonth = static::minMonth() > self::MIN_MONTH ? static::minMonth() : self::MIN_MONTH; + $maxMonth = static::maxMonth() < self::MAX_MONTH ? static::maxMonth() : self::MAX_MONTH; + + if ($month < $minMonth || $month > $maxMonth) { return Result\err( ValueObjectError::dateTime()->invalidRange( className: static::class, attributeName: '月', value: (string)$month, - minValue: '1', - maxValue: '12', + minValue: (string)$minMonth, + maxValue: (string)$maxMonth, ) ); } @@ -258,14 +333,17 @@ className: static::class, */ final protected static function isValidDay(int $day): Result { - if ($day < 1 || $day > 31) { + $minDay = static::minDay() > self::MIN_DAY ? static::minDay() : self::MIN_DAY; + $maxDay = static::maxDay() < self::MAX_DAY ? static::maxDay() : self::MAX_DAY; + + if ($day < $minDay || $day > $maxDay) { return Result\err( ValueObjectError::dateTime()->invalidRange( className: static::class, attributeName: '日', value: (string)$day, - minValue: '1', - maxValue: '31', + minValue: (string)$minDay, + maxValue: (string)$maxDay, ) ); } diff --git a/tests/Unit/DateTime/LocalDateTest.php b/tests/Unit/DateTime/LocalDateTest.php index 457b840..9854519 100644 --- a/tests/Unit/DateTime/LocalDateTest.php +++ b/tests/Unit/DateTime/LocalDateTest.php @@ -440,4 +440,23 @@ public function privateコンストラクタへのアクセスを試みるとエ /** @phpstan-ignore-next-line */ $date = new LocalDate(2023, 5, 15); } + + #[Test] + public function maxメソッドで正常にインスタンスが作成できる(): void + { + $date = LocalDate::max(); + + $this->assertSame(LocalDate::MAX_YEAR, $date->getYear()); + $this->assertSame(LocalDate::MAX_MONTH, $date->getMonth()); + $this->assertSame(LocalDate::MAX_DAY, $date->getDay()); + } + + #[Test] + public function minメソッドで正常にインスタンスが作成できる(): void + { + $date = LocalDate::min(); + $this->assertSame(LocalDate::MIN_YEAR, $date->getYear()); + $this->assertSame(LocalDate::MIN_MONTH, $date->getMonth()); + $this->assertSame(LocalDate::MIN_DAY, $date->getDay()); + } } From b2deb50d18616cac097f7648cba1743d43131b70 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Wed, 15 Oct 2025 18:10:11 +0900 Subject: [PATCH 2/2] =?UTF-8?q?PHPStan=E3=82=A8=E3=83=A9=E3=83=BC=E8=A7=A3?= =?UTF-8?q?=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Collection/ArrayList.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index 3a70cad..a6ac83e 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -532,11 +532,12 @@ final public function mapToDictionary(Closure $closure): array * @template TMapToGroupsValue * * @param Closure(TValue, int): array $closure - * @return array> + * @return array> */ #[Override] final public function mapToGroups(Closure $closure): array { + /** @var array> $groups */ $groups = $this->mapToDictionary($closure); return array_map(static fn ($items) => new self($items), $groups);