Skip to content

Commit 05a4eee

Browse files
kylekatarnlsnicolas-grekas
authored andcommitted
[8.6] Add clamp function
1 parent 8495cbf commit 05a4eee

9 files changed

Lines changed: 358 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Polyfills are provided for:
7979
- the `NoDiscard` attribute introduced in PHP 8.5;
8080
- the `array_first` and `array_last` functions introduced in PHP 8.5;
8181
- the `DelayedTargetValidation` attribute introduced in PHP 8.5;
82+
- the `clamp` function introduced in PHP 8.6;
8283

8384
It is strongly recommended to upgrade your PHP version and/or install the missing
8485
extensions whenever possible. This polyfill should be used only when there is no

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"symfony/polyfill-php83": "self.version",
3535
"symfony/polyfill-php84": "self.version",
3636
"symfony/polyfill-php85": "self.version",
37+
"symfony/polyfill-php86": "self.version",
3738
"symfony/polyfill-iconv": "self.version",
3839
"symfony/polyfill-intl-grapheme": "self.version",
3940
"symfony/polyfill-intl-icu": "self.version",

src/Php86/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2025-present Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

src/Php86/Php86.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Polyfill\Php86;
13+
14+
/**
15+
* @author kylekatarnls <kylekatarnls@gmail.com>
16+
*
17+
* @internal
18+
*/
19+
final class Php86
20+
{
21+
/**
22+
* @template Value
23+
* @template Minimum
24+
* @template Maximum
25+
*
26+
* @param Value $value
27+
* @param Minimum $min
28+
* @param Maximum $max
29+
*
30+
* @return Value|Minimum|Maximum
31+
*/
32+
public static function clamp($value, $min, $max)
33+
{
34+
if (\is_float($min) && is_nan($min)) {
35+
self::throwValueErrorIfAvailable('clamp(): Argument #2 ($min) must not be NAN');
36+
}
37+
38+
if (\is_float($max) && is_nan($max)) {
39+
self::throwValueErrorIfAvailable('clamp(): Argument #3 ($max) must not be NAN');
40+
}
41+
42+
if ($max < $min) {
43+
self::throwValueErrorIfAvailable('clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)');
44+
}
45+
46+
if ($value > $max) {
47+
return $max;
48+
}
49+
50+
if ($value < $min) {
51+
return $min;
52+
}
53+
54+
return $value;
55+
}
56+
57+
private static function throwValueErrorIfAvailable(string $message): void
58+
{
59+
if (!class_exists(\ValueError::class)) {
60+
throw new \InvalidArgumentException($message);
61+
}
62+
63+
throw new \ValueError($message);
64+
}
65+
}

src/Php86/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Symfony Polyfill / Php86
2+
========================
3+
4+
This component provides features added to PHP 8.6 core:
5+
6+
- [`clamp`](https://wiki.php.net/rfc/clamp_v2)
7+
8+
More information can be found in the
9+
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
10+
11+
License
12+
=======
13+
14+
This library is released under the [MIT license](LICENSE).

src/Php86/bootstrap.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\Polyfill\Php86 as p;
13+
14+
if (\PHP_VERSION_ID >= 80600) {
15+
return;
16+
}
17+
18+
if (!function_exists('clamp')) {
19+
if (\PHP_VERSION_ID >= 80000) {
20+
/**
21+
* @template V
22+
* @template L
23+
* @template H
24+
*
25+
* @param V $value
26+
* @param L $min
27+
* @param H $max
28+
*
29+
* @return V|L|H
30+
*/
31+
function clamp(mixed $value, mixed $min, mixed $max): mixed { return p\Php86::clamp($value, $min, $max); }
32+
} else {
33+
/**
34+
* @template V
35+
* @template L
36+
* @template H
37+
*
38+
* @param V $value
39+
* @param L $min
40+
* @param H $max
41+
*
42+
* @return V|L|H
43+
*/
44+
function clamp($value, $min, $max) { return p\Php86::clamp($value, $min, $max); }
45+
}
46+
}

src/Php86/composer.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "symfony/polyfill-php86",
3+
"type": "library",
4+
"description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions",
5+
"keywords": ["polyfill", "shim", "compatibility", "portable"],
6+
"homepage": "https://symfony.com",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "Nicolas Grekas",
11+
"email": "p@tchwork.com"
12+
},
13+
{
14+
"name": "Symfony Community",
15+
"homepage": "https://symfony.com/contributors"
16+
}
17+
],
18+
"require": {
19+
"php": ">=7.2"
20+
},
21+
"autoload": {
22+
"psr-4": { "Symfony\\Polyfill\\Php86\\": "" },
23+
"files": [ "bootstrap.php" ]
24+
},
25+
"minimum-stability": "dev",
26+
"extra": {
27+
"thanks": {
28+
"name": "symfony/polyfill",
29+
"url": "https://github.com/symfony/polyfill"
30+
}
31+
}
32+
}

src/bootstrap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@
4040
if (\PHP_VERSION_ID < 80500) {
4141
require __DIR__.'/Php85/bootstrap.php';
4242
}
43+
44+
if (\PHP_VERSION_ID < 80600) {
45+
require __DIR__.'/Php86/bootstrap.php';
46+
}

tests/Php86/Php86Test.php

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Polyfill\Tests\Php86;
13+
14+
use PHPUnit\Framework\TestCase;
15+
16+
class Php86Test extends TestCase
17+
{
18+
/**
19+
* @dataProvider provideValidClampInput
20+
*/
21+
public function testClampSuccess(array $arguments, $result): void
22+
{
23+
[$value, $min, $max] = $arguments;
24+
25+
$actual = clamp($value, $min, $max);
26+
27+
if ($value instanceof \DateTimeImmutable) {
28+
$this->assertInstanceOf(\DateTimeImmutable::class, $actual);
29+
30+
$actual = $actual->format('Y-m-d');
31+
}
32+
33+
$this->assertSame($result, $actual);
34+
}
35+
36+
public function testClampNanReturn(): void
37+
{
38+
$this->assertTrue(is_nan(clamp(NAN, 4, 6)));
39+
}
40+
41+
public static function provideValidClampInput(): array
42+
{
43+
$a = new \InvalidArgumentException('a');
44+
$b = new \RuntimeException('b');
45+
$c = new \LogicException('c');
46+
47+
return [
48+
[
49+
[2, 1, 3],
50+
2,
51+
],
52+
[
53+
[0, 1, 3],
54+
1,
55+
],
56+
[
57+
[6, 1, 3],
58+
3,
59+
],
60+
[
61+
[2, 1.3, 3.4],
62+
2,
63+
],
64+
[
65+
[2.5, 1, 3],
66+
2.5,
67+
],
68+
[
69+
[2.5, 1.3, 3.4],
70+
2.5,
71+
],
72+
[
73+
[0, 1.3, 3.4],
74+
1.3,
75+
],
76+
[
77+
[M_PI, -INF, INF],
78+
M_PI,
79+
],
80+
[
81+
['a', 'c', 'g'],
82+
'c',
83+
],
84+
[
85+
['d', 'c', 'g'],
86+
'd',
87+
],
88+
[
89+
['2025-08-01', '2025-08-15', '2025-09-15'],
90+
'2025-08-15',
91+
],
92+
[
93+
['2025-08-20', '2025-08-15', '2025-09-15'],
94+
'2025-08-20',
95+
],
96+
[
97+
[new \DateTimeImmutable('2025-08-01'), new \DateTimeImmutable('2025-08-15'), new \DateTimeImmutable('2025-09-15')],
98+
'2025-08-15',
99+
],
100+
[
101+
[new \DateTimeImmutable('2025-08-20'), new \DateTimeImmutable('2025-08-15'), new \DateTimeImmutable('2025-09-15')],
102+
'2025-08-20',
103+
],
104+
[
105+
[null, -1, 1],
106+
-1,
107+
],
108+
[
109+
[null, 1, 3],
110+
1,
111+
],
112+
[
113+
[null, -3, -1],
114+
-3,
115+
],
116+
[
117+
[-9999, null, 10],
118+
-9999,
119+
],
120+
[
121+
[12, null, 10],
122+
10,
123+
],
124+
[
125+
[$a, $b, $c],
126+
$a,
127+
],
128+
[
129+
[$b, $a, $c],
130+
$b,
131+
],
132+
[
133+
[$c, $a, $b],
134+
$c,
135+
],
136+
];
137+
}
138+
139+
/**
140+
* @dataProvider provideInvalidClampInput
141+
*/
142+
public function testClampFailure(array $arguments, string $error): void
143+
{
144+
$this->expectException(\ValueError::class);
145+
$this->expectExceptionMessage($error);
146+
147+
[$value, $min, $max] = $arguments;
148+
clamp($value, $min, $max);
149+
}
150+
151+
public static function provideInvalidClampInput(): array
152+
{
153+
return [
154+
[
155+
[4, NAN, 6],
156+
'clamp(): Argument #2 ($min) must not be NAN',
157+
],
158+
[
159+
[7, 6, NAN],
160+
'clamp(): Argument #3 ($max) must not be NAN',
161+
],
162+
[
163+
[1, 3, 2],
164+
'clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)',
165+
],
166+
[
167+
[-9999, 5, null],
168+
'clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)',
169+
],
170+
[
171+
[12, -5, null],
172+
'clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max)',
173+
],
174+
];
175+
}
176+
}

0 commit comments

Comments
 (0)