unyx /
utils
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php namespace nyx\utils; |
||
| 2 | |||
| 3 | /** |
||
| 4 | * Func |
||
| 5 | * |
||
| 6 | * Utilities related to functions/methods/callables/closures. |
||
| 7 | * |
||
| 8 | * @package Nyx\Utils\Func |
||
| 9 | * @version 0.0.5 |
||
| 10 | * @author Michal Chojnacki <[email protected]> |
||
| 11 | * @copyright 2012-2016 Nyx Dev Team |
||
| 12 | * @link http://docs.muyo.io/nyx/utils/func.html |
||
| 13 | * @todo Decide: Return a custom value instead of null when a Closure prevents the invocation of a callable? |
||
| 14 | * @todo Decide: Add Func::timeout()? |
||
| 15 | */ |
||
| 16 | class Func |
||
| 17 | { |
||
| 18 | /** |
||
| 19 | * The traits of the Func class. |
||
| 20 | */ |
||
| 21 | use traits\StaticallyExtendable; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * Special constant used to pass through the arguments from a main wrapped callable to a related callable. Concrete |
||
| 25 | * behaviour depends on the method being used, therefore check the method docs for actual usage info - self::unless() |
||
| 26 | * and self::when(). |
||
| 27 | */ |
||
| 28 | const PASSTHROUGH = "__passthrough__"; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * @var array The in-memory results cache for self::memoize(). |
||
| 32 | */ |
||
| 33 | private static $memory = []; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * Creates a Closure that, when called, ensures the wrapped callable only gets invoked after being called |
||
| 37 | * at least the given number of $times. |
||
| 38 | * |
||
| 39 | * @param int $times The number of times the function should get called before being executed. |
||
| 40 | * @param callable $callback The callable to wrap. |
||
| 41 | * @return \Closure The wrapper. |
||
| 42 | */ |
||
| 43 | View Code Duplication | public static function after(int $times = 1, callable $callback) : \Closure |
|
| 44 | { |
||
| 45 | return function(...$args) use ($callback, $times) { |
||
| 46 | static $count = 0; |
||
| 47 | |||
| 48 | if (++$count >= $times) { |
||
| 49 | return $callback(...$args); |
||
| 50 | } |
||
| 51 | }; |
||
| 52 | } |
||
| 53 | |||
| 54 | /** |
||
| 55 | * Returns a Closure which will return the subsequent given value (argument to this method) on each call. |
||
| 56 | * While this is primarily a utility for strings, it can be used with any type of values. |
||
| 57 | * |
||
| 58 | * When the Closure gets called with false as its argument, it will return the current internal value without |
||
| 59 | * alternating the next time (ie. the same value will be returned with the next call). |
||
| 60 | * |
||
| 61 | * @param mixed ...$between Two or more values to alternate between, given as separate arguments. |
||
| 62 | * @return \Closure |
||
| 63 | */ |
||
| 64 | public static function alternate(...$between) : \Closure |
||
| 65 | { |
||
| 66 | return function($next = true) use ($between) { |
||
| 67 | static $i = 0; |
||
| 68 | return $between[($next ? $i++ : $i) % count($between)]; |
||
| 69 | }; |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Returns a Closure which, once invoked, applies each given callable to the result of the previous callable, |
||
| 74 | * in right-to-left order. As such, the arguments passed to the Closure are passed through to the last |
||
| 75 | * callable given to the composition. |
||
| 76 | * |
||
| 77 | * Example: Func::compose(f, g, h)(x) is the equivalent of f(g(h(x))). |
||
| 78 | * |
||
| 79 | * @param callable[] ...$callables The callables to compose. |
||
| 80 | * @return \Closure |
||
| 81 | */ |
||
| 82 | public static function compose(callable ...$callables) : \Closure |
||
| 83 | { |
||
| 84 | // Reverse the order of the given callables outside of the Closure. With the assumption |
||
| 85 | // the Closure may get invoked n > 1 times, this shaves off some execution time. |
||
| 86 | $callables = array_reverse($callables); |
||
| 87 | |||
| 88 | return function(...$args) use ($callables) { |
||
| 89 | foreach ($callables as $callable) { |
||
| 90 | $args = [$callable(...$args)]; |
||
| 91 | } |
||
| 92 | |||
| 93 | return current($args); |
||
| 94 | }; |
||
| 95 | } |
||
| 96 | |||
| 97 | /** |
||
| 98 | * Generates a hash (signature) for a given callable with the given arguments. |
||
| 99 | * |
||
| 100 | * @param callable $callable The callable to hash. |
||
| 101 | * @param array $args The arguments to hash. |
||
| 102 | * @return string The hash. |
||
| 103 | */ |
||
| 104 | public static function hash(callable $callable, array $args) : string |
||
| 105 | { |
||
| 106 | if ($callable instanceof \Closure) { |
||
| 107 | $callable = var_export($callable, true); |
||
| 108 | } elseif (is_array($callable)) { |
||
| 109 | $callable = is_string($callable[0]) ? implode('::', $callable) : $callable[1]; |
||
| 110 | } |
||
| 111 | |||
| 112 | return md5(implode('_', [$callable, var_export($args, true)])); |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Creates a Closure that memoizes the result of the wrapped callable and returns it once the wrapper gets |
||
| 117 | * called. |
||
| 118 | * |
||
| 119 | * @param callable $callback The callable to wrap. |
||
| 120 | * @param callable|string $key - When a (resolver) callable is given, it will be invoked with 1 |
||
| 121 | * or more arguments: the wrapped callable and any arguments |
||
| 122 | * passed to the wrapped callable (variadic). Its return value |
||
| 123 | * will be cast to a string and used as the cache key for the result. |
||
| 124 | * - When any other value except for null is given, it will be cast to |
||
| 125 | * a string and used as the cache key. |
||
| 126 | * - When not given, self::hash() will be used to create a hash |
||
| 127 | * of the call signature to use as key. |
||
| 128 | * @return \Closure The wrapper. |
||
| 129 | * @todo Expose the cached result inside the callable (limited to the same |
||
| 130 | * callable?) |
||
| 131 | * @todo Use the first argument for the callable as cache key if not given? |
||
| 132 | */ |
||
| 133 | public static function memoize(callable $callback, $key = null) : \Closure |
||
| 134 | { |
||
| 135 | return function(...$args) use ($callback, $key) { |
||
| 136 | |||
| 137 | // Determine which cache key to use. |
||
| 138 | $key = null === $key |
||
|
0 ignored issues
–
show
|
|||
| 139 | ? static::hash($callback, $args) |
||
| 140 | : (string) (is_callable($key) ? $key($callback, ...$args) : $key); |
||
| 141 | |||
| 142 | // If we don't already have a hit for the cache key, populate it with the result of invocation. |
||
| 143 | if (!array_key_exists($key, static::$memory)) { |
||
|
0 ignored issues
–
show
Since
$memory is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $memory to at least protected.
Let’s assume you have a class which uses late-static binding: class YourClass
{
private static $someVariable;
public static function getSomeVariable()
{
return static::$someVariable;
}
}
The code above will run fine in your PHP runtime. However, if you now create a
sub-class and call the class YourSubClass extends YourClass { }
YourSubClass::getSomeVariable(); // Will cause an access error.
In the case above, it makes sense to update class SomeClass
{
private static $someVariable;
public static function getSomeVariable()
{
return self::$someVariable; // self works fine with private.
}
}
Loading history...
|
|||
| 144 | static::$memory[$key] = $callback(...$args); |
||
|
0 ignored issues
–
show
Since
$memory is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $memory to at least protected.
Let’s assume you have a class which uses late-static binding: class YourClass
{
private static $someVariable;
public static function getSomeVariable()
{
return static::$someVariable;
}
}
The code above will run fine in your PHP runtime. However, if you now create a
sub-class and call the class YourSubClass extends YourClass { }
YourSubClass::getSomeVariable(); // Will cause an access error.
In the case above, it makes sense to update class SomeClass
{
private static $someVariable;
public static function getSomeVariable()
{
return self::$someVariable; // self works fine with private.
}
}
Loading history...
|
|||
| 145 | } |
||
| 146 | |||
| 147 | return static::$memory[$key]; |
||
|
0 ignored issues
–
show
Since
$memory is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $memory to at least protected.
Let’s assume you have a class which uses late-static binding: class YourClass
{
private static $someVariable;
public static function getSomeVariable()
{
return static::$someVariable;
}
}
The code above will run fine in your PHP runtime. However, if you now create a
sub-class and call the class YourSubClass extends YourClass { }
YourSubClass::getSomeVariable(); // Will cause an access error.
In the case above, it makes sense to update class SomeClass
{
private static $someVariable;
public static function getSomeVariable()
{
return self::$someVariable; // self works fine with private.
}
}
Loading history...
|
|||
| 148 | }; |
||
| 149 | } |
||
| 150 | |||
| 151 | /** |
||
| 152 | * Creates a Closure that, when called, ensures the wrapped callable only gets invoked once. |
||
| 153 | * |
||
| 154 | * @param callable $callback The callable to wrap. |
||
| 155 | * @return \Closure The wrapper. |
||
| 156 | */ |
||
| 157 | public static function once(callable $callback) : \Closure |
||
| 158 | { |
||
| 159 | return static::only(1, $callback); |
||
| 160 | } |
||
| 161 | |||
| 162 | /** |
||
| 163 | * Creates a Closure that, when called, ensures the wrapped callable may only get invoked $times times at most. |
||
| 164 | * |
||
| 165 | * @param int $times The number of times the function may get invoked at most. |
||
| 166 | * @param callable $callback The callable to wrap. |
||
| 167 | * @return \Closure The wrapper. |
||
| 168 | */ |
||
| 169 | View Code Duplication | public static function only(int $times = 1, callable $callback) : \Closure |
|
| 170 | { |
||
| 171 | return function(...$args) use ($callback, $times) { |
||
| 172 | // Keep track of how many times the Closure was already called. |
||
| 173 | static $called = 0; |
||
| 174 | |||
| 175 | // Invoke the callback when we didn't hit our limit yet. |
||
| 176 | if ($times >= ++$called) { |
||
| 177 | return $callback(...$args); |
||
| 178 | } |
||
| 179 | }; |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * Creates a Closure that, when called, invokes the wrapped callable with any additional partial arguments |
||
| 184 | * prepended to those provided to the new Closure. |
||
| 185 | * |
||
| 186 | * @param callable $callback The callable to wrap. |
||
| 187 | * @param mixed ...$prependedArgs The arguments to prepend to the callback. |
||
| 188 | * @return \Closure The wrapper. |
||
| 189 | */ |
||
| 190 | public static function partial(callable $callback, ...$prependedArgs) : \Closure |
||
| 191 | { |
||
| 192 | return function(...$args) use ($callback, $prependedArgs) { |
||
| 193 | return $callback(...$prependedArgs, ...$args); |
||
| 194 | }; |
||
| 195 | } |
||
| 196 | |||
| 197 | /** |
||
| 198 | * Creates a Closure that, when called, invokes the wrapped callable with any additional partial arguments |
||
| 199 | * appended to those provided to the new Closure. |
||
| 200 | * |
||
| 201 | * @param callable $callback The callable to wrap. |
||
| 202 | * @param mixed ...$appendedArgs The arguments to append to the callback. |
||
| 203 | * @return \Closure The wrapper. |
||
| 204 | */ |
||
| 205 | public static function partialRight(callable $callback, ...$appendedArgs) : \Closure |
||
| 206 | { |
||
| 207 | return function(...$args) use ($callback, $appendedArgs) { |
||
| 208 | return $callback(...$args, ...$appendedArgs); |
||
| 209 | }; |
||
| 210 | } |
||
| 211 | |||
| 212 | /** |
||
| 213 | * Wraps a callable in a Closure to ensure the callable can only be executed once every $wait milliseconds. |
||
| 214 | * Subsequent calls to the wrapped callable before the wait time allows another execution run will return the |
||
| 215 | * result of the previous execution. |
||
| 216 | * |
||
| 217 | * @param callable $callback The callable to wrap. |
||
| 218 | * @param int $wait The time in milliseconds that must pass before the callable can be executed |
||
| 219 | * again after each call. |
||
| 220 | * @return \Closure The wrapper. |
||
| 221 | */ |
||
| 222 | public static function throttle(callable $callback, int $wait = null) : \Closure |
||
| 223 | { |
||
| 224 | return function(...$args) use ($callback, $wait) { |
||
| 225 | static $timer = 0; |
||
| 226 | static $result = null; |
||
| 227 | |||
| 228 | if ($timer <= $microtime = microtime(true)) { |
||
| 229 | // Update the internal timer. |
||
| 230 | $timer = $microtime + $wait / 1000; |
||
| 231 | |||
| 232 | // And call the actual callable. |
||
| 233 | $result = $callback(...$args); |
||
| 234 | } |
||
| 235 | |||
| 236 | return $result; |
||
| 237 | }; |
||
| 238 | } |
||
| 239 | |||
| 240 | /** |
||
| 241 | * Retries the execution of a callable for a given number of $times, optionally delaying each retry |
||
| 242 | * by $delay seconds. The only condition of failure triggering a retry is when the callable throws |
||
| 243 | * an Exception. |
||
| 244 | * |
||
| 245 | * Unlike most other methods in this utility, this method does not return a Closure due to its nature. |
||
| 246 | * If you require a "retrier" function, you could of course just wrap the call to Func::retry inside a Closure |
||
| 247 | * of your own. |
||
| 248 | * |
||
| 249 | * Note: Delay is *blocking*. When running though an event loop, instead of using the delay, you should |
||
| 250 | * schedule retries of your code on loop ticks or via timers (unless, of course, blocking is of no concern |
||
| 251 | * or even desired). |
||
| 252 | * |
||
| 253 | * @param callable $callback The callable to invoke. |
||
| 254 | * @param int $times The number of times the callable should be invoked upon failure. |
||
| 255 | * @param float $delay The delay between each retry, in seconds. |
||
| 256 | * @return mixed The result of the callable's invocation (upon success). |
||
| 257 | * @throws \Exception When the last allowed retry fails (the type of the exception is a re-throw |
||
| 258 | * of the last exception thrown by the callable). |
||
| 259 | */ |
||
| 260 | public static function retry(callable $callback, int $times = 1, float $delay = null) |
||
| 261 | { |
||
| 262 | retry: try { |
||
| 263 | return $callback(); |
||
| 264 | } catch (\Exception $exception) { |
||
| 265 | if (0 < $times--) { |
||
| 266 | if (isset($delay)) { |
||
| 267 | usleep($delay * 1000000); |
||
| 268 | } |
||
| 269 | |||
| 270 | goto retry; |
||
| 271 | } |
||
| 272 | |||
| 273 | throw $exception; |
||
| 274 | } |
||
| 275 | } |
||
| 276 | |||
| 277 | /** |
||
| 278 | * Wraps a callable in a Closure to only allow it to be invoked when the truth $test callable returns a falsy value. |
||
| 279 | * |
||
| 280 | * @param callable $test The truth test. |
||
| 281 | * @param callable $callback The callable to wrap. |
||
| 282 | * @param mixed ...$testArgs The arguments to pass to the truth test. If self::PASSTHROUGH gets passed |
||
| 283 | * (as the first argument, so anything past that gets ignored) the same |
||
| 284 | * arguments passed to the callable will also be passed to the truth test. |
||
| 285 | * @return \Closure The wrapper. |
||
| 286 | */ |
||
| 287 | public static function unless(callable $test, callable $callback, ...$testArgs) : \Closure |
||
| 288 | { |
||
| 289 | return static::whenInternal($test, $callback, false, ...$testArgs); |
||
| 290 | } |
||
| 291 | |||
| 292 | /** |
||
| 293 | * Wraps a callable in a Closure to only allow it to be invoked when the truth $test callable returns a truthy value. |
||
| 294 | * |
||
| 295 | * @param callable $test The truth test. |
||
| 296 | * @param callable $callback The callable to wrap. |
||
| 297 | * @param mixed ...$testArgs The arguments to pass to the truth test. If self::PASSTHROUGH gets passed |
||
| 298 | * (as the first argument, so anything past that gets ignored) the same |
||
| 299 | * arguments passed to the callable will also be passed to the truth test. |
||
| 300 | * @return \Closure The wrapper. |
||
| 301 | */ |
||
| 302 | public static function when(callable $test, callable $callback, ...$testArgs) : \Closure |
||
| 303 | { |
||
| 304 | return static::whenInternal($test, $callback, true, ...$testArgs); |
||
| 305 | } |
||
| 306 | |||
| 307 | /** |
||
| 308 | * Wraps a callable in a Closure to only allow it to be invoked when the $test callable returns a truthy or falsy |
||
| 309 | * value. Used internally by self::unless() and self::when() to reduce some code duplication. |
||
| 310 | * |
||
| 311 | * @param callable $test The truth test. |
||
| 312 | * @param callable $callback The callable to wrap. |
||
| 313 | * @param bool $expect The boolean to expect to allow the callable to be invoked. |
||
| 314 | * @param mixed ...$testArgs The arguments to pass to the truth test. If self::PASSTHROUGH gets passed |
||
| 315 | * (as the first argument, so anything past that gets ignored) the same |
||
| 316 | * arguments passed to the callable will also be passed to the truth test. |
||
| 317 | * @return \Closure The wrapper. |
||
| 318 | */ |
||
| 319 | protected static function whenInternal(callable $test, callable $callback, $expect = true, ...$testArgs) : \Closure |
||
| 320 | { |
||
| 321 | return function(...$callbackArgs) use ($callback, $test, $testArgs, $expect) { |
||
| 322 | $testArgs = (isset($testArgs[0]) && $testArgs[0] === self::PASSTHROUGH) ? $callbackArgs : $testArgs; |
||
|
0 ignored issues
–
show
Consider using a different name than the imported variable
$testArgs, or did you forget to import by reference?
It seems like you are assigning to a variable which was imported through a For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope. Change not visible in outer-scope$x = 1;
$callable = function() use ($x) {
$x = 2; // Not visible in outer scope. If you would like this, how
// about using a different variable name than $x?
};
$callable();
var_dump($x); // integer(1)
Change visible in outer-scope$x = 1;
$callable = function() use (&$x) {
$x = 2;
};
$callable();
var_dump($x); // integer(2)
Loading history...
|
|||
| 323 | |||
| 324 | // Loose comparison on purpose to make this more elastic. |
||
| 325 | if ($expect == $test(...$testArgs)) { |
||
| 326 | return $callback(...$callbackArgs); |
||
| 327 | } |
||
| 328 | }; |
||
| 329 | } |
||
| 330 | } |
||
| 331 |
It seems like you are assigning to a variable which was imported through a
usestatement which was not imported by reference.For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.
Change not visible in outer-scope
Change visible in outer-scope