Ubah / transmisikan objek stdClass ke kelas lain


89

Saya menggunakan sistem penyimpanan pihak ketiga yang hanya mengembalikan objek stdClass apa pun yang saya berikan untuk beberapa alasan yang tidak jelas. Jadi saya penasaran untuk mengetahui apakah ada cara untuk mentransmisikan / mengonversi objek stdClass menjadi objek lengkap dari tipe tertentu.

Misalnya sesuatu di sepanjang baris:

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

Saya baru saja mentransmisikan stdClass ke dalam array dan memasukkannya ke konstruktor BusinessClass, tapi mungkin ada cara untuk mengembalikan kelas awal yang tidak saya sadari.

Catatan: Saya tidak tertarik dengan jenis jawaban 'Ubah sistem penyimpanan Anda' karena ini bukan poin yang menarik. Harap pertimbangkan ini lebih sebagai pertanyaan akademis tentang kemampuan bahasa.

Bersulang


Ini dijelaskan di posting saya setelah sampel kode pseudo. Saya mentransmisikan ke dalam array dan memberi makan ke konstruktor otomatis.
The Mighty Rubber Duck

Jawaban @Adam Puza jauh lebih baik daripada retasan yang ditunjukkan dalam jawaban yang diterima. meskipun saya yakin mapper akan tetap menjadi metode yang disukai
Chris

Nah bagaimana PDOStatement::fetchObjectmenyelesaikan tugas ini?
William Entriken

Jawaban:


90

Lihat manual Type Juggling tentang kemungkinan cast.

Pemeran yang diizinkan adalah:

  • (int), (integer) - dilemparkan ke integer
  • (bool), (boolean) - dilemparkan ke boolean
  • (float), (double), (real) - dilemparkan ke float
  • (string) - cast ke string
  • (array) - cast ke array
  • (objek) - melemparkan ke objek
  • (tidak disetel) - masukkan ke NULL (PHP 5)

Anda harus menulis Mapper yang melakukan pengecoran dari stdClass ke kelas beton lainnya. Seharusnya tidak terlalu sulit untuk dilakukan.

Atau, jika Anda sedang dalam mood hackish, Anda dapat menyesuaikan kode berikut:

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

yang melakukan pseudocast sebuah array ke objek dari kelas tertentu. Ini bekerja dengan terlebih dahulu membuat serial array dan kemudian mengubah data serial sehingga mewakili kelas tertentu. Hasilnya kemudian di-unserialized ke instance kelas ini. Tapi seperti yang saya katakan, itu hackish, jadi harapkan efek sampingnya.

Untuk objek ke objek, kodenya adalah

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

9
Peretasan ini adalah teknik yang cerdas. Tidak akan menggunakannya karena cara saya saat ini untuk menyelesaikan masalah lebih stabil, tetapi tetap menarik.
Bebek Karet Perkasa

1
Anda berakhir dengan sebuah __PHP_Incomplete_Classobjek dengan menggunakan metode ini (setidaknya pada PHP 5.6).
TiMESPLiNTER

1
@TiMESPLiNTER tidak, Anda tidak. Lihat codepad.org/spGkyLzL . Pastikan kelas yang akan ditransmisikan disertakan sebelum memanggil fungsi tersebut.
Gordon

@TiMESPLiNTER tidak yakin apa yang Anda maksud. itu berhasil. itu contoh \ Foo sekarang.
Gordon

Ya masalahnya adalah menambahkan properti stdClass. Jadi Anda memiliki dua fooBar(yang pribadi dari example\Foodan yang umum dari stdClass). Sebaliknya itu menggantikan nilai.
TiMESPLiNTER

53

Anda dapat menggunakan fungsi di atas untuk mentransmisikan objek kelas yang tidak serupa (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;
}

CONTOH:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

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

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

5
Itu solusi yang cukup elegan , harus saya katakan! Aku hanya ingin tahu seberapa baik skalanya ... Refleksi membuatku takut.
Theodore R. Smith

Hai Adam, solusi ini memecahkan masalah serupa untuk saya di sini: stackoverflow.com/questions/35350585/… Jika Anda ingin mendapatkan jawaban yang mudah, lanjutkan dan saya akan memeriksanya. Terima kasih!
oucil

Ini tidak berfungsi untuk properti yang diwarisi dari kelas induk.
Toilal

Saya baru saja menggunakan ini sebagai dasar solusi saya untuk melakukan seeding database saya dengan ID yang dikenal untuk pengujian fungsional API dengan Behat. Masalah saya adalah bahwa ID normal saya dihasilkan UUID dan saya tidak ingin menambahkan metode setId () di entitas saya hanya demi lapisan pengujian saya, dan saya tidak ingin memuat file perlengkapan dan memperlambat pengujian. Sekarang saya dapat memasukkan @Given the user :username has the id :idfitur saya, dan menanganinya dengan refleksi di kelas konteks
nealio82

2
Solusi hebat, saya ingin menambahkan yang $destination = new $destination();dapat ditukar dengan $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();jika Anda perlu menghindari pemanggilan konstruktor.
Scuzzy

15

Untuk memindahkan semua properti yang ada stdClasske objek baru dari nama kelas yang ditentukan:

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

Pemakaian:

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

Keluaran:

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

Ini dibatasi karena newoperator tidak diketahui parameter mana yang diperlukan. Untuk kasus Anda mungkin cocok.


1
Untuk memberi tahu orang lain yang mencoba menggunakan metode ini. Terdapat peringatan untuk fungsi ini karena melakukan iterasi pada objek yang dibuat di luar dirinya tidak akan dapat menyetel properti pribadi atau dilindungi dalam objek yang dicor. EG: Menyetel publik $ authKey = ''; ke pribadi $ authKey = ''; Hasil di E_ERROR: tipe 1 - Tidak dapat mengakses properti pribadi RestQuery :: $ authKey
Akan B.

Sebuah stdClass dengan properti pribadi?
Frug

@Frug OP secara khusus menunjukkan persyaratan ini ... melemparkan / mengubah objek stdClass menjadi objek lengkap dari tipe tertentu
oucil

11

Saya memiliki masalah yang sangat mirip. Solusi refleksi yang disederhanakan bekerja dengan baik untuk saya:

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

8

Semoga seseorang menemukan ini berguna

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

Bisakah kamu menjelaskan mengapa "is_array ($ object) || is_array ($ object)"?
kuk Peninsula

5

Fungsi yang diubah untuk pengecoran dalam (menggunakan rekursi)

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

2

Dan pendekatan lain menggunakan pola dekorator dan pengambil & penyetel sihir PHP:

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

1

pertimbangkan untuk menambahkan metode baru ke BusinessClass:

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

maka Anda dapat membuat BusinessClass baru dari $ stdClass:

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

0

BTW: Konversi sangat penting jika Anda dibuat berseri, terutama karena de-serialisasi memecah jenis objek dan berubah menjadi stdclass, termasuk objek DateTime.

Saya memperbarui contoh @Jadrovski, sekarang memungkinkan objek dan array.

contoh

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

contoh larik

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

kode: (rekursifnya). Namun, saya tidak tahu apakah itu rekursif dengan array. Mungkin ada is_array tambahan yang hilang

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

Namun pendekatan lain.

Berikut ini sekarang dimungkinkan berkat versi PHP 7 terbaru.

$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

Dalam contoh ini, $ foo sedang diinisialisasi sebagai kelas anonim yang mengambil satu array atau stdClass sebagai satu-satunya parameter untuk konstruktor.

Akhirnya, kami mengulang setiap item yang terdapat dalam objek yang diteruskan dan secara dinamis menetapkannya ke properti objek.

Untuk membuat acara pendekatan ini lebih umum, Anda dapat menulis antarmuka atau Sifat yang akan Anda implementasikan di kelas mana pun tempat Anda ingin mentransmisikan stdClass.


0

Ubah menjadi sebuah array, kembalikan elemen pertama dari array itu, dan setel parameter kembalian ke kelas itu. Sekarang Anda harus mendapatkan pelengkapan otomatis untuk kelas itu karena akan mengatur ulang sebagai kelas itu alih-alih stdclass.

/**
 * @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];
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.