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