web-dev-qa-db-fra.com

Convertir/convertir un objet stdClass en une autre classe

J'utilise un système de stockage tiers qui ne me renvoie que des objets stdClass, quelle que soit la raison de mon alimentation pour une raison obscure. Je suis donc curieux de savoir s’il existe un moyen de convertir/convertir un objet stdClass en un objet à part entière d’un type donné. 

Par exemple, quelque chose comme:

//$stdClass is an stdClass instance
$converted = (BusinessClass) $stdClass;

Je viens de transtyper stdClass dans un tableau et de le transmettre au constructeur BusinessClass, mais il existe peut-être un moyen de restaurer la classe initiale dont je ne suis pas au courant.

Remarque: le type de réponse «Modifiez votre système de stockage» ne m'intéresse pas, car il ne s'agit pas du point d'intérêt. Considérez-le plutôt comme une question académique sur les capacités linguistiques.

À votre santé

72

Voir le manuel Jongler avec les types sur les modèles possibles.

Les moulages autorisés sont:

  • (int), (integer) - converti en integer
  • (bool), (boolean) - cast to boolean
  • (float), (double), (real) - cast pour float
  • (chaîne) - cast to string
  • (array) - cast to array
  • (objet) - cast to object
  • (non défini) - cast to NULL (PHP 5)

Vous devez écrire un mappeur qui effectue le transtypage de stdClass vers une autre classe concrète. Ça ne devrait pas être trop difficile à faire.

Ou, si vous êtes d'humeur humiliée, vous pouvez adapter le code suivant:

function arrayToObject(array $array, $className) {
    return unserialize(sprintf(
        'O:%d:"%s"%s',
        strlen($className),
        $className,
        strstr(serialize($array), ':')
    ));
}

qui pseudocasts un tableau à un objet d'une certaine classe. Cela fonctionne en sérialisant d'abord le tableau, puis en modifiant les données sérialisées afin qu'elles représentent une certaine classe. Le résultat est alors non sérialisé sur une instance de cette classe. Mais comme je l’ai dit, c’est hackish, alors attendez-vous à des effets secondaires.

Pour objet à objet, le code serait

function objectToObject($instance, $className) {
    return unserialize(sprintf(
        'O:%d:"%s"%s',
        strlen($className),
        $className,
        strstr(strstr(serialize($instance), '"'), ':')
    ));
}
81
Gordon

Vous pouvez utiliser la fonction ci-dessus pour la conversion d'objets de classe non similaires (PHP> = 5.3)

/**
 * Class casting
 *
 * @param string|object $destination
 * @param object $sourceObject
 * @return object
 */
function cast($destination, $sourceObject)
{
    if (is_string($destination)) {
        $destination = new $destination();
    }
    $sourceReflection = new ReflectionObject($sourceObject);
    $destinationReflection = new ReflectionObject($destination);
    $sourceProperties = $sourceReflection->getProperties();
    foreach ($sourceProperties as $sourceProperty) {
        $sourceProperty->setAccessible(true);
        $name = $sourceProperty->getName();
        $value = $sourceProperty->getValue($sourceObject);
        if ($destinationReflection->hasProperty($name)) {
            $propDest = $destinationReflection->getProperty($name);
            $propDest->setAccessible(true);
            $propDest->setValue($destination,$value);
        } else {
            $destination->$name = $value;
        }
    }
    return $destination;
}

EXEMPLE:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

$a = new A();
$b = new B();

$x = cast('A',$b);
$x = cast('B',$a);
48
Adam Puza

Pour déplacer toutes les propriétés existantes d'une stdClass vers un nouvel objet du nom de classe spécifié:

/**
 * recast stdClass object to an object with type
 *
 * @param string $className
 * @param stdClass $object
 * @throws InvalidArgumentException
 * @return mixed new, typed object
 */
function recast($className, stdClass &$object)
{
    if (!class_exists($className))
        throw new InvalidArgumentException(sprintf('Inexistant class %s.', $className));

    $new = new $className();

    foreach($object as $property => &$value)
    {
        $new->$property = &$value;
        unset($object->$property);
    }
    unset($value);
    $object = (unset) $object;
    return $new;
}

Usage:

$array = array('h','n');

$obj=new stdClass;
$obj->action='auth';
$obj->params= &$array;
$obj->authKey=md5('i');

class RestQuery{
    public $action;
    public $params=array();
    public $authKey='';
}

$restQuery = recast('RestQuery', $obj);

var_dump($restQuery, $obj);

Sortie:

object(RestQuery)#2 (3) {
  ["action"]=>
  string(4) "auth"
  ["params"]=>
  &array(2) {
    [0]=>
    string(1) "h"
    [1]=>
    string(1) "n"
  }
  ["authKey"]=>
  string(32) "865c0c0b4ab0e063e5caa3387c1a8741"
}
NULL

Ceci est limité à cause de l'opérateur new car on ne sait pas quels paramètres il aurait besoin. Pour votre cas, probablement approprié.

13
hakre

J'ai un problème très similaire. La solution de réflexion simplifiée a bien fonctionné pour moi:

public static function cast($destination, \stdClass $source)
{
    $sourceReflection = new \ReflectionObject($source);
    $sourceProperties = $sourceReflection->getProperties();
    foreach ($sourceProperties as $sourceProperty) {
        $name = $sourceProperty->getName();
        $destination->{$name} = $source->$name;
    }
    return $destination;
}
10
Sergei G

J'espère que quelqu'un trouvera cela utile

// new instance of stdClass Object
$item = (object) array(
    'id'     => 1,
    'value'  => 'test object',
);

// cast the stdClass Object to another type by passing
// the value through constructor
$casted = new ModelFoo($item);

// OR..

// cast the stdObject using the method
$casted = new ModelFoo;
$casted->cast($item);
class Castable
{
    public function __construct($object = null)
    {
        $this->cast($object);
    }

    public function cast($object)
    {
        if (is_array($object) || is_object($object)) {
            foreach ($object as $key => $value) {
                $this->$key = $value;
            }
        }
    }
} 
class ModelFoo extends Castable
{
    public $id;
    public $value;
}
7
Wizzard

Fonction modifiée pour la coulée profonde (en utilisant la récursivité)

/**
 * Translates type
 * @param $destination Object destination
 * @param stdClass $source Source
 */
private static function Cast(&$destination, stdClass $source)
{
    $sourceReflection = new \ReflectionObject($source);
    $sourceProperties = $sourceReflection->getProperties();
    foreach ($sourceProperties as $sourceProperty) {
        $name = $sourceProperty->getName();
        if (gettype($destination->{$name}) == "object") {
            self::Cast($destination->{$name}, $source->$name);
        } else {
            $destination->{$name} = $source->$name;
        }
    }
}
5
Jadrovski

BTW: La conversion est extrêmement importante si vous êtes sérialisé, principalement parce que la dé-sérialisation casse le type des objets et se transforme en stdclass, y compris les objets DateTime.

J'ai mis à jour l'exemple de @Jadrovski. Il autorise désormais les objets et les tableaux.

exemple

$stdobj=new StdClass();
$stdobj->field=20;
$obj=new SomeClass();
fixCast($obj,$stdobj);

exemple tableau

$stdobjArr=array(new StdClass(),new StdClass());
$obj=array(); 
$obj[0]=new SomeClass(); // at least the first object should indicates the right class.
fixCast($obj,$stdobj);

code: (son récursif). Cependant, je ne sais pas si c'est récursif avec les tableaux. Peut-être sa manque un extra is_array

public static function fixCast(&$destination,$source)
{
    if (is_array($source)) {
        $getClass=get_class($destination[0]);
        $array=array();
        foreach($source as $sourceItem) {
            $obj = new $getClass();
            fixCast($obj,$sourceItem);
            $array[]=$obj;
        }
        $destination=$array;
    } else {
        $sourceReflection = new \ReflectionObject($source);
        $sourceProperties = $sourceReflection->getProperties();
        foreach ($sourceProperties as $sourceProperty) {
            $name = $sourceProperty->getName();
            if (is_object(@$destination->{$name})) {
                fixCast($destination->{$name}, $source->$name);
            } else {
                $destination->{$name} = $source->$name;
            }
        }
    }
}
0
magallanes

envisagez d'ajouter une nouvelle méthode à Business Class:

public static function fromStdClass(\stdClass $in): BusinessClass
{
  $out                   = new self();
  $reflection_object     = new \ReflectionObject($in);
  $reflection_properties = $reflection_object->getProperties();
  foreach ($reflection_properties as $reflection_property)
  {
    $name = $reflection_property->getName();
    if (property_exists('BusinessClass', $name))
    {
      $out->{$name} = $in->$name;
    }
  }
  return $out;
}

vous pouvez ensuite créer une nouvelle classe d’affaires à partir de $ stdClass:

$converted = BusinessClass::fromStdClass($stdClass);
0
pgee70

Encore une autre approche.

Ce qui suit est maintenant possible grâce à la récente version de PHP 7.

$theStdClass = (object) [
  'a' => 'Alpha',
  'b' => 'Bravo',
  'c' => 'Charlie',
  'd' => 'Delta',
];

$foo = new class($theStdClass)  {
  public function __construct($data) {
    if (!is_array($data)) {
      $data = (array) $data;
    }

    foreach ($data as $prop => $value) {
      $this->{$prop} = $value;
    }
  }
  public function Word4Letter($letter) {
    return $this->{$letter};
  }
};

print $foo->Word4Letter('a') . PHP_EOL; // Alpha
print $foo->Word4Letter('b') . PHP_EOL; // Bravo
print $foo->Word4Letter('c') . PHP_EOL; // Charlie
print $foo->Word4Letter('d') . PHP_EOL; // Delta
print $foo->Word4Letter('e') . PHP_EOL; // PHP Notice:  Undefined property

Dans cet exemple, $ foo est initialisé en tant que classe anonyme qui prend un tableau ou stdClass comme seul paramètre du constructeur.

Finalement, nous parcourons chacun des éléments contenus dans l'objet passé et affectons ensuite dynamiquement à la propriété d'un objet.

Pour rendre cet événement d'approche plus générique, vous pouvez écrire une interface ou un trait que vous implémenterez dans n'importe quelle classe où vous voulez pouvoir transtyper un stdClass.

0
asiby