If you'd like to understand pack/unpack. There is a tutorial here in perl, that works equally well in understanding it for php:
http://perldoc.perl.org/perlpacktut.html(PHP 4, PHP 5, PHP 7, PHP 8)
pack — Empacota dados em uma string binária
Empacota os argumentos fornecidos em uma string binária de acordo com o formato
especificado em format.
A ideia para esta função foi tirada do Perl e todos os códigos de formatação funcionam da mesma forma que no Perl. Entretanto, há alguns códigos de formatação que não existem, como código "u" do Perl.
Observe que a distinção entre valores com e sem sinal somente afeta a função unpack(), enquanto que a função pack() fornece o mesmo resultado para códigos de formato com e sem sinal.
format
A string format consistem em códigos de formato
seguidos de um argumento repetidor opcional. O argumento repetidor pode
ser um valor inteiro ou * para repetir até
o final dos dados de entrada. Para a, A, h, H a contagem de repetição especifica
quantos caracteres de um argumento de dados são tomados, para @ é a
posição absoluta onde será colocado o próximo dado, para todo o restante a contagem
de repetição especifica quantos argumentos de dados são consumidos e empacotados
na string binária resultante.
Os formatos atualmente implementados são:
| Código | Descrição |
|---|---|
| a | String com bytes NUL à esquerda |
| A | String com espaços à esquerda |
| h | String hexadecimal, nibble menos significativo primeiro |
| H | String hexadecimal, nibble mais significativo primeiro |
| c | signed char |
| C | unsigned char |
| s | signed short (sempre 16 bits, ordem de bytes da máquina) |
| S | unsigned short (sempre 16 bits, ordem de bytes da máquina) |
| n | unsigned short (sempre 16 bits, ordem de bytes Big Endian) |
| v | unsigned short (sempre 16 bits, ordem de bytes Little Endian) |
| i | signed integer (tamanho e ordem de bytes dependentes da máquina) |
| I | unsigned integer (tamanho e ordem de bytes dependentes da máquina) |
| l | signed long (sempre 32 bits, ordem de bytes da máquina) |
| L | unsigned long (sempre 32 bits, ordem de bytes da máquina) |
| N | unsigned long (sempre 32 bits, ordem de bytes Big Endian) |
| V | unsigned long (sempre 32 bits, ordem de bytes Little Endian) |
| q | signed long long (sempre 64 bits, ordem de bytes da máquina) |
| Q | unsigned long long (sempre 64 bits, ordem de bytes da máquina) |
| J | unsigned long long (sempre 64 bits, ordem de bytes Big Endian) |
| P | unsigned long long (sempre 64 bits, ordem de bytes Little Endian) |
| f | float (representação e tamanho dependentes da máquina) |
| g | float (tamanho dependente da máquina, ordem de bytes Little Endian) |
| G | float (tamanho dependente da máquina, ordem de bytes Big Endian) |
| d | double (representação e tamanho dependentes da máquina) |
| e | double (tamanho dependente da máquina, ordem de bytes Little Endian) |
| E | double (tamanho dependente da máquina, ordem de bytes Big Endian) |
| x | byte NUL |
| X | Volta um byte |
| Z | String terminada em NUL (ASCIIZ), preenchida com bytes NUL |
| @ | Preenche com bytes NUL até a posição absoluta |
values
Retorna uma string binária contendo dados.
| Versão | Descrição |
|---|---|
| 8.0.0 |
Esta função não mais retorna false em caso de falha.
|
| 7.2.0 | Tipos float e double suportam tanto Big Endian quanto Little Endian. |
| 7.0.15, 7.1.1 | Os códigos "e", "E", "g" e "G" foram adicionados para habilitar suporte a ordem de byte para float e double. |
Exemplo #1 Exemplo de pack()
<?php
$binarydata = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>A string binária resultante terá 6 bytes de comprimento e conterá a sequência de bytes 0x12, 0x34, 0x78, 0x56, 0x41, 0x42.
Os códigos de formato q, Q, J e P não estão disponíveis em versões do PHP de 32 bits.
Observe que, internamente, o PHP armazena valores int como valores com sinal de tamanho dependente da máquina. Literais inteiros e operações que resultam em números fora da faixa do tipo int serão armazenados como float. Ao empacotar estes floats como inteiros, eles primeiro são convertidos para o tipo inteiro. Isto pode ou não resultar no padrão de bytes desejado.
O caso mais relevante ocorre ao empacotar números sem sinal que poderiam
ser representáveis pelo tipo int se eles fossem sem sinal.
Em sistemas onde o tipo int tem 32 bits, a conversão
normalmente resulta no mesmo padrão de byte como se o int fosse
sem sinal (embora isso dependa das conversões de sem sinal para com sinal definidas
pela implementação, conforme o padrão C). Em sistemas onte o tipo
int tem 64 bits, o tipo float provavelmente
não terá a mantissa grande o suficiente para representar o valor sem
perda de precisão. Se estes sistemas também tiverem o tipo
int nativo de 64 bits (a maioria dos sistemas tipo UNIX não têm), a única maneira
de usar o formato I de empacotamento na faixa superior é criar
valores negativos int com a mesma representação de bytes
do valor sem sinal desejado.
If you'd like to understand pack/unpack. There is a tutorial here in perl, that works equally well in understanding it for php:
http://perldoc.perl.org/perlpacktut.htmlA helper class to convert integer to binary strings and vice versa. Useful for writing and reading integers to / from files or sockets.
<?php
class int_helper
{
public static function int8($i) {
return is_int($i) ? pack("c", $i) : unpack("c", $i)[1];
}
public static function uInt8($i) {
return is_int($i) ? pack("C", $i) : unpack("C", $i)[1];
}
public static function int16($i) {
return is_int($i) ? pack("s", $i) : unpack("s", $i)[1];
}
public static function uInt16($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("n", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("v", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("S", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int32($i) {
return is_int($i) ? pack("l", $i) : unpack("l", $i)[1];
}
public static function uInt32($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("N", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("V", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("L", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int64($i) {
return is_int($i) ? pack("q", $i) : unpack("q", $i)[1];
}
public static function uInt64($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("J", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("P", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("Q", $i);
}
return is_array($i) ? $i[1] : $i;
}
}
?>
Usage example:
<?php
Header("Content-Type: text/plain");
include("int_helper.php");
echo int_helper::uInt8(0x6b) . PHP_EOL; // k
echo int_helper::uInt8(107) . PHP_EOL; // k
echo int_helper::uInt8("\x6b") . PHP_EOL . PHP_EOL; // 107
echo int_helper::uInt16(4101) . PHP_EOL; // \x05\x10
echo int_helper::uInt16("\x05\x10") . PHP_EOL; // 4101
echo int_helper::uInt16("\x05\x10", true) . PHP_EOL . PHP_EOL; // 1296
echo int_helper::uInt32(2147483647) . PHP_EOL; // \xff\xff\xff\x7f
echo int_helper::uInt32("\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 2147483647
// Note: Test this with 64-bit build of PHP
echo int_helper::uInt64(9223372036854775807) . PHP_EOL; // \xff\xff\xff\xff\xff\xff\xff\x7f
echo int_helper::uInt64("\xff\xff\xff\xff\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 9223372036854775807
?>Note that the the upper command in perl looks like this:
$binarydata = pack ("n v c*", 0x1234, 0x5678, 65, 66);
In PHP it seems that no whitespaces are allowed in the first parameter. So if you want to convert your pack command from perl -> PHP, don't forget to remove the whitespaces!If you need to unpack a signed short from big-endian or little-endian specifically, instead of machine-byte-order, you need only unpack it as the unsigned form, and then if the result is >= 2^15, subtract 2^16 from it.
And example would be:
<?php
$foo = unpack("n", $signedbigendianshort);
$foo = $foo[1];
if($foo >= pow(2, 15)) $foo -= pow(2, 16);
?>/* Convert float from HostOrder to Network Order */
function FToN( $val )
{
$a = unpack("I",pack( "f",$val ));
return pack("N",$a[1] );
}
/* Convert float from Network Order to HostOrder */
function NToF($val )
{
$a = unpack("N",$val);
$b = unpack("f",pack( "I",$a[1]));
return $b[1];
}Even though in a 64-bit architecure intval(6123456789) = 6123456789, and sprintf('%b', 5000000000) = 100101010000001011111001000000000
pack will not treat anything passed to it as 64-bit. If you want to pack a 64-bit integer:
<?php
$big = 5000000000;
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($big & $left) >>32;
$r = $big & $right;
$good = pack('NN', $l, $r);
$urlsafe = str_replace(array('+','/'), array('-','_'), base64_encode($good));
//done!
//rebuild:
$unurl = str_replace(array('-','_'), array('+','/'), $urlsafe);
$binary = base64_decode($unurl);
$set = unpack('N2', $tmp);
print_r($set);
$original = $set[1] << 32 | $set[2];
echo $original, "\\r\\n";
?>
results in:
Array
(
[1] => 1
[2] => 705032704
)
5000000000
but ONLY on a 64-bit enabled machine and PHP distro.You will get the same effect with
<?php
function _readInt($fp)
{
return unpack('V', fread($fp, 4));
}
?>
or unpack('N', ...) for big-endianness.Be aware of format code H always padding the 0 for byte-alignment to the right (for odd count of nibbles).
So pack("H", "7") results in 0x70 (ASCII character 'p') and not in 0x07 (BELL character)
as well as pack("H*", "347") results in 0x34 ('4') and 0x70 ('p') and not 0x03 and 0x47.Sorry, but i use AI ;-)
Was talking about memory optimisation and performance with Google Gemini. Here is a nice axample for the \pack function. (And using `\pack` and not `pack` is also performance related)
If your build tool generates giant in-memory lookup data metrics (like IP routing zones, geo-location grids, or localized translation indices), do not store them as standard multidimensional PHP arrays. A PHP array bucket requires massive zval and tracking hash overhead.
Instead, pack the data into raw binary sequences using pack() and look it up via byte offsets with substr().
The Memory Comparison:
Imagine storing 50,000 coordinate status IDs.
PHP
// ❌ Array Allocation: Takes ~6 Megabytes of RAM
$data = [10023, 10024, 10025, ...];
// 🚀 Packed Binary String: Takes ~200 Kilobytes of RAM (30x less memory)
$packedData = \pack('N*', 10023, 10024, 10025);
How you execute an O(1) read:
Because you packed the integers using the N format (unsigned 32-bit big-endian integers), you know with absolute mathematical certainty that every single number occupies exactly 4 bytes of space inside that string.
To read index number 5,000, you don't map arrays. You calculate the direct byte offset instantly:
PHP
// Direct memory offset extraction via string pointer shifting
$byteOffset = 5000 * 4;
$binarySegment = \substr($packedData, $byteOffset, 4);
// Unpack back to an integer instantly
$unpacked = \unpack('Nid', $binarySegment);
$id = $unpacked['id'];
To the Zend Engine, a string is just a flat, contiguous vector of memory. By using binary packed strings for deep lookup matrices, you bypass the entire zval architecture. You store raw numbers directly beside each other in system RAM, making your application footprint incredibly light and keeping the entire data structure small enough to fit inside the CPU's high-speed L2 or L3 cache lines.pack()
h Hex string, low nibble first (not same hex2bin())
H Hex string, high nibble first (same hex2bin())Using pack to write Arabic char(s) to a file.
<?php
$text = "㔆㘆㘆";
$text = mb_convert_encoding($text, "UCS-2BE", "HTML-ENTITIES");
$len = mb_strlen($text);
$bom = mb_convert_encoding("", "unicode", "HTML-ENTITIES");
$fp = fopen('text.txt', 'w');
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack("a{$len}", $text));
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack('a2', "\n"));
fclose($fp);
?>