Pergunta

Eu encontrei um problema estranho e não tenho certeza de como corrigi -lo. Eu tenho várias classes que são todas as implementações de PHP dos objetos JSON. Aqui 'uma ilustração da questão

class A
{
    protected $a;

    public function __construct()
    {
        $this->a = array( new B, new B );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

class B
{
    protected $b = array( 'foo' => 'bar' );

    public function __toString()
    {
        return json_encode( $this->b );
    }
}

$a = new A();

echo $a;

A saída disso é

[{},{}]

Quando o desejado saída é

[{"foo":"bar"},{"foo":"bar"}]

O problema é que eu estava confiando no __ToString () Hook para fazer meu trabalho por mim. Mas não pode, porque a serialização que JSON_ENCODE () usa não chama __toString (). Quando encontra um objeto aninhado, simplesmente serializa apenas propriedades públicas.

Então, a pergunta então se tornou a seguinte: existe uma maneira de desenvolver uma interface gerenciada para as aulas JSON que me permitem usar setters e getters para propriedades, mas também me permite obter o comportamento de serialização do JSON que eu desejo?

Se isso não estiver claro, aqui está um exemplo de uma implementação que não vai Trabalho, já que o gancho __set () é chamado apenas para a tarefa inicial

class a
{
    public function __set( $prop, $value )
    {
        echo __METHOD__, PHP_EOL;
        $this->$prop = $value;
    }

    public function __toString()
    {
        return json_encode( $this );
    }
}

$a = new a;
$a->foo = 'bar';
$a->foo = 'baz';

echo $a;

Suponho que também poderia fazer algo assim

class a
{
    public $foo;

    public function setFoo( $value )
    {
        $this->foo = $value;
    }

    public function __toString()
    {
        return json_encode( $this );
    }
}

$a = new a;
$a->setFoo( 'bar' );

echo $a;

Mas então eu teria que confiar na diligência dos outros desenvolvedores para usar os Setters - não posso forçar a adesão a programação com esta solução.

---> editar <---

Agora com um teste de resposta de Rob Elsner

<?php

class a implements IteratorAggregate 
{
    public $foo = 'bar';
    protected $bar = 'baz';

    public function getIterator()
    {
        echo __METHOD__;
    }
}

echo json_encode( new a );

Quando você executa isso, você pode ver que o método getIterator () nunca nunca foi invocado.

Foi útil?

Solução

Respostas tardias, mas podem ser úteis para outras pessoas com o mesmo problema.

Dentro PHP <5.4.0 json_encode não chama nenhum método do objeto. Isso é válido para getIterator, __serialize, etc ...

Em php> v5.4.0, no entanto, uma nova interface foi introduzida, chamada Jsonserializable.

Basicamente controla o comportamento do objeto quando json_encode é chamado nesse objeto.


Violino (Bem, na verdade é PHP CodePad, mas a mesma coisa ...)

Exemplo:

class A implements JsonSerializable
{
    protected $a = array();

    public function __construct()
    {
        $this->a = array( new B, new B );
    }

    public function jsonSerialize()
    {
        return $this->a;
    }
}

class B implements JsonSerializable
{
    protected $b = array( 'foo' => 'bar' );

    public function jsonSerialize()
    {
        return $this->b;
    }
}


$foo = new A();

$json = json_encode($foo);

var_dump($json);

Saídas:

String (29) [{" foo ":" bar "}, {" foo ":" bar "}]

Outras dicas

Não é sua resposta no PHP DOCs para JSON_ENCODE?

Para qualquer pessoa que tenha encontrado o problema das propriedades privadas que não estão sendo adicionadas, você pode simplesmente implementar a interface iteratorAggregate com o método getIterator (). Adicione as propriedades que você deseja incluir na saída em uma matriz no método getIterator () e retorne -o.

Em php> v5.4.0, você pode implementar a interface chamada JsonSerializable conforme descrito em a resposta por Tivie.

Para aqueles de nós usando PHP <5.4.0, você pode usar uma solução que emprega get_object_vars() de dentro do próprio objeto e depois alimenta aqueles para json_encode(). Isso é o que eu fiz no exemplo a seguir, usando o __toString() Método, para que, quando eu ligo o objeto como uma string, recebo uma representação codificada JSON.

Também está incluído uma implementação do IteratorAggregate interface, com seu getIterator() Método, para que possamos iterar sobre as propriedades do objeto como se fossem uma matriz.

<?php
class TestObject implements IteratorAggregate {

  public $public = "foo";
  protected $protected = "bar";
  private $private = 1;
  private $privateList = array("foo", "bar", "baz" => TRUE);

  /**
   * Retrieve the object as a JSON serialized string
   *
   * @return string
   */
  public function __toString() {
    $properties = $this->getAllProperties();

    $json = json_encode(
      $properties,
      JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
    );

    return $json;
  }

  /**
   * Retrieve an external iterator
   *
   * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
   * @return \Traversable
   *  An instance of an object implementing \Traversable
   */
  public function getIterator() {
    $properties = $this->getAllProperties();
    $iterator = new \ArrayIterator($properties);

    return $iterator;
  }

  /**
   * Get all the properties of the object
   *
   * @return array
   */
  private function getAllProperties() {
    $all_properties = get_object_vars($this);

    $properties = array();
    while (list ($full_name, $value) = each($all_properties)) {
      $full_name_components = explode("\0", $full_name);
      $property_name = array_pop($full_name_components);
      if ($property_name && isset($value)) $properties[$property_name] = $value;
    }

    return $properties;
  }

}

$o = new TestObject();

print "JSON STRING". PHP_EOL;
print "------" . PHP_EOL;
print strval($o) . PHP_EOL;
print PHP_EOL;

print "ITERATE PROPERTIES" . PHP_EOL;
print "-------" . PHP_EOL;
foreach ($o as $key => $val) print "$key -> $val" . PHP_EOL;
print PHP_EOL;

?>

Este código produz a seguinte saída:

JSON STRING
------
{"public":"foo","protected":"bar","private":1,"privateList":{"0":"foo","1":"bar","baz":true}}

ITERATE PROPERTIES
-------
public -> foo
protected -> bar
private -> 1
privateList -> Array

Mesmo que sua variável protegida fosse pública em vez de protegida, você não terá a entrada desejada, pois isso produzirá todo o objeto como este:

[{"b":{"foo":"bar"}},{"b":{"foo":"bar"}}]

Ao invés de:

[{"foo":"bar"},{"foo":"bar"}]

Provavelmente, ele derrotará seu propósito, mas estou mais inclinado a converter para JSON na classe original com um getter padrão e pedindo os valores diretamente

class B
{
    protected $b = array( 'foo' => 'bar' );

    public function __get($name)
    {
        return json_encode( $this->$name );
    }
}

Então você pode fazer com eles o que quiser, até aninhando os valores em uma matriz adicional como a sua classe A, mas usando JSON_DECODE. Ainda parece um pouco sujo, mas funciona.

class A
{
    protected $a;

    public function __construct()
    {
        $b1 = new B;
        $b2 = new B;
        $this->a = array( json_decode($b1->b), json_decode($b2->b) );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

No documentação Existem algumas respostas a esse problema (mesmo que eu não goste da maioria deles, serializar + removendo as propriedades me faz sentir sujo).

Você está certo, o __toString () para a classe B não está sendo chamado, porque não há razão para. Então, para chamá -lo, você pode usar um elenco

class A
{
    protected $a;

    public function __construct()
    {
        $this->a = array( (string)new B, (string)new B );
    }

    public function __toString()
    {
        return json_encode( $this->a );
    }
}

Nota: O (String) fundido antes dos novos B's ... Isso chamará o método _ToString () da classe B, mas não receberá o que você deseja, porque você encontrará problemas clássicos de "codificação dupla" , porque a matriz é codificada no método B classe _toString (), e será codificado novamente no método a classe _toString ().

Portanto, há uma escolha de decodificar o resultado após o elenco, ou seja:

 $this->a = array( json_decode((string)new B), json_decode((string)new B) );

Ou você precisará obter a matriz, criando um método ToArray () na classe B que retorna a matriz reta. O que adicionará algum código à linha acima porque você não pode usar um construtor PHP diretamente (você não pode fazer um novo B ()-> Toarray ();) para que você possa ter algo como:

$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top