Serializando e reconstituindo arrays

Nossa meta é criar um gerenciador de conteúdos. Isto implica em guardar dados e, posteriormente, utilizá-los para montar um documento.

Iremos utilizar a função file_put_contents() para gravar um arquivo e file_get_contents() para ler o arquivo criado.

O problema é que um documento possui diversos “campos” distintos – título, conteúdo, nome do autor, data de criação, etc – que desejaríamos guardar e recuperar separadamente.

Como arquivos são lidos como strings (cadeia de caractéres), para gravar diversas variáveis precisaremos “serializá-las” – transformá-las em uma única série de caracteres – de tal forma que posteriormente seja possível separá-las novamente, reconstituindo cada campo.

O PHP oferece duas funções muito práticas para realizar esta tarefa: serialize() e unserialize().

Vamos considerar nosso documento como um array, e vamos preencher este array com os conteúdos do nosso documento:

$document['title'] = 'Olá mundo';
$document['content'] = 'Eu estou prestes a me tornar um programador de verdade!';

Vamos agora gravar este array em um arquivo:

file_put_contents ('teste.txt', serialize ($document));

O arquivo “teste.txt” será criado com o seguinte conteúdo:

a:2:{s:5:"title";s:9:"Olá mundo";s:7:"content";s:55:"Eu estou prestes a me tornar um programador de verdade!";}

Poderemos reconstituir o nosso array com a função unserialize() como se segue:

$document = unserialize (file_get_contents ('teste.txt'));
   print '<html><body>';
   print '<h1>' . $document['title'] . '</h1>';
   print nl2br ($document['content']);
   print '</body></html>'

Isto irá enviar o seguinte documento para o navegador:

Olá mundo
Eu estou prestes a me tornar um programador de verdade!

Este método é rápido e prático para armazenar dados em arquivos e em bancos de dados. O inconveniente é que o arquivo gerado é confuso e difícil de alterar. Durante o desenvolvimento do nosso gerenciador de conteúdos, será necessário editar alguns arquivos de dados “manualmente“. Por isto, vamos criar nossa própria função para serializar e reconstituir arrays.

Os arquivos de dados deverão guardar, além dos textos, também algumas configurações. Sabemos, de antemão, que nossos arrays de dados poderão conter strings, números inteiros e arrays (com strings, inteiros ou arrays…). Embora o PHP possua outros tipos de dados – como booleanos, números de ponto flutuante, objetos… nós não iremos utilizá-los para armazenar dados – isto simplificará consideravelmente o processo de serialização e reconstituição.

A complexidade deste processo provêm do fato de que o valor de um array pode ser também um array, este, por sua vez, pode ter outros arrays como valores, e assim por diante. Para percorrer um array multi-dimensional, o processo é um pouco mais complexo que um simples “foreach”.

Imagine um edifício cheio de andares vazios. Vamos iniciar o nosso trabalho no primeiro andar. Espalharemos o array a ser processado no primeiro andar e começaremos a percorrer pelos seus itens com each(). Se por ventura o valor de um item for também um array, pegaremos este valor e o levaremos para o andar de cima (o segundo andar). Lá, iniciaremos o trabalho com o novo array até que ele termine. Ao terminar, desceremos novamente para o andar inferior (o primeiro andar) e continuaremos a partir de onde havíamos parado. Ao terminar, desceremos até o térreo e sairemos do edifício.

O nosso edifício pode ter alguns bilhões de andares, então podemos processar arrays com sub-arrays à vontade, estando limitados somente pela quantidade de memória disponível no computador.

Nosso edifício será uma variável – um array. O índice deste array corresponderá ao andar em que nos encontramos, então entenda

$building[$floor]

como sendo o edifício e o andar. Subir um andar:

$floor ++

descer:

$floor --

O processo de serialização será bem simples: em cada linha de um arquivo colocaremos  uma chave e valor do array, como em:

'chave'='valor'
'outra_chave'='outro_valor'

O apóstrofo que envolve o nome da chave ou o valor nos permitirá escrever valores de múltiplas linhas, como em:

'chave'='um texto em
múltiplas linhas'

Porém, se houver um apóstrofo dentro do valor, vamos duplicá-lo para que, durante o processo de reconstituição, possamos compreender que ali havia um apóstrofo escapado.

Chaves e valores que forem números inteiros não serão circundados com apóstrofo para que, durante a reconstituição, os valores sejam novamente convertidos para inteiros.

Se o valor de um item for um array, iremos abrir uma chave (“{“) e colocaremos os itens do subarray aí dentro. Ao terminar, fechamos com “}”. Assim,

'array'={
   1='Estou aqui dentro'
}

Isto será a representação de

$array['array'][1] = 'Estou aqui dentro.';

Para reconstituir o array, o processo é semelhante: criamos um edifício e iniciamos no primeiro andar.

  • Sempre que encontrarmos “{“, subiremos um andar ($floor ++) e passaremos a criar todos os itens ($key=$value) neste andar.
  • Ao encontrar “}”, desceremos novamente para o nível anterior e copiaremos o array do nível superior como valor do item corrente.

Serializando os arrays com esta técnica, torna-se fácil criar e editar os arquivos de dados manualmente. Mas há ainda mais uma funcionalidade que pode nos ajudar quando quisermos editar os dados manualmente: trata-se de utilizar um “coringa” (“#”) para as chaves numéricas. Isto facilita a inserção e a remoção de itens. Para que as chaves numéricas sejam serializadas como coringas, chamamos a função array2string() com um segundo argumento “true“. Ao reconstituir, os coringas serão convertidos automaticamente para chaves numéricas.

Para testar, utilize os primeiros exemplos, substituindo serialize() por array2string() e unserialize() por string2array().

function array2string ($array, $use_counter=false)
   { // array to string
      // Se nosso array não for de fato um array, abortaremos a operação
      if (!is_array ($array)) return '';

      // a string resultante
      $string = '';
      // o edifício
      $building = array();
      // o andar em que nos encontramos
      $floor = 1;
      // iniciaremos copiando o array para o primeiro andar do edifício
      $building[$floor] = $array;

      // nosso processo se dará em um loop "infinito"
      while (1)
          { // loop

          // capturaremos o item corrente do array no andar em que nos encontramos
          // com each, após a captura, o ponteiro interno do array deverá avançar para o próximo item
          $item = each ($building[$floor]);

          // se o array tiver terminado, desceremos um andar
          if ($item === false)
               { // end of array
               unset ($building[$floor]);
               $floor --;

               // Se chegarmos ao "térreo", o processo terminou
               if (!$floor) break;

     // Se não terminou, imprime "}"
     $string .= "}\r\n";
     } // end of array
else
     { // array item

       // capturamos a chave e o valor do item corrente
       $key = $item[0];
       $value = $item[1];

       // imprimimos a chave do array
       if (is_int ($key))
           { // int key
               $use_counter ? $string .= '#' : $string .= $key;
           } // int key
else
    { // string key
        $string .= "'" . str_replace ("'", "''", $key) . "'";
    } // string key

// Se o valor for um "subarray", 
if (is_array ($value))
    { // value is array
        // subimos um andar
        $floor ++;
        // copiamos o array deste valor para o andar em que nos encontramos
        $building[$floor] = $value;
            // Imprimimos "={"
            $string .= "={\r\n";
    } // value is array

// Caso o valor seja numérico
elseif (is_int ($value))
    { // numeric value
        // imprimimos "=" e o valor sem os apóstrofos
        $string .= '=' . strval($value) . "\r\n";
    } // numeric value

// Ou consideraremos o valor como string
else
    { // string value
        // imprimimos a string englobada por "'", e duplicamos os apóstrofos encontrados dentro dela
        $string .= "='" . str_replace ("'", "''", $value) . "'\r\n";
    } // string value
    } // array item

    } // loop

    return $string;
    } // array to string

function string2array ($string, $counter1=0)
    { // string to array
        // Se a string não for de fato uma string, abortamos
        if (!is_string ($string)) return array();

        // acrescentamos à string recebida uma quebra de linha
        $string .= "\n";
        // o cumprimento total da string
        $strlen = strlen ($string);
        // o ponteiro que percorrerá a string
        $pointer = 0;
        // o edifício
        $building = array ();
        // o andar em que nos encontramos
        $floor = 1;
        $building[$floor] = array();
        // O contador que substituirá os coringas encontrados
        $counter[$floor] = $counter1;

        // Sinalizadores
        $buffer = '';
        $buffer_enabled = false;
        $buffer_string = false;
        $key_enabled = false;

        // iremos varrer a string até que o ponteiro alcance seu cumprimento total
        while ($pointer < $strlen)
            { // get char

                // capturamos o caractére da string que se encontra sob o ponteiro
                $char = $string[$pointer];

                // Verificamos que caractére é este
                switch ($char)
                    { // char

                        // ao encontrar uma quebra de linha, criamos um novo item de array
                        case "\n":
                        if (!$buffer_string) settype ($buffer, 'int');
                        if ($key_enabled)
                            { // creates a value
                               $building[$floor][$key] = $buffer;
                            } // creates a value
                        $buffer = '';
                        $key_enabled = false;
                        $buffer_enabled = false;
                        $buffer_string = false;
                        break;

                        // ao encontrar "=", o que estiver no $buffer representa a chave de um item
                        case '=':
                        if ($buffer_enabled)
                            { // creates key
                                if ($buffer == '#')
                                    { // counter
                                        $counter[$floor] ++;
                                        $buffer = $counter[$floor];
                                    } // counter
                                elseif (!$buffer_string) 
                                    { // numeric
                                        settype ($buffer, 'int');
                                    } // numeric
                                $key = $buffer;
                                $buffer = '';
                                $buffer_enabled = false;
                                $buffer_string = false;
                                $key_enabled = true;
                            } // creates key
                        break;

                        // ao encontrar "{", subiremos um andar
                        case '{':
                        if ($key_enabled)
                            { // sublevel
                                $array_key[$floor] = $key;
                                $floor ++;
                                $building[$floor] = array();
                                $counter[$floor] = 0;
                            } // sublevel
                        $key = '';
                        $key_enabled = false;
                        $buffer = '';
                        $buffer_enabled = false;
                        $buffer_string = false;
                        break;

                        // ao encontrar "}", copiamos o array do andar corrente para o item atual do andar inferior e descemos um andar
                        case '}':
                    if ($floor <= 1) break 2;

                    $building[$floor - 1][$array_key[$floor - 1]] = $building[$floor];
                    $counter[$floor] = 0;
                    $floor --;
                    $buffer = '';
                    $buffer_enabled = false;
                    $buffer_string = false;
                    $key = '';
                    $key_enabled = false;
                    break;

                    // Ao encontrar "'", procuramos uma string com apóstrofos duplicados ("''")
                    case "'":
                    case '"':
                    loop_escape_char:
                    $n_close_string = strpos ($string, $char, $pointer + 1);
                    if ($n_close_string === false) break;

                    $buffer .= substr ($string, $pointer + 1, $n_close_string - ($pointer + 1));
                    $buffer_enabled = true;
                    $buffer_string = true;
                    $pointer = $n_close_string;
                    if ($string[$pointer + 1] != $char) break;
                    $buffer .= $char;
                    $pointer ++;
                    goto loop_escape_char;

                    // Caractéres ignorados
                    case "\r":
                    case "\t":
                    case ' ':
                    break;

                    // Ao encontrar "/" ou "<", seguiremos ignorando uma linha de comentário
                    case '<':
                    case '/':
                    $n_close_string = strpos ($string, "\n", $pointer + 1);
                    if ($n_close_string === false)
                    break;
                    else
                        { // coment
                            $pointer = $n_close_string - 1;
                        } // coment
                    break;

                    // outros caractéres são guardados em $buffer
                    default:
                    $buffer .= $char;
                    $buffer_enabled = true;
                    break;

                } // char

            // avança o ponteiro para o próximo caractére
            $pointer ++;
        } // get char
    return $building[1];
} // string to array

Sobre o Autor

Redes Sociais:

Deixe seu comentário

X