Home > Net >  PHP variable packing and unpacking
PHP variable packing and unpacking

Time:04-12

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));
    }

CodePudding user response:

... 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));

CodePudding user response:

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