Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
95.24% covered (success)
95.24%
40 / 42
70.37% covered (warning)
70.37%
19 / 27
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
FloatingPoint
100.00% covered (success)
100.00%
23 / 23
95.24% covered (success)
95.24%
40 / 42
70.37% covered (warning)
70.37%
19 / 27
100.00% covered (success)
100.00%
7 / 7
30.40
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%
8 / 8
80.00% covered (warning)
80.00%
4 / 5
100.00% covered (success)
100.00%
1 / 1
4.13
 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
 toFloat
100.00% covered (success)
100.00%
3 / 3
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
 isFloatCastable
100.00% covered (success)
100.00%
1 / 1
90.00% covered (success)
90.00%
9 / 10
50.00% covered (danger)
50.00%
4 / 8
100.00% covered (success)
100.00%
1 / 1
6.00
 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 Celema\Sire\Coercer;
6
7use Celema\Sire\Coercion;
8use Celema\Sire\CoercionMode;
9use Celema\Sire\Contract;
10use Celema\Sire\Failure;
11use Override;
12
13/** @api */
14final class FloatingPoint 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::toFloat($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_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 toFloat(mixed $value): ?float
63    {
64        return self::isFloatCastable($value)
65            ? self::finiteFloat((float) $value)
66            : null;
67    }
68
69    /** @psalm-assert-if-true int|float|numeric-string $value */
70    private static function isFloatCastable(mixed $value): bool
71    {
72        return is_int($value) || is_float($value) || is_string($value) && is_numeric($value);
73    }
74
75    private static function finiteFloat(float $value): ?float
76    {
77        return is_finite($value) ? $value : null;
78    }
79}