Skip to content

Commit edbd0d6

Browse files
committed
Add tryFromString and tryFromFloat methods for float types with accompanying tests
- Implemented `tryFromString` and `tryFromFloat` methods in `FloatStandard`, `FloatNonNegative`, and `FloatPositive` to return `Undefined` on invalid input. - Updated `FloatTypeInterface` to include declarations for new methods. - Refactored exception handling for string and float parsing logic. - Added comprehensive unit tests to validate behavior of new methods for valid and invalid cases.
1 parent 6b82395 commit edbd0d6

File tree

8 files changed

+229
-1
lines changed

8 files changed

+229
-1
lines changed

src/Abstract/Float/FloatTypeInterface.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace PhpTypedValues\Abstract\Float;
66

7+
use PhpTypedValues\Undefined\Alias\Undefined;
8+
79
/**
810
* @psalm-immutable
911
*/
@@ -17,5 +19,9 @@ public function toString(): string;
1719

1820
public static function fromString(string $value): static;
1921

22+
public static function tryFromString(string $value): static|Undefined;
23+
24+
public static function tryFromFloat(float $value): static|Undefined;
25+
2026
public function __toString(): string;
2127
}

src/Float/FloatNonNegative.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use PhpTypedValues\Abstract\Float\FloatType;
88
use PhpTypedValues\Exception\FloatTypeException;
9+
use PhpTypedValues\Exception\TypeException;
10+
use PhpTypedValues\Undefined\Alias\Undefined;
911

1012
use function sprintf;
1113

@@ -26,7 +28,7 @@
2628
public function __construct(float $value)
2729
{
2830
if ($value < 0) {
29-
throw new FloatTypeException(sprintf('Expected non-negative float, got "%d"', $value));
31+
throw new FloatTypeException(sprintf('Expected non-negative float, got "%s"', $value));
3032
}
3133

3234
$this->value = $value;
@@ -40,6 +42,24 @@ public static function fromFloat(float $value): static
4042
return new static($value);
4143
}
4244

45+
public static function tryFromString(string $value): static|Undefined
46+
{
47+
try {
48+
return static::fromString($value);
49+
} catch (TypeException) {
50+
return Undefined::create();
51+
}
52+
}
53+
54+
public static function tryFromFloat(float $value): static|Undefined
55+
{
56+
try {
57+
return static::fromFloat($value);
58+
} catch (TypeException) {
59+
return Undefined::create();
60+
}
61+
}
62+
4363
/**
4464
* @throws FloatTypeException
4565
*/

src/Float/FloatPositive.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use PhpTypedValues\Abstract\Float\FloatType;
88
use PhpTypedValues\Exception\FloatTypeException;
9+
use PhpTypedValues\Exception\TypeException;
10+
use PhpTypedValues\Undefined\Alias\Undefined;
911

1012
use function sprintf;
1113

@@ -40,6 +42,24 @@ public static function fromFloat(float $value): static
4042
return new static($value);
4143
}
4244

45+
public static function tryFromString(string $value): static|Undefined
46+
{
47+
try {
48+
return static::fromString($value);
49+
} catch (TypeException) {
50+
return Undefined::create();
51+
}
52+
}
53+
54+
public static function tryFromFloat(float $value): static|Undefined
55+
{
56+
try {
57+
return static::fromFloat($value);
58+
} catch (TypeException) {
59+
return Undefined::create();
60+
}
61+
}
62+
4363
/**
4464
* @throws FloatTypeException
4565
*/

src/Float/FloatStandard.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use PhpTypedValues\Abstract\Float\FloatType;
88
use PhpTypedValues\Exception\FloatTypeException;
9+
use PhpTypedValues\Exception\TypeException;
10+
use PhpTypedValues\Undefined\Alias\Undefined;
911

1012
/**
1113
* Represents any PHP float (double).
@@ -28,6 +30,20 @@ public static function fromFloat(float $value): static
2830
return new static($value);
2931
}
3032

33+
public static function tryFromString(string $value): static|Undefined
34+
{
35+
try {
36+
return static::fromString($value);
37+
} catch (TypeException) {
38+
return Undefined::create();
39+
}
40+
}
41+
42+
public static function tryFromFloat(float $value): static|Undefined
43+
{
44+
return static::fromFloat($value);
45+
}
46+
3147
/**
3248
* @throws FloatTypeException
3349
*/

src/Usage/Float.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use PhpTypedValues\Float\Alias\NonNegativeFloat;
88
use PhpTypedValues\Float\Alias\PositiveFloat;
99
use PhpTypedValues\Float\FloatNonNegative;
10+
use PhpTypedValues\Float\FloatPositive;
1011
use PhpTypedValues\Float\FloatStandard;
12+
use PhpTypedValues\Undefined\Alias\Undefined;
1113

1214
/**
1315
* Float.
@@ -24,6 +26,22 @@
2426
testPositiveFloat(FloatNonNegative::fromFloat(0.5)->value());
2527
echo FloatNonNegative::fromString('3.14159')->toString() . \PHP_EOL;
2628

29+
// try* usages to satisfy Psalm (ensure both success and failure branches are referenced)
30+
$ts = FloatStandard::tryFromString('1.23');
31+
if (!($ts instanceof Undefined)) {
32+
echo $ts->toString() . \PHP_EOL;
33+
}
34+
35+
$ti = FloatPositive::tryFromFloat(2.2);
36+
if (!($ti instanceof Undefined)) {
37+
echo $ti->toString() . \PHP_EOL;
38+
}
39+
40+
$tn = FloatNonNegative::tryFromString('-1'); // will likely be Undefined
41+
if (!($tn instanceof Undefined)) {
42+
echo $tn->toString() . \PHP_EOL;
43+
}
44+
2745
/**
2846
* Artificial functions.
2947
*/
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpTypedValues\Exception\FloatTypeException;
6+
use PhpTypedValues\Float\FloatNonNegative;
7+
use PhpTypedValues\Undefined\Alias\Undefined;
8+
9+
it('FloatNonNegative::tryFromString returns value for >= 0.0 and Undefined otherwise', function (): void {
10+
$ok0 = FloatNonNegative::tryFromString('0');
11+
$ok = FloatNonNegative::tryFromString('0.5');
12+
$bad = FloatNonNegative::tryFromString('-0.1');
13+
$badStr = FloatNonNegative::tryFromString('abc');
14+
15+
expect($ok0)
16+
->toBeInstanceOf(FloatNonNegative::class)
17+
->and($ok0->value())->toBe(0.0)
18+
->and($ok)
19+
->toBeInstanceOf(FloatNonNegative::class)
20+
->and($ok->value())->toBe(0.5)
21+
->and($bad)->toBeInstanceOf(Undefined::class)
22+
->and($badStr)->toBeInstanceOf(Undefined::class);
23+
});
24+
25+
it('FloatNonNegative::tryFromFloat returns value for >= 0 and Undefined otherwise', function (): void {
26+
$ok = FloatNonNegative::tryFromFloat(0);
27+
$bad = FloatNonNegative::tryFromFloat(-1);
28+
29+
expect($ok)
30+
->toBeInstanceOf(FloatNonNegative::class)
31+
->and($ok->value())
32+
->toBe(0.0)
33+
->and($bad)
34+
->toBeInstanceOf(Undefined::class);
35+
});
36+
37+
it('FloatNonNegative throws on negative values in ctor and fromFloat', function (): void {
38+
expect(fn() => new FloatNonNegative(-0.1))
39+
->toThrow(FloatTypeException::class, 'Expected non-negative float, got "-0.1"')
40+
->and(fn() => FloatNonNegative::fromFloat(-1.0))
41+
->toThrow(FloatTypeException::class, 'Expected non-negative float, got "-1"');
42+
});
43+
44+
it('FloatNonNegative::fromString enforces numeric and non-negativity', function (): void {
45+
// Non-numeric
46+
expect(fn() => FloatNonNegative::fromString('abc'))
47+
->toThrow(FloatTypeException::class, 'String "abc" has no valid float value');
48+
49+
// Non-negativity
50+
expect(fn() => FloatNonNegative::fromString('-0.5'))
51+
->toThrow(FloatTypeException::class, 'Expected non-negative float, got "-0.5"');
52+
53+
// Success path
54+
$v = FloatNonNegative::fromString('0.75');
55+
expect($v->value())->toBe(0.75);
56+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpTypedValues\Exception\FloatTypeException;
6+
use PhpTypedValues\Float\FloatPositive;
7+
use PhpTypedValues\Undefined\Alias\Undefined;
8+
9+
it('FloatPositive::tryFromString returns value for > 0.0 and Undefined otherwise', function (): void {
10+
$ok = FloatPositive::tryFromString('0.1');
11+
$badZero = FloatPositive::tryFromString('0');
12+
$badNeg = FloatPositive::tryFromString('-0.1');
13+
$badStr = FloatPositive::tryFromString('abc');
14+
15+
expect($ok)
16+
->toBeInstanceOf(FloatPositive::class)
17+
->and($ok->value())->toBe(0.1)
18+
->and($badZero)->toBeInstanceOf(Undefined::class)
19+
->and($badNeg)->toBeInstanceOf(Undefined::class)
20+
->and($badStr)->toBeInstanceOf(Undefined::class);
21+
});
22+
23+
it('FloatPositive::tryFromFloat returns value for positive int and Undefined otherwise', function (): void {
24+
$ok = FloatPositive::tryFromFloat(2);
25+
$bad = FloatPositive::tryFromFloat(0);
26+
27+
expect($ok)
28+
->toBeInstanceOf(FloatPositive::class)
29+
->and($ok->value())
30+
->toBe(2.0)
31+
->and($bad)
32+
->toBeInstanceOf(Undefined::class);
33+
});
34+
35+
it('FloatPositive throws on non-positive values in ctor and fromFloat', function (): void {
36+
expect(fn() => new FloatPositive(0.0))
37+
->toThrow(FloatTypeException::class, 'Expected positive float, got "0"')
38+
->and(fn() => FloatPositive::fromFloat(-1.0))
39+
->toThrow(FloatTypeException::class, 'Expected positive float, got "-1"');
40+
});
41+
42+
it('FloatPositive::fromString enforces numeric and positivity', function (): void {
43+
// Non-numeric
44+
expect(fn() => FloatPositive::fromString('abc'))
45+
->toThrow(FloatTypeException::class, 'String "abc" has no valid float value');
46+
47+
// Positivity
48+
expect(fn() => FloatPositive::fromString('0'))
49+
->toThrow(FloatTypeException::class, 'Expected positive float, got "0"');
50+
51+
// Success path
52+
$v = FloatPositive::fromString('1.25');
53+
expect($v->value())->toBe(1.25);
54+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpTypedValues\Exception\FloatTypeException;
6+
use PhpTypedValues\Float\FloatStandard;
7+
use PhpTypedValues\Undefined\Alias\Undefined;
8+
9+
it('FloatStandard::tryFromString returns value on valid float string', function (): void {
10+
$v = FloatStandard::tryFromString('1.5');
11+
12+
expect($v)
13+
->toBeInstanceOf(FloatStandard::class)
14+
->and($v->value())
15+
->toBe(1.5)
16+
->and($v->toString())
17+
->toBe('1.5');
18+
});
19+
20+
it('FloatStandard::tryFromString returns Undefined on invalid float string', function (): void {
21+
$v = FloatStandard::tryFromString('abc');
22+
23+
expect($v)->toBeInstanceOf(Undefined::class);
24+
});
25+
26+
it('FloatStandard::tryFromFloat returns value for any int', function (): void {
27+
$v = FloatStandard::tryFromFloat(2);
28+
29+
expect($v)
30+
->toBeInstanceOf(FloatStandard::class)
31+
->and($v->value())
32+
->toBe(2.0);
33+
});
34+
35+
it('FloatStandard::fromString throws on non-numeric strings', function (): void {
36+
expect(fn() => FloatStandard::fromString('NaN'))
37+
->toThrow(FloatTypeException::class, 'String "NaN" has no valid float value');
38+
});

0 commit comments

Comments
 (0)