StdClass nesnesini başka bir sınıfa dönüştürme / dönüştürme


90

Belirsiz bir nedenden ötürü ne beslersem beslersem bana yalnızca stdClass nesnelerini döndüren bir üçüncü taraf depolama sistemi kullanıyorum. Bu nedenle, bir stdClass nesnesini belirli bir türden tam teşekküllü bir nesneye dönüştürmenin / dönüştürmenin bir yolu olup olmadığını merak ediyorum.

Örneğin şu satırlar boyunca bir şey:

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

Sadece stdClass'ı bir diziye atıyorum ve BusinessClass yapıcısına besliyorum, ancak belki de farkında olmadığım ilk sınıfı geri yüklemenin bir yolu olabilir.

Not: İlgi çekici nokta olmadığı için 'Depolama sisteminizi değiştirin' türü yanıtlarla ilgilenmiyorum. Lütfen bunu daha çok dil kapasiteleri ile ilgili akademik bir soru olarak düşünün.

Şerefe


Sözde kod örneğinden sonra yazımda açıklandı. Bir diziye döküm yapıyorum ve otomatik bir kurucuya besliyorum.
The Mighty Rubber Duck

@Adam Puza'nın cevabı, kabul edilen cevapta gösterilen hack'ten çok daha iyi. Bir haritacının hala tercih edilen yöntem olacağından emin olsam da
Chris

Peki PDOStatement::fetchObjectbu görevi nasıl yerine getiriyor?
William Entriken

Yanıtlar:


91

Olası yayınlarla ilgili Tip Hokkabazlık kılavuzuna bakın .

İzin verilen yayınlar:

  • (int), (tamsayı) - tam sayıya dönüştür
  • (bool), (boolean) - boolean'a çevrilir
  • (float), (double), (real) - float için döküm
  • (string) - dizgeye dönüştür
  • (dizi) - diziye dönüştür
  • (nesne) - nesneye çevir
  • (unset) - NULL'a çevir (PHP 5)

StdClass'tan başka bir somut sınıfa dönüştürme yapan bir Mapper yazmanız gerekir . Yapması çok zor olmamalı.

Ya da hilekâr bir ruh halindeyseniz, aşağıdaki kodu uyarlayabilirsiniz:

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

bir diziyi belirli bir sınıftaki bir nesneye sözde dizen. Bu, önce diziyi serileştirerek ve ardından serileştirilmiş verileri belirli bir sınıfı temsil edecek şekilde değiştirerek çalışır. Sonuç daha sonra bu sınıfın bir örneğine serileştirilmez. Ama dediğim gibi, bu hack, bu yüzden yan etkiler bekleyin.

Nesneden nesneye için kod,

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

9
Bu hack akıllıca bir tekniktir. Şu anki sorunu çözme yöntemim daha istikrarlı, ancak yine de ilginç olduğu için kullanmayacağım.
The Mighty Rubber Duck

1
__PHP_Incomplete_ClassBu yöntemi kullanarak bir nesne elde edersiniz (en azından PHP 5.6'dan itibaren).
TiMESPLiNTER

1
@TiMESPLiNTER hayır, yapmıyorsun. Codepad.org/spGkyLzL adresine bakın . İşlev çağrılmadan önce dönüştürülecek sınıfın dahil edildiğinden emin olun.
Gordon

@TiMESPLiNTER ne demek istediğinizden emin değilim. işe yaradı. bu bir örnek \ Foo şimdi.
Gordon

Evet sorun, stdClass özelliğini eklemesi. Yani iki fooBars'niz var (özel olan example\Foove stdClass'tan genel olan). Bunun yerine değeri değiştirir.
TiMESPLiNTER

53

Benzer olmayan sınıf nesnelerini çevirmek için yukarıdaki işlevi kullanabilirsiniz (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;
}

MİSAL:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

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

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

5
Bu oldukça zarif bir çözüm, söylemeliyim! Sadece ne kadar iyi ölçeklendiğini merak ediyorum ... Yansıma beni korkutuyor.
Theodore R. Smith

Hey Adam, bu çözüm burada benim için benzer bir sorunu çözdü: stackoverflow.com/questions/35350585/… Kolay bir cevap almak istiyorsanız, gidin ve kontrol edeceğim. Teşekkürler!
oucil

Üst sınıflardan miras alınan özellikler için çalışmaz.
Toilal

Bunu, veritabanımı Behat ile API işlevsel testi için bilinen kimliklerle tohumlama çözümümün temeli olarak kullandım. Sorunum, normal kimliklerimin UUID'ler oluşturması ve varlığıma yalnızca test katmanım uğruna bir setId () yöntemi eklemek istememem ve fikstür dosyalarını yüklemek ve testleri yavaşlatmak istemiyordum. Şimdi @Given the user :username has the id :idbenim özelliğime ekleyebilir ve bağlam sınıfındaki yansımalarla baş edebilirim
nealio82

2
Harika bir çözüm, $destination = new $destination();kurucuyu $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();çağırmaktan kaçınmanız gerekiyorsa bunun takas edilebileceğini eklemek istiyorum .
Scuzzy

15

A'nın tüm mevcut özelliklerini stdClassbelirli bir sınıf adına sahip yeni bir nesneye taşımak için:

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

Kullanım:

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

Çıktı:

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

Bu, newoperatör nedeniyle sınırlıdır, çünkü hangi parametrelere ihtiyaç duyacağı bilinmemektedir. Senin davan için muhtemelen uygun.


1
Bu yöntemi kullanmaya çalışan diğer kişileri bilgilendirmek için. Kendi dışında somutlaştırılmış bir nesnenin yinelemesinin, dönüştürülen nesne içinde özel veya korumalı özellikleri ayarlayamayacağı konusunda bu işlevin bir uyarısı vardır. EG: Genel $ authKey ayarlanıyor = ''; özel $ authKey = ''; Sonuçlar E_ERROR: tür 1 - Özel mülkiyete erişilemiyor RestQuery :: $ authKey
Will B.

Yine de özel özelliklere sahip bir stdClass?
Frug

Bu gereklilik özellikle OP gösterir @Frug ... dökme / belirli bir türden tam teşekküllü nesnesine bir stdClass nesnesi dönüştürmek
oucil

11

Çok benzer bir problemim var. Basitleştirilmiş yansıma çözümü benim için gayet iyi çalıştı:

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

Umarım birisi bunu faydalı bulmuştur

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

Neden "is_array ($ nesne) || is_array ($ nesne)" ifadesini açıklayabilir misiniz?
kukinsula

5

Derin döküm için değiştirilen işlev (özyineleme kullanarak)

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

Ve dekoratör kalıbını ve PHP'nin sihirli alıcı ve ayarlayıcılarını kullanan başka bir yaklaşım:

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

BusinessClass'a yeni bir yöntem eklemeyi düşünün:

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

o zaman $ stdClass'tan yeni bir BusinessClass oluşturabilirsiniz:

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

1

Yine başka bir yaklaşım.

Son PHP 7 sürümü sayesinde aşağıdakiler artık mümkündür.

$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

Bu örnekte, $ foo, yapıcı için tek parametre olarak bir dizi veya stdClass alan anonim bir sınıf olarak başlatılıyor.

Sonunda, aktarılan nesnede bulunan her öğe arasında döngü oluştururuz ve ardından dinamik olarak bir nesnenin özelliğini atarız.

Bu yaklaşım olayını daha genel hale getirmek için, stdClass'ı çevirebilmek istediğiniz herhangi bir sınıfta uygulayacağınız bir arabirim veya Özellik yazabilirsiniz.


0

BTW: Eğer serileştirdiyseniz dönüştürme çok önemlidir, çünkü serileştirmenin nesnelerin tipini bozması ve DateTime nesneleri dahil stdclass'a dönüşmesi.

@Jadrovski örneğini güncelledim, artık nesnelere ve dizilere izin veriyor.

misal

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

örnek dizi

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

kod: (özyinelemeli). Ancak, dizilerle özyinelemeli olup olmadığını bilmiyorum. Ek bir is_array eksik olabilir

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

Bunu bir diziye dönüştürün, o dizinin ilk öğesini döndürün ve dönüş parametresini bu sınıfa ayarlayın. Şimdi o sınıf için otomatik tamamlamayı almalısınız, çünkü onu stdclass yerine o sınıf olarak yeniden yapılandıracaktır.

/**
 * @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];
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.