PHP variable packing and unpacking - php

I have a function that is used in Stripe PHP that requires PHP 5.6. I am running it on a server that has PHP 5.5.9 and giving me some trouble. The function is:
protected function buildPath($basePath, ...$ids)
{
foreach ($ids as $id) {
if (null === $id || '' === \trim($id)) {
$msg = 'The resource ID cannot be null or whitespace.';
throw new \Stripe\Exception\InvalidArgumentException($msg);
}
}
return \sprintf($basePath, ...\array_map('\urlencode', $ids));
}
I understand that the elipses(...) means it is variable packing. but when I try to convert it to something PHP 5.5.9 can use, using the below, it does not work:
protected function buildPath($basePath, pack($ids))
{
foreach ($ids as $id) {
if (null === $id || '' === \trim($id)) {
$msg = 'The resource ID cannot be null or whitespace.';
throw new \Stripe\Exception\InvalidArgumentException($msg);
}
}
return \sprintf($basePath, ...\array_map('\urlencode', $ids));
}

... in PHP can be used for two things:
Array unpacking, when calling a function, or populating another array. Note that this is not related to the unpack function, which is about handling binary data, but means "turn the items of this array into separate arguments to the function", or "... separate items of the final array".
Collecting variadic arguments - that is, a variable length argument list - when declaring a function. This is close to the reverse of "unpacking", but I've never heard it called "packing". Again, the pack function is completely unrelated (although, by coincidence, it is itself a variadic function). The effect is to take any number of arguments passed to the function and turn them into an array.
The example you show uses both features.
For the function signature, it is using variadic arguments. As noted on the manual page linked earlier:
Note: It is also possible to achieve variable-length arguments by using func_num_args(), func_get_arg(), and func_get_args() functions.
So, in all supported versions of PHP (5.5 has been unsupported for over 5 years; I hope you're paying someone for long-term-support security patches!), you can define the following function:
function example($normalArg, ...$variadicArgs) {
var_dump($variadicArgs);
}
and call it like this:
example('normal'); // $variadicArgs = []
example('normal', 1); // $variadicArgs = [1]
example('normal', 1, 2); // $variadicArgs = [1,2]
example('normal', 1, 2, 3); // $variadicArgs = [1,2,3]
In ancient versions of PHP before the ... notation was added, you had to instead declare the function with only the normal arguments, collect all the passed arguments by calling func_get_args, and skip over the "normal" ones. For example:
function example($normalArg) {
$allArgs = func_get_args();
$variadicArgs = array_slice($allArgs, 1);
var_dump($variadicArgs);
}
So in your case, the function could begin:
protected function buildPath($basePath)
{
$ids = \array_slice(\func_get_args(), 1);
Later in the function, it is using array unpacking. The only way to achieve this in ancient versions of PHP is using call_user_func_array, which takes a "callable" (which in simple cases can just be a function name as a string), and an array of arguments to pass to it. Again, you'll need to construct the full list of arguments, for instance using array_merge:
$fixedArgs = [$basePath];
$dynamicArgs = \array_map('\urlencode', $ids);
$allArgs = \array_merge($fixedArgs, $dynamicArgs);
return \call_user_func_array('\sprintf', $allArgs);
Or all on one line:
return \call_user_func_array('\sprintf', \array_merge([$basePath], \array_map('\urlencode', $ids)]);
As it happens, the particular function called here is sprintf, which has a variant called vsprintf ("v" for "vector") which takes an array of parameters instead of multiple separate arguments, so for this particular case you can use that:
return \vsprintf($basePath, \array_map('\urlencode', $ids));

In php 5.5.9 it can be written like this and is called Variadic function:
protected function buildPath($basePath)
{
$ids = func_get_args();
array_shift($ids);
foreach ($ids as $id) {
if (null === $id || '' === \trim($id)) {
$msg = 'The resource ID cannot be null or whitespace.';
throw new \Stripe\Exception\InvalidArgumentException($msg);
}
}
return \sprintf($basePath, ...\array_map('\urlencode', $ids));
}

Related

unsetting objects in an array based on a bool method

I'm trying to filter an array of objects implementing a specific interface (which simply defines the isComplete(): bool method) based on the result of that method. array_filter doesn't work because it can't call a method on each object to determine whether to filter it (or can it?). I've tried writing a function that takes the splatted array as an argument by reference, this doesn't work either:
function skipIncomplete(CompletableObjectInterface &...$objects): array {
$skipped = [];
foreach ($objects as $index => $item) {
if (!$item->isComplete()) {
$skipped[] = $item->id ?? $index;
unset($objects[$index]);
}
}
return $skipped;
}
The original elements passed in simply don't end up getting unset.
I'm looking for a way that doesn't include creating an entirely new Collection class to hold my CompletableObjects for complexity reasons. I really want to keep the type hint so no one can pass in a generic array, causing runtime errors when the function tries to call $item->isComplete.
Is there any way I can achieve this in PHP 7.3.15?
Added a filter, please comment as to what is wrong with this type of approach:
<?php
interface CompletableObjectInterface {
public function isComplete() : bool;
}
class Foo implements CompletableObjectInterface
{
public function isComplete() : bool
{
return false;
}
}
class Bar implements CompletableObjectInterface
{
public function isComplete() : bool
{
return true;
}
}
$foo = new Foo;
$bar = new Bar;
$incomplete = array_filter([$foo, $bar], function($obj) { return !$obj->isComplete();});
var_dump($incomplete);
Output:
array(1) {
[0]=>
object(Foo)#1 (0) {
}
}
Looks like you got a bit hung up on a wrong understanding of the ... syntax for a variable number of arguments.
You are passing in one array, and the $objects parameter will therefore contain that array in the first index, i.e. in $objects[0].
So in theory you could just change your line
unset($objects[$index]);
to
unset($objects[0][$index]);
However, I do not really see why the variable number of arguments syntax is used at all, since you apparently are just expecting one array of values (objects in this case) as an argument to the function. Therefore I'd recommend you just remove the ... from the argument list and your solution does what you wanted.
Alternatively you can of course add an outer foreach-loop and iterate over all passed "arrays of objects", if that is an use case for you.

Specific php functionality

My team lead some time ago asked this question, which I didn't understood:
Implement function calc()
Please, implement function calc so code below works:
$sum = function($a, $b) { return $a + $b; };
calc(5)(3)(2)($sum); // 10
calc(1)(2)($sum); // 3
calc(2)(3)('pow'); // 8
So, can someone explain me what is it, and maybe some link on this functionality
The following will satisfy your example use:
<?php
function calc($input)
{
static $args = [];
if(is_callable($input)) {
$carry = array_shift($args);
$result = array_reduce($args, $input, $carry);
$args = []; // Clear the arguments.
return $result;
} else {
$args[] = $input; // Add arguments to the stack.
return __FUNCTION__;
}
}
$sum = function($a, $b) {
return $a + $b;
};
echo
calc(5)(3)(2)($sum), "\n",
calc(1)(2)($sum), "\n",
calc(2)(3)('pow'), "\n",
calc(5)(2)(2)('pow');
Output:
10
3
8
625
Explanation:
When calc is called with a single argument (that is not a callable), the input is pushed to an array and the name of the function, here 'calc', is returned.
calc(2)(3) will add 2 to the static array (this will persist between subsequent function calls), and return the function name. So this becomes calc(3), where the previous call has the side effect of storing 2 in $args.
If the argument passed is a callable. array_reduce will pass pairs of arguments from $args left to right to the callable, seeding subsequent calls with the result of each iteration.
array_reduce([1,2,3], 'pow', $initial) is similar to the following:
$result = pow($initial, 1);
$result = pow($result, 2);
$result = pow($result, 3);
However we need to use array_shift to remove the first item from the $args array as a seed for the first iteration of the pow call:
So that becomes array_reduce([2,3], 'pow', 1).
We then clear the argument list, otherwise subsequent calls to calc will use these arguments again, and the result of the array_reduce is returned.
You could have it as a calc function that takes a value and:
if it's a not a function, adds it to a buffer array, then return the function itself,
if it's a function, calls that function subsequently on every value of that buffer.
So if we take calc(6)(3)($sum):
calc(6) call will add 6 to the buffer array then return the calc function,
(3) will therefore pass 3 as a param to that same calc function that was just returned, therefore adding 3 to the buffer and returning the calc
function again,
finally, ($sum) will generate a call to that $sum function, passing it every value from the buffer (therefore, 3 then 6), reducing it into the final result.
Code:
function calc($value_or_function, array $buffer = [])
{
// If the argument is callable (a function), check that the buffer has
// at least one value and call this function subsequently on each value,
// reducing it into a final value
if (is_callable($value_or_function)) {
if (count($buffer) === 0) {
throw new \InvalidArgumentException('Not enough parameters.');
}
return array_reduce(array_slice($buffer, 1), $value_or_function, $buffer[0]);
}
// Otherwise (not a callable arg), add it to the buffer and return the
// function itself so that its result can be chain-called
return static function ($otherValue) use ($buffer, $value_or_function) {
return calc($otherValue, array_merge($buffer, [$value_or_function]));
};
}
// Example usage
$sum = function ($a, $b) { return $a + $b; };
echo calc(5)(4)(3)($sum), PHP_EOL;
echo calc(5)(2)(2)('pow');
Additional notes:
this is a decent exercise but probably a bad idea to have in a real codebase, this is quite unintuitive, the function does too many things, doesn't have strictly typed params, etc.,
those inline comments are a bit much for real code (they're that detailed for explanation purposes).
Demo: https://3v4l.org/AoKJO

Is there a more elegant way to pass a function to another function in PhP?

It can be done by passing the function name in a string PHP pass function as param then call the function?
Well that is very crude.
No type checking
If I refactor the function name the string containing the variable need to be fixed too manually. If we have a typo it won't be checked on compile time.
It's not like in vb.net where we have addressOf operator.
Is this really the only way to do this in PhP?
I mean the lamda function seems more sane. At least the variable we pass is really a functio6 and not a string.
Am I wrong here?
Is the right way to do this is to use lambda?
Or is there any other way?
I can use closures like this
function getSelectedElements($textFile)
{
$standardfuncline2= function (&$arout,$line)
{
standardfuncline1 ($arout,$line);
};
$result = getSelectedElementsCustomFunc ($textFile,$standardfuncline2);
return $result;
}
instead of
$result = getSelectedElementsCustomFunc ($textFile,"standardfuncline1");
That seems to be more proven with all type checking and stuffs. However, kind of too long isn't it?
You can define your function as a closure, i.e. an anonymous function which can be assigned to a variable or passed as a function argument directly. The following example is taken from the PHP docs on callables:
Callback example using a Closure
<?php
// Our closure
$double = function($a) {
return $a * 2;
};
// This is our range of numbers
$numbers = range(1, 5);
// Use the closure as a callback here to
// double the size of each element in our
// range
$new_numbers = array_map($double, $numbers);
print implode(' ', $new_numbers);
?>
The above example will output:
2 4 6 8 10
More variants on the above can be found in the PHP documentation on anonymous functions.
When referencing an existing function
There is no such solution for functions that are defined in the usual way, but you can encapsulate them as a Callable:
// Your already existing function:
function myFunc($arg) {
echo "running myFunc with '$arg'.";
}
// The new Callable wrapper for it:
$myFunc = function ($arg) {
myFunc($arg);
};
// Calling it (same as in first solution)
call_user_func($myFunc, 'test');
You can sure do some Type-Hinting even when using call_user_func() like so:
/**
* #var $arrActions array
* #return mixed
*/
function doSomething(array $arrActions) { // TYPE HINT ARRAY
$output = "";
if(is_array($arrActions)) {
foreach ($arrActions as $action) {
$output .= "Have you already performed Action Nr. $action ?\n";
}
}
return $output;
}
// CALL WITH ARRAY AS ARGUMENT..
var_dump( call_user_func('doSomething', ["one", "two", "three"]) );
// PRODUCES:
string 'Have you already performed Action Nr. one ?
Have you already performed Action Nr. two ?
Have you already performed Action Nr. three ?
' (length=134)
// CALL WITH STRING AS ARGUMENT..
var_dump( call_user_func('doSomething', "one"]) );
// PRODUCES:
Fatal error: Uncaught TypeError: Argument 1 passed to doSomething() must be of the type array, string given....

$provider = function() vs function provider()

I've seen in various coding examples different coding style when creating functions.
What is the difference between creating a function using
$provider = function() { code here }
vs
function provider(){ code here }
Is the first example simply a short version of: $data = provider(); ?
When do we use the first example?
No, it isn't. First code is declaration of closure, i.e. anonymous function. It has no name and can be called with identifier that holds it. Second sample is normal function (user-defined function, to be more specific) and, thus, it will be accessible within all scopes via it's name - not like closure, which will be available for calling only within scope, where it was defined.
You can have as many closures as you wish - they are just callable entities, for example this is valid:
$provider = function() { Code here }
$another = function() { Code here } //same code
-and calling $provider (for example, with call_user_func()) will have nothing to do with $another
Another significant difference is that closure can accept context parameters like:
$provider = function() use ($param1, $param2, ...) { Code here }
-so inside it's body context parameters will be available. Context parameters are not like usual arguments - since context parameters defined and exist independent from closure while arguments are evaluated at the moment, when call happened.
First declaration is anonymous function.And after assignment,we have variable with name $provider and can call $provider() .Second declaration its just normally function.
Anonymous function can be user for example in array_map,array_filter.For example
$a = array(1, 2, 3, 4, 5);
$res = array_filter(
$a, function ($elem) {
return $elem > 3;
}
);
print_r($res);
output element who larger 3

working of callback function

while reading abt array_filter() from php manual,came to face example to demostrate
the same function using callback function as given below
<?php
function odd($var)
{
// returns whether the input integer is odd
return($var & 1);
}
function even($var)
{
// returns whether the input integer is even
return(!($var & 1));
}
$array1 = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
$array2 = array(6, 7, 8, 9, 10, 11, 12);
echo "Odd :\n";
print_r(array_filter($array1, "odd"));
echo "Even:\n";
print_r(array_filter($array2, "even"));
?>
can you please help me to know how actually calback function calling,actual parameter pass,working?
any link to demostrate about callback wld be great help.
In the two given examples, array_filter will go over the values in the passed array and send each value in it to the callback function (odd or even). The callback function will then inspect the value to see whether it is odd or even and return TRUE or FALSE. If it returns FALSE, the value is filtered from the array.
The easiest way to find out what your function is passing to your callback is to supply a callback that prints the passed arguments, e.g.
array_filter($anArray, function() { var_dump(func_get_args()) });
Callbacks are described in detail at
http://de.php.net/manual/en/language.pseudo-types.php#language.types.callback
Imagine you have a function like this:
function getNumbersDivisibleBy3($arr)
{
$threes = array();
foreach($arr as $val)
{
if($val % 3 == 0)
{
$threes[] = $val;
}
}
return $threes
}
This function filters out all the numbers divisible by three from an array and returns them as another array.
Now imagine another function:
function GetWordsStartingWithC($arr)
{
$cs = array();
foreach($arr as $word)
{
if($word[0] == 'C')
{
$cs[] = $word;
}
}
return $cs;
}
This function filters out all the words that start with C from an array of words and returns them another array.
If you look at the above functions, their meta function (as it were) can be explained as "This functions filters out all the items in an array that satisfies a condition and returns them as another array."
So instead of having to write the same boiler plate code to iterate through a list and filter out all elements that match, the PHP developers have written a function that takes an array and a string that is a function name.
In other languages, such as C#, instead of a string that is a function name, you actually pass in an object called a delegate, which is a pointer or reference to a function. But in PHP they have ways of figuring out which function you mean by the name you pass in. I don't know what those are.
So the array_filter function could look something like this (it won't as it's probably not written in PHP)
function array_filter($arr, $callbackname)
{
$result = array();
foreach($arr as $item)
{
if(call_user_func($callbackname, $item))
{
$result[] = $item;
}
}
return $result;
}
In that function you can see how similar it is to the previous two, but instead of a predefined condition, it calls back (using the call_user_func() function) the function to be used via the name you passed in and applies it to each item in the array by using each item as a parameter for the function.
So you can reduce the amount of code you write by using array_filter because you don't have to write the boiler plate iteration code, just the conditional function you need to filter on.
A callback function is a function that it "called back" by another one. In the case of array_filter() the callback is invoked for every element of the array, and that element is the argument passed to it. You don't control what argument are passed, that's up the the main function you're using.
So you basically say to array_filter(): please run through this array and apply this function named "odd" to every element; this function will return a boolean value so you know what to keep and what to discard.

Categories