Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Usando o serialize do PHP, nao estou conseguindo descobrir por que ele converte determinados caracteres. Andei pesquisando e alguns dizem para usar html_entity_decode, porém, neste caso não da certo.
Exemplo:
class Player
{
protected $tipo;
//as duas propriedades abaixo não são serializadas pela classe Jogador.
//Pois, propriedade private não é visivel no escopo da classe filha. No caso da propriedade estatica, ela por default não é serializada.
private $maxPlayer;
private $experiencia;
public static $roupa;
public function __construct()
{
$this->maxPlayer = 2;
$this->experiencia = 1500;
static::$roupa = 'casual';
}
protected function defaultMaxPlayer()
{
$this->maxPlayer = 4;
}
}
class Jogador extends Player
{
public $nome;
private $hp;
protected $score;
public function __construct($nome)
{
$this->nome = $nome;
$this->hp = 120;
$this->score = 3;
$this->tipo = 'Jogador';
}
public function __sleep()
{
//omitimos a propriedade 'hp', pois ela será redefinida na wakeup, então não é interessante te-la na nossa string de serialização.
return array("nome", "score", "tipo");
}
public function __wakeup()
{
$this->hp = 100;
parent::defaultMaxPlayer();
}
public function dizerOla()
{
echo "<br /><br /> Olá, método executado.";
}
}
$jogador = new Jogador('Rafael');
$jogadorSerialize = serialize($jogador);
//note que a saida possui caracteres estranhos que nao deviam estar presente. Exemplo ?? devia ser apenas
var_dump($jogadorSerialize);
Isso pode gerar um problema grave.
Por exemplo:
se eu ao invés de debugar a string serializada com var_dump eu der um echo:
echo $jogadorSerialize;
a saida no navegador será (85 caracteres):
O:7:"Jogador":3:{s:4:"nome";s:6:"Felipe";s:8:"score";i:3;s:7:"tipo";s:7:"Jogador";}
e não (89 caracteres):
O:7:"Jogador":3:{s:4:"nome";s:6:"Felipe";s:8:"��score";i:3;s:7:"��tipo";s:7:"Jogador";}
agora imagine o problema se eu copiasse manualmente a string gerada com echo e tentasse criar o objeto numa outra página.
Algo do tipo:
$stringSerialize = <<<DATA
O:7:"Jogador":3:{s:4:"nome";s:6:"Felipe";s:8:"score";i:3;s:7:"tipo";s:7:"Jogador";}
DATA;
$myObject = unserialize($stringSerialize);
a função unserialize geraria um erro Notice com a mensagem 'Error at offset 54 of 85 bytes' - pois não foi passado os bytes completos. (perdemos 5 caracteres).
Alguém sabe o por que o serialize gera este tipo de conversão, se é normal...?
Testei com o uso da interface nativa do PHP, a Serializable e ela não gera essa conversão estranha mencionada.
Deve-se lembrar que a interface Serializable substitui os métodos mágicos __sleep e __wakeup, assim, a conversão dos caracteres pode ocorrer nestes métodos mágicos quando dado serialize().
Fica a dica para quem vai usar serialização com PHP, tomar cuidado com serialização com métodos mágicos.
Mas fica aberta a questão, se alguém souber se esses métodos mágicos realmente fazem conversão informe.
Como sempre, encaro o fórum aqui como um lugar de aprendizado, então segue o código usando a interface Serializable para solucionar talvez essa questão que alguns possam enfrentar.
class Jogador implements Serializable
{
public $nome;
private $hp;
protected $score;
public function __construct($nome)
{
$this->nome = $nome;
$this->hp = 120;
$this->score = 3;
}
public function serialize()
{
return serialize(array(
'nome' => $this->nome,
'score' => $this->score
));
}
public function unserialize($serialized)
{
$data = unserialize($serialized);
if (isset($data['nome']) && isset($data['score']))
{
$this->nome = $data['nome'];
$this->score = $data['score'];
$this->hp = 100;
}
else
{
echo 'Erro ao deserializar objeto';
}
}
public function dizerOla()
{
echo "<br /><br /> Olá, método executado.";
}
}
$jogador = new Jogador('Felipe');
$jogadorSerialize = serialize($jogador);
var_dump($jogadorSerialize);
exit();
se der um echo() ou var_dump vai ter o mesmo número de caracteres, podendo copiar a string diretamente do echo() que não vai dar problema de Offset.
//65 caracteres
echo str($jogadorSerialize);
//65 caracteres
var_dump($jogadorSerialize);
Quem souber a respeito dos caracteres estranhos gerados sem o uso da interface Serializable, colabore respondendo o tópico.
Muito boa a sua explicação Bruno. Tinha percebido que a interface agiu de modo melhor do que com __sleep e __wakeup neste caso, porém, mesmo pesquisando exaustivamente na net, eu não tinha visto que o problema era gerado por causa de propriedades private. E olha que olhei o manual (até coloquei um comentário antes da declaração da propriedade private), mas por não ter gerado erro, não me importei com ela.
Mais uma vez você tem colaborado de forma surpreendente, valeu Bruno.
Bruno, no código que você postou com a implementação do Serializable acredito que há um problema...
class Box implements Serializable {
private $items = array();
public function __construct( $item = NULL ) {
if( ! is_null( $item ) ) {
$this -> items[] = $item;
}
}
public function addItem( $item ) {
$this -> items[] = $item;
return $this;
}
public function getItems() {
return $this -> items;
}
// Serializable Interface Methods Implementation
public function serialize() {
return serialize( $this -> data );
}
public function unserialize( $data ) {
$this -> items = unserialize( $data );
}
}
No método serialize está enviando uma propriedade indefinida ($this->data) à função serialize(), gerando um Notice.
Acredito que a intenção era enviar a propriedade $items para ser serializada. abaixo segue a alteração para facilitar para outros usuários se beneficiarem do tópico.
class Box implements Serializable {
private $items = array();
public function __construct( $item = NULL )
{
if( ! is_null( $item ) ) {
$this -> items[] = $item;
}
}
public function addItem( $item )
{
$this -> items[] = $item;
return $this;
}
public function getItems()
{
return $this -> items;
}
// Serializable Interface Methods Implementation
public function serialize()
{
return serialize( array('items' => $this -> items));
}
public function unserialize( $data )
{
$dados = unserialize( $data );
$this -> items = $dados['items'];
}
}
$box = new Box;
$box -> addItem( 'Item #1' )
-> addItem( 'Item #2' )
-> addItem( 'Item #3' );
$serialized = serialize( $box );
$unserialized = unserialize( $serialized );
var_dump(
$box, $serialized,
get_class_methods( $unserialized ), $unserialized -> getItems()
);
Algo que acho interessante mencionar sobre o Serializable perante o __sleep e __Wakeup é a flexibilidade que a interface possibilita na hora de serializar o objeto.
Um dos casos que pode ser citado é a possibilidade de manipular os dados no método unserialize(). Neste método podemos validar os dados e até gerar um Exception caso necessite. Coisa que o __wakeup não possibilita.
Consertei (de novo) o nome da propriedade.
Eu havia editado ontem à noite quando postei mas permaneceu errado <_<
Mas é bem isso mesmo, com a Serializable você faz coisas que achava (e constatava) impossíveis de serem feitas com os métodos mágicos.
Eu acho, que não demora muito o pessoal do PHP inventa um jeito certo de fazer o que os métodos mágicos fazem sem depender deles.
Tem uma pequena diferença entre __sleep() / __wakeup() e Serializable::serialize() / Serializable::unserialize().
Os dois primeiros são invocados antes de serialize() / unserialize() são invocados, respectivamente.
Os outros dois são invocados exatamente quando esses métodos são invocados permitindo, como a descrição da interface informa, executar uma serialização personalizada.
A interface deve ser usada quando nem todo objeto precisa estar presente na string resultante ou quando a forma normal não conseguir lidar com a complexidade do objeto. Ex:
class Box {
Ao serializar este objeto nós temos:
O:3:"Box":1:{s:10:"�Box�items";a:0:{}}
Opa! Apareceu um caractere indevido porque serialize() não conseguiu lidar com o cifrão da propriedade Box::$items
De acordo com a observação sobre __sleep() / __wakeup() no manual esse é um bom caso para usar a interface:
class Box implements Serializable {
// ...
// Serializable Interface Methods Implementation
Perceba, porém, que não basta criar os métodos e retornar o mesmo que a função acima, serializando o objetopois vai entrar num loop infinito.
Com a serialização personalizada da interface você pode reconstruir o objeto mantendo seus dados originais sem serializar o objeto inteiro:
Como assim?
class Box implements Serializable {
Olha o que acontece quando usamos:
$box = new Box;
$box -> addItem( 'Item #1' )
-> addItem( 'Item #2' )
var_dump(
$box, $serialized,
get_class_methods( $unserialized ), $unserialized -> getItems()
);
Resulta em:
object(Box)[1]
private 'items' =>
array
string 'C:3:"Box":60:{a:3:{i:0;s:7:"Item #1";i:1;s:7:"Item #2";i:2;s:7:"Item #3";}}'
(length=75)
array
array
Ou seja, o objeto box foi serializado mas quem na verdade foi, foi apenas sua coleção de items.
E como Serialize::unserialize() age como construtor do objeto, é como se estivéssemos instanciando manualmente um objeto novo, porém sem nos preocuparmos com a "limitação" do exemplo de só permitir um item por vez a ser adicionado através do construtor.
E quando de-serializamos, o objeto foi reconstruído, comprovado pela listagem de métodos disponíveis, e a coleção foi restaurada, comprovado pela invocação de Box::getItems() a partir da string de-serializada.
Explicado isso vamos ao ponto do seu problema.
Na bem da verdade, a resposta já foi dada ao citar as Notas do manual sobre __sleep() / __wakeup():
Traduzindo livremente temos que propriedades particulares (já que privada é feio :P ) não serão serializadas corretamente e que nesse caso o uso da interface é o mais adequado.
O que não é mencionado nessas notas é que para as propriedades protegidas, não restritas às definidas em escopo de herança, tais métodos também não funcionarão para uma serialização correta.
Pior! Somos alertados de que nesses casos um E_NOTICE seria disparado, mas ele não é, mesmo com os erros habilitados e no máximo:
Sendo assim, resumindo e fechando, alterando a visibilidade de suas propriedades Player::tipo() e Jogador::score() para public, a serialização funciona:
string 'O:7:"Jogador":3:{s:4:"nome";s:6:"Rafael";s:5:"score";i:3;s:4:"tipo";s:7:"Jogador";}' (length=83)
Porém, é sabido que propriedades públicas não devem existir quando programando uma classe PHP dada a possível (e bem provável) violação no encapsulamento.