Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
BirthDate
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
6 / 6
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 fromDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromNullable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 age
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 toDateTimeImmutable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 value
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Identity\Domain\ValueObject;
6
7use App\Shared\Domain\ValueObject\IsoDate;
8use DateTimeImmutable;
9use InvalidArgumentException;
10
11/**
12 * A user's date of birth. We store the date, never a raw age: age drifts with
13 * time, so it is always derived from this value at read time via age().
14 */
15final class BirthDate
16{
17    private const MAX_AGE_YEARS = 150;
18
19    private readonly DateTimeImmutable $value;
20
21    public function __construct(string $date)
22    {
23        $value = IsoDate::parse($date);
24        $today = new DateTimeImmutable('today');
25
26        if ($value > $today) {
27            throw new InvalidArgumentException(sprintf('Birth date <%s> cannot be in the future.', $date));
28        }
29
30        if ($value < $today->modify('-' . self::MAX_AGE_YEARS . ' years')) {
31            throw new InvalidArgumentException(sprintf('Birth date <%s> is implausible.', $date));
32        }
33
34        $this->value = $value;
35    }
36
37    public static function fromDate(DateTimeImmutable $value): self
38    {
39        return new self($value->format('Y-m-d'));
40    }
41
42    /**
43     * Birth date is optional; a missing or empty value yields no BirthDate.
44     */
45    public static function fromNullable(?string $date): ?self
46    {
47        return $date === null || $date === '' ? null : new self($date);
48    }
49
50    public function age(?DateTimeImmutable $now = null): int
51    {
52        $now ??= new DateTimeImmutable('today');
53
54        return $this->value->diff($now)->y;
55    }
56
57    public function toDateTimeImmutable(): DateTimeImmutable
58    {
59        return $this->value;
60    }
61
62    public function value(): string
63    {
64        return $this->value->format('Y-m-d');
65    }
66}