Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
31 / 31
94.23% covered (success)
94.23%
49 / 52
67.65% covered (warning)
67.65%
23 / 34
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Number
100.00% covered (success)
100.00%
31 / 31
94.23% covered (success)
94.23%
49 / 52
67.65% covered (warning)
67.65%
23 / 34
100.00% covered (success)
100.00%
8 / 8
43.51
100.00% covered (success)
100.00%
1 / 1
 coerce
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
7 / 7
80.00% covered (warning)
80.00%
4 / 5
100.00% covered (success)
100.00%
1 / 1
4.13
 coerceStrict
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
10 / 10
71.43% covered (warning)
71.43%
5 / 7
100.00% covered (success)
100.00%
1 / 1
5.58
 invalid
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isEmpty
100.00% covered (success)
100.00%
1 / 1
87.50% covered (warning)
87.50%
7 / 8
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
 toNumber
100.00% covered (success)
100.00%
7 / 7
92.31% covered (success)
92.31%
12 / 13
50.00% covered (danger)
50.00%
5 / 10
100.00% covered (success)
100.00%
1 / 1
8.12
 isNumericString
100.00% covered (success)
100.00%
1 / 1
83.33% covered (warning)
83.33%
5 / 6
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
2.15
 fromNumericString
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 finiteFloat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Sire\Coercer;
6
7use Celemas\Sire\Coercion;
8use Celemas\Sire\CoercionMode;
9use Celemas\Sire\Contract;
10use Celemas\Sire\Failure;
11use Override;
12
13/** @api */
14final class Number implements Contract\Coercer
15{
16    public string $message {
17        get => '{label} must be a number';
18    }
19
20    #[Override]
21    public function coerce(mixed $pristine, CoercionMode $mode): Contract\Coercion
22    {
23        if ($mode === CoercionMode::Strict) {
24            return self::coerceStrict($pristine);
25        }
26
27        $value = self::toNumber($pristine);
28
29        if ($value === null && $pristine !== null) {
30            return self::invalid($pristine);
31        }
32
33        return new Coercion($value, $pristine, empty: $value === null);
34    }
35
36    private static function coerceStrict(mixed $pristine): Coercion
37    {
38        if ($pristine === null) {
39            return new Coercion(null, null, empty: true);
40        }
41
42        return is_int($pristine) || is_float($pristine) && is_finite($pristine)
43            ? new Coercion($pristine, $pristine)
44            : self::invalid($pristine);
45    }
46
47    private static function invalid(mixed $pristine): Coercion
48    {
49        return new Coercion(
50            $pristine,
51            $pristine,
52            Failure::invalid(),
53            empty: self::isEmpty($pristine),
54        );
55    }
56
57    private static function isEmpty(mixed $value): bool
58    {
59        return $value === null || is_string($value) && trim($value) === '';
60    }
61
62    private static function toNumber(mixed $value): int|float|null
63    {
64        if ($value === null || is_int($value)) {
65            return $value;
66        }
67
68        if (is_float($value)) {
69            return self::finiteFloat($value);
70        }
71
72        return self::isNumericString($value)
73            ? self::fromNumericString(trim($value))
74            : null;
75    }
76
77    private static function isNumericString(mixed $value): bool
78    {
79        return is_string($value) && is_numeric($value);
80    }
81
82    private static function fromNumericString(string $value): int|float|null
83    {
84        $integer = filter_var($value, FILTER_VALIDATE_INT);
85
86        if ($integer !== false) {
87            return $integer;
88        }
89
90        return self::finiteFloat((float) $value);
91    }
92
93    private static function finiteFloat(float $value): ?float
94    {
95        return is_finite($value) ? $value : null;
96    }
97}