Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
95.45% covered (success)
95.45%
42 / 44
71.43% covered (warning)
71.43%
20 / 28
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
FloatingPoint
100.00% covered (success)
100.00%
25 / 25
95.45% covered (success)
95.45%
42 / 44
71.43% covered (warning)
71.43%
20 / 28
100.00% covered (success)
100.00%
7 / 7
31.29
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%
5 / 5
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 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 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 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        if ($value === null) {
65            return null;
66        }
67
68        return self::isFloatCastable($value)
69            ? self::finiteFloat((float) $value)
70            : null;
71    }
72
73    private static function isFloatCastable(mixed $value): bool
74    {
75        return is_int($value) || is_float($value) || is_string($value) && is_numeric($value);
76    }
77
78    private static function finiteFloat(float $value): ?float
79    {
80        return is_finite($value) ? $value : null;
81    }
82}