====== PHP RFC: clamp ====== * Version: 2 * Date: 2025-08-21 * Author: kylekatarnls, kylekatarnls@gmail.com * Status: Under Discussion * Implementation: [[https://github.com/php/php-src/pull/19434]] ===== Introduction ===== ''clamp'' checks if a comparable value is within a certain bound. If the value is in range it returns the value, if the value is not in range it returns the nearest bound. This RFC is a continuation of [[https://wiki.php.net/rfc/clamp]] ([[https://github.com/php/php-src/pull/7191]]) Current userland implementations are handled in several ways, some of which use [[https://www.php.net/manual/en/function.min.php|min]] and [[https://www.php.net/manual/en/function.max.php|max]] to check the bound, which is [[https://github.com/kylekatarnls/clamp-perf-test/actions/runs/17546601109/job/49829393835|slower than what a native function could do]] (as per tests linked a native function would be even slightly faster than userland implementation using ternary, while providing some extra validation out of the box: NAN handling and verifying min <= max). ''clamp'' is already available in multiple languages with a rather common implementation and prototype, so this RFC aims to brings this feature natively in PHP also. echo clamp($percentage, min: 0, max: 100); ===== Proposal ===== This RFC proposes a new function: ''clamp'' clamp ( mixed $value, mixed $min, mixed $max ) : mixed ''clamp'' takes three arguments, a ''$value'', ''$min'' and ''$max'', then checks if ''$value'' is within the bounds of ''$min'' and ''$max'' (both inclusive). If ''$value'' is in the range (between ''$min'' and ''$max''), then ''$value'' is returned. Otherwise, the nearest bound is returned, i.e. if ''$value > $max'', it returns ''$max'', if ''$value < $min'', it returns ''$min''. If the ''$min'' value is greater than ''$max'', a ''ValueError'' will be thrown, since that constitutes an invalid bound. If ''$min'' or ''$max'' is ''NAN'', a ''ValueError'' will be thrown. ==== Examples ==== clamp(2, min: 1, max: 3) // 2 clamp(0, min: 1, max: 3) // 1 clamp(6, min: 1, max: 3) // 3 clamp(2, 1.3, 3.4) // 2 clamp(2.5, 1, 3) // 2.5 clamp(2.5, 1.3, 3.4) // 2.5 clamp(0, 1.3, 3.4) // 1.3 clamp(M_PI, -INF, INF) // 3.141592653589793 clamp(NAN, 4, 6) // NAN clamp("a", "c", "g") // "c" clamp("d", "c", "g") // "d" clamp(new \DateTimeImmutable('2025-08-01'), new \DateTimeImmutable('2025-08-15'), new \DateTimeImmutable('2025-09-15'))->format('Y-m-d') // 2025-08-15 clamp(new \DateTimeImmutable('2025-08-20'), new \DateTimeImmutable('2025-08-15'), new \DateTimeImmutable('2025-09-15'))->format('Y-m-d') // 2025-08-20 clamp(4, 8, 6) // Throws ValueError: clamp(): Argument #2 ($min) must be smaller than or equal to argument #3 ($max) clamp(4, NAN, 6) // Throws ValueError: clamp(): Argument #2 ($min) cannot be NAN clamp(4, 6, NAN) // Throws ValueError: clamp(): Argument #3 ($max) cannot be NAN ===== Comparison ===== ''clamp($value, $min, $max)'' will be equivalent to ''($value < $min) ? $min : ( ($value > $max) ? $max : $value )'' or to ''max($min, min($max, $value))'' in its result and also in what it accepts and how it compares those values, [[https://www.php.net/manual/en/language.operators.comparison.php|following the usual PHP comparison rules]]. In particular, be careful of how [[https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.types|arrays and objects are compared]]; this will only fit for some very specific cases. Like for ''min()'' and ''max()'', the documentation of ''clamp()'' will warn users about comparing values of different types. The function accepts ''mixed'' to follow the "loosely typed language" rules, but ''clamp()'' (like ''min()'' and ''max()'') is primarily designed to be used with ''$value'', ''$min'', and ''$max'' all being of the same type. ===== Parameters order ===== Parameters order is as in the sentence "clamp value between min and max", (as seen in [[https://en.cppreference.com/w/cpp/algorithm/clamp.html|C++]], [[https://learn.microsoft.com/en-us/dotnet/api/system.math.clamp?view=net-9.0|C#]], [[https://www.geeksforgeeks.org/python/python-pytorch-clamp-method/|Python]], [[https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Math.html#clamp(double,double,double)|Java]], etc.) It also happens to be found with the order: ''clamp(min, value, max)'' like [[https://developer.mozilla.org/en-US/docs/Web/CSS/clamp|in CSS]]. Order ''value, min, max'' as been picked as found to be the more common, but users can use named parameters to force a different order: clamp(min: 0, value: $angle, max: 90) ===== Backward Incompatible Changes ===== No backwards incompatible changes with PHP itself. ''clamp'' will no longer be available as a function name, which could break potential userland implementations. ===== Proposed PHP Version(s) ===== * next PHP 8.x ===== RFC Impact ===== * **To SAPIs:** Function will be added to all PHP environments. * **To Existing Extensions:** None * **To Opcache:** None. * **New Constants:** No new constants introduced. * **php.ini defaults:** No changes to php.ini introduced. ===== Open Issues ===== To be found ===== Future Scope ===== This section should outline areas that you are not planning to work on in the scope of this RFC, but that might be iterated upon in the future by yourself or another contributor. This helps with long-term planning and ensuring this RFC does not prevent future work. ===== Proposed Voting Choices ===== The vote will require 2/3 majority. ===== Implementation ===== RFC implementation can be found in the following pull request: [[https://github.com/php/php-src/pull/19434]]. ===== References ===== * Implementation of similar methods/functions in other languages: * [[https://en.cppreference.com/w/cpp/algorithm/clamp]] * [[https://www.rdocumentation.org/packages/raster/versions/3.4-10/topics/clamp]] * [[https://developer.mozilla.org/en-US/docs/Web/CSS/clamp()]] * Implementation PR: [[https://github.com/php/php-src/issues/7191]] * Discussion on the php.internals mailing list: [[https://news-web.php.net/php.internals/128570]] * Announcement thread: [[https://externals.io/message/115076]] ===== Changelog ===== Main changes between first implementation ([[https://github.com/php/php-src/pull/7191]]) and this one ([[https://github.com/php/php-src/pull/19434]]) are: * ''int|float'' became ''mixed'' (accept any comparable value) to better match the PHP "loosely typed language" rule * Support string comparison (alphabetically sorted) * Support ''DateTime'' * ''NAN'' handling was an open issue in the first implementation, this is now handled (''ValueError'' if passed as ''min'' or ''max'' argument, returned as is if passed as ''value'') * New implementation has arity frameless support