PHP Passing an array to pack() function
Asked Answered
W

5

12

pack() syntax is (from http://php.net/manual/en/function.pack.php)

string pack ( string $format [, mixed $args [, mixed $... ]] )

so assuming I need to pack three bytes

$packed = pack( "c*", 65, 66, 67 );

But what if I have to pack an arbitrary number of bytes?

They could coveniently be stored into an array so I naively tried

$a = array( 65, 66, 67 );
$packed = pack( "c*", $a );

But it doesn't work.

Is there a way to make pack() work with an array ?

Wanton answered 28/4, 2015 at 12:16 Comment(2)
I think this should help you: https://mcmap.net/q/466323/-how-can-i-convert-array-of-bytes-to-a-string-in-phpCombust
Not nice to unaccept a working answer for php when a php5.6+ specific answer was given later when php5.6 was not specified in the question :)... My voodoo doll of you is almost complete.Trimming
G
26

At little late to the party, but for future reference, you can use the new ... operator (v5.6+) to explode the array inline:

$packed = pack("c*", ...$a);
Glanders answered 19/8, 2016 at 14:21 Comment(0)
T
3

You could create your own function array_pack that internally calls pack using the call_user_func or the call_user_func_array functions so you can pass the correct number of parameters to it.

Something like this could probably work (not tested though... But you get the general idea)

function array_pack(array $arr) {
  return call_user_func_array("pack", array_merge(array("c*"), $arr));
}
Trimming answered 28/4, 2015 at 12:20 Comment(0)
W
3

use string concatenation instead of pack()

When packing bytes the packed binary data (string) may be produced by simply using chr(), concatenation . and a foreach loop:

$packed = "";
foreach ($a as $byte) {
    $packed .= chr($byte);
}

Where $a is the source array and $packed is the produced binary data stored in a string variable, as per the original question.


benchmark

Having come, at the time of writing, with 5 different working solutions, it's worth doing a benchmark in case the amount of data to pack is huge.

I've tested the five cases with a 1048576 elements array in order to produce 1 MB of binary data. I measured execution time and consumed memory.

2023 (update)

Testing environment: PHP 8.2.7 - macOS Ventura - CPU Apple M1 Pro

(only a single core is used of course)

// pack with ... operator:     9 ms
// string concatentation:     10 ms
// call_user_func_array:      12 ms
// multiple pack:             46 ms
// array_reduce:           10918 ms

2018

Testing environment: PHP 5.6.30 - Mac OS X - 2.2 GHz Intel Core I7

(only a single core is used of course)

// pack with ... operator:    57 ms - 1.3 MB
// string concatentation:    197 ms - 1.3 MB
// call_user_func_array:     249 ms - 1.5 MB
// multiple pack:            298 ms - 1.3 MB
// array_reduce:           39114 ms - 1.3 MB

The ... operator used directly with the pack function if by far the fastest solution (accepted answer)

If ... is not available (PHP version prior to 5.6) the solution proposed by this answer (string concatentation) is the fastest.

Memory usage is almost the same for every case.

I post the test code if anyone interested.


<?php

// Return elapsed time from epoch time in milliseconds

function milliseconds() {
    $mt = explode(' ', microtime());
    return ((int)$mt[1]) * 1000 + ((int)round($mt[0] * 1000));
}



// Which test to run [1..5]

$test = $argv[ 1 ];



// Test 1024x1024 sized array

$arr = array();
for( $i = 0; $i < 1024 * 1024; $i++ )
{
    $arr[] = rand( 0, 255 );
}



// Initial memory usage and time

$ms0 = milliseconds();
$mem0 = memory_get_usage( true );



// Test 1: string concatentation

if( $test == '1' )
{
    $data = "";
    foreach ( $arr as $byte ) {
        $data .= chr( $byte );
    }
            
    $test = "string concatentation";
}



// Test 2: call_user_func_array

if( $test == '2' )
{
    $data = call_user_func_array("pack", array_merge(array("c*"), $arr));

    $test = "call_user_func_array";
}



// Test 3: pack with ... operator

if( $test == '3' )
{
    $data = pack("c*", ...$arr);

    $test = "pack with ... operator";
}



// Test 4: array_reduce

if( $test == '4' )
{
    $data = array_reduce($arr, function($carry, $item) { return $carry .= pack('c', $item); });

    $test = "array_reduce";
}



// Test 5: Multiple pack

if( $test == '5' )
{
    $data = "";
    foreach ($arr as $item) $data .= pack("c", $item);

    $test = "multiple pack";
}



// Output result

$ms = milliseconds() - $ms0;
$mem = round( ( memory_get_usage( true ) - $mem0 ) / ( 1024 * 1024 ), 1 );
echo "$test: $ms ms; $mem MB\n";
Wanton answered 15/4, 2018 at 11:8 Comment(1)
another variant (instead of foreach) for completeness: $packed = implode(array_map('chr', $a));Schnook
P
1

I use this function:

private function pack_array($format, $arg)
{
    $result="";
    foreach ($arg as $item) $result .= pack ($format, $item);
    return $result;
}
Perrone answered 16/7, 2015 at 16:24 Comment(0)
S
0

Another option if you can't use ... operator:

$packed = array_reduce($a, function($carry, $item) {
    return $carry .= pack('c', $item);
});
Soviet answered 22/1, 2017 at 21:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.