Konvertieren / Umwandeln eines stdClass-Objekts in eine andere Klasse

89

Ich verwende ein Speichersystem eines Drittanbieters, das mir nur stdClass-Objekte zurückgibt, unabhängig davon, was ich aus einem unbekannten Grund eingebe. Ich bin gespannt, ob es eine Möglichkeit gibt, ein stdClass-Objekt in ein vollwertiges Objekt eines bestimmten Typs umzuwandeln.

Zum Beispiel etwas in der Art von:

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

Ich wandle die stdClass nur in ein Array um und füge sie dem BusinessClass-Konstruktor zu, aber vielleicht gibt es eine Möglichkeit, die ursprüngliche Klasse wiederherzustellen, die mir nicht bekannt ist.

Hinweis: Ich bin nicht an Antworten vom Typ "Ändern Sie Ihr Speichersystem" interessiert, da dies nicht der Punkt von Interesse ist. Bitte betrachten Sie es eher als eine akademische Frage zu den Sprachkapazitäten.

Prost

Die mächtige Gummiente
quelle
Es wird in meinem Beitrag nach dem Pseudocode-Beispiel erklärt. Ich werfe in ein Array und füttere einen automatisierten Konstruktor.
Die mächtige Gummiente
@Adam Puzas Antwort ist viel besser als der in der akzeptierten Antwort gezeigte Hack. obwohl ich sicher bin, dass ein Mapper immer noch die bevorzugte Methode wäre
Chris
Wie wird PDOStatement::fetchObjectdiese Aufgabe erfüllt?
William Entriken

Antworten:

88

Informationen zu möglichen Würfen finden Sie im Handbuch zum Typ Jonglieren .

Die erlaubten Abgüsse sind:

  • (int), (integer) - in integer umgewandelt
  • (bool), (boolean) - in boolean umwandeln
  • (float), (double), (real) - zum Float werfen
  • (string) - in string umwandeln
  • (Array) - In Array umwandeln
  • (Objekt) - auf Objekt werfen
  • (nicht gesetzt) ​​- auf NULL setzen (PHP 5)

Sie müssten einen Mapper schreiben , der das Casting von stdClass in eine andere konkrete Klasse ausführt. Sollte nicht zu schwer sein.

Wenn Sie in einer hackigen Stimmung sind, können Sie den folgenden Code anpassen:

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

die ein Array an ein Objekt einer bestimmten Klasse pseudocastet. Dies funktioniert, indem zuerst das Array serialisiert und dann die serialisierten Daten so geändert werden, dass sie eine bestimmte Klasse darstellen. Das Ergebnis wird dann auf eine Instanz dieser Klasse unserialisiert. Aber wie gesagt, es ist hackisch, also erwarten Sie Nebenwirkungen.

Für Objekt zu Objekt wäre der Code

function objectToObject($instance, $className) {
    return unserialize(sprintf(
        'O:%d:"%s"%s',
        strlen($className),
        $className,
        strstr(strstr(serialize($instance), '"'), ':')
    ));
}
Gordon
quelle
9
Dieser Hack ist eine clevere Technik. Ich werde es nicht verwenden, da meine derzeitige Art, das Problem zu lösen, stabiler, aber dennoch interessant ist.
Die mächtige Gummiente
1
__PHP_Incomplete_ClassMit dieser Methode erhalten Sie ein Objekt (mindestens ab PHP 5.6).
TiMESPLiNTER
1
@TiMESPLiNTER nein, das tust du nicht. Siehe codepad.org/spGkyLzL . Stellen Sie sicher, dass die Klasse, in die umgewandelt werden soll, enthalten ist, bevor Sie die Funktion aufrufen.
Gordon
@TiMESPLiNTER nicht sicher, was du meinst. es funktionierte. Es ist jetzt ein Beispiel \ Foo.
Gordon
Ja, das Problem ist, dass die stdClass-Eigenschaft angehängt wird. Sie haben also zwei fooBars (das private von example\Foound das öffentliche von stdClass). Stattdessen ersetzt es den Wert.
TiMESPLiNTER
53

Sie können die obige Funktion zum Umwandeln nicht ähnlicher Klassenobjekte verwenden (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;
}

BEISPIEL:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

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

$x = cast('A',$b);
$x = cast('B',$a);
Adam Puza
quelle
5
Das ist eine ziemlich elegante Lösung, muss ich sagen! Ich frage mich nur, wie gut es skaliert ... Reflexion macht mir Angst.
Theodore R. Smith
Hey Adam, diese Lösung hat ein ähnliches Problem für mich hier gelöst: stackoverflow.com/questions/35350585/… Wenn Sie eine einfache Antwort erhalten möchten, schauen Sie vorbei und ich werde es abhaken . Vielen Dank!
Oucil
Es funktioniert nicht für Eigenschaften, die von übergeordneten Klassen geerbt wurden.
Toilette
Ich habe dies gerade als Grundlage für meine Lösung zum Seeding meiner Datenbank mit bekannten IDs für API-Funktionstests mit Behat verwendet. Mein Problem war, dass meine normalen IDs UUIDs sind und ich nicht nur aus Gründen meiner Testebene eine setId () -Methode in meine Entität einfügen wollte, und ich wollte keine Fixtures-Dateien laden und Tests verlangsamen. Jetzt kann ich @Given the user :username has the id :idin mein Feature aufnehmen und es mit Reflexionen in der
Kontextklasse behandeln
2
Tolle Lösung, möchte ich hinzufügen, $destination = new $destination();die ausgetauscht werden kann, $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();wenn Sie den Aufruf des Konstruktors vermeiden müssen.
Scuzzy
14

So verschieben Sie alle vorhandenen Eigenschaften von a stdClassin ein neues Objekt mit einem angegebenen Klassennamen:

/**
 * 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;
}

Verwendung:

$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);

Ausgabe:

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

Dies ist aufgrund des newOperators begrenzt, da nicht bekannt ist, welche Parameter benötigt werden. Für Ihren Fall wahrscheinlich passend.

hakre
quelle
1
Um andere zu informieren, die versuchen, diese Methode zu verwenden. Diese Funktion hat den Nachteil, dass beim Durchlaufen eines instanziierten Objekts außerhalb von sich selbst keine privaten oder geschützten Eigenschaften innerhalb des gegossenen Objekts festgelegt werden können. EG: Setzen von public $ authKey = ''; zu privat $ authKey = ''; Ergebnisse in E_ERROR: Typ 1 - Kein Zugriff auf Privateigentum RestQuery :: $ authKey
fyrye
Eine stdClass mit privaten Eigenschaften?
Frug
@Frug Das OP weist ausdrücklich auf diese Anforderung hin ... ein stdClass-Objekt in ein vollwertiges Objekt eines bestimmten Typs umwandeln / konvertieren
oucil
11

Ich habe ein sehr ähnliches Problem. Die vereinfachte Reflexionslösung hat bei mir gut funktioniert:

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;
}
Sergei G.
quelle
8

Hoffe, dass jemand dies nützlich findet

// 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;
}
Wizzard
quelle
Können Sie erklären, warum "is_array ($ object) || is_array ($ object)"?
Kukinsula
5

Geänderte Funktion für Deep Casting (mit Rekursion)

/**
 * 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;
        }
    }
}
Jadrovski
quelle
1

Und noch ein Ansatz, der das Dekorationsmuster und die PHPs Magic Getter & Setter verwendet:

// A simple StdClass object    
$stdclass = new StdClass();
$stdclass->foo = 'bar';

// Decorator base class to inherit from
class Decorator {

    protected $object = NULL;

    public function __construct($object)
    {
       $this->object = $object;  
    }

    public function __get($property_name)
    {
        return $this->object->$property_name;   
    }

    public function __set($property_name, $value)
    {
        $this->object->$property_name = $value;   
    }
}

class MyClass extends Decorator {}

$myclass = new MyClass($stdclass)

// Use the decorated object in any type-hinted function/method
function test(MyClass $object) {
    echo $object->foo . '<br>';
    $object->foo = 'baz';
    echo $object->foo;   
}

test($myclass);
Benni
quelle
0

Übrigens: Das Konvertieren ist sehr wichtig, wenn Sie serialisiert sind, hauptsächlich weil die De-Serialisierung den Objekttyp aufteilt und in eine Standardklasse umgewandelt wird, einschließlich DateTime-Objekten.

Ich habe das Beispiel von @Jadrovski aktualisiert, jetzt sind Objekte und Arrays zulässig.

Beispiel

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

Beispiel Array

$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: (rekursiv). Ich weiß jedoch nicht, ob es mit Arrays rekursiv ist. Möglicherweise fehlt ein zusätzliches 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;
            }
        }
    }
}
Magallane
quelle
0

Erwägen Sie, BusinessClass eine neue Methode hinzuzufügen:

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;
}

dann können Sie aus $ stdClass eine neue BusinessClass erstellen:

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

Noch ein Ansatz.

Folgendes ist jetzt dank der aktuellen PHP 7-Version möglich.

$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

In diesem Beispiel wird $ foo als anonyme Klasse initialisiert, die ein Array oder stdClass als einzigen Parameter für den Konstruktor verwendet.

Schließlich durchlaufen wir alle im übergebenen Objekt enthaltenen Elemente und weisen sie dann dynamisch der Eigenschaft eines Objekts zu.

Um dieses Annäherungsereignis allgemeiner zu gestalten, können Sie eine Schnittstelle oder ein Merkmal schreiben, das Sie in jeder Klasse implementieren, in der Sie eine stdClass umwandeln möchten.

Asiby
quelle
0

Konvertieren Sie es in ein Array, geben Sie das erste Element dieses Arrays zurück und setzen Sie den Rückgabeparameter auf diese Klasse. Jetzt sollten Sie die automatische Vervollständigung für diese Klasse erhalten, da sie als diese Klasse anstelle von stdclass neu konfiguriert wird.

/**
 * @return Order
 */
    public function test(){
    $db = new Database();

    $order = array();
    $result = $db->getConnection()->query("select * from `order` where productId in (select id from product where name = 'RTX 2070')");
    $data = $result->fetch_object("Order"); //returns stdClass
    array_push($order, $data);

    $db->close();
    return $order[0];
}
KUPPLER
quelle