แปลง / โยนวัตถุ stdClass ไปยังคลาสอื่น


90

ฉันใช้ระบบจัดเก็บข้อมูลของบุคคลที่สามที่ส่งคืนอ็อบเจ็กต์ stdClass เท่านั้นไม่ว่าฉันจะป้อนด้วยเหตุผลอะไรที่คลุมเครือก็ตาม ดังนั้นฉันจึงอยากรู้ว่ามีวิธีการโยน / แปลงวัตถุ stdClass ให้เป็นวัตถุประเภทใดประเภทหนึ่งหรือไม่

ตัวอย่างเช่นบางสิ่งบางอย่างตามแนวของ:

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

ฉันแค่แคสต์ stdClass ลงในอาร์เรย์และป้อนไปยังตัวสร้าง BusinessClass แต่อาจมีวิธีคืนค่าคลาสเริ่มต้นที่ฉันไม่ทราบ

หมายเหตุ: ฉันไม่สนใจคำตอบประเภท "เปลี่ยนระบบจัดเก็บข้อมูลของคุณ" เนื่องจากไม่ใช่ประเด็นที่น่าสนใจ โปรดพิจารณาคำถามเชิงวิชาการเพิ่มเติมเกี่ยวกับความสามารถทางภาษา

ไชโย


มันอธิบายไว้ในโพสต์ของฉันหลังจากตัวอย่างโค้ดหลอก ฉันกำลังส่งไปยังอาร์เรย์และป้อนให้กับตัวสร้างอัตโนมัติ
The Mighty Rubber Duck

คำตอบของ @Adam Puza ดีกว่าแฮ็คที่แสดงในคำตอบที่ยอมรับ แม้ว่าฉันแน่ใจว่าผู้ทำแผนที่จะยังคงเป็นวิธีที่เราต้องการก็ตาม
คริส

จะPDOStatement::fetchObjectทำงานนี้ให้สำเร็จได้อย่างไร?
William Entriken

คำตอบ:


91

ดูคู่มือเกี่ยวกับ Type Jugglingในการร่ายที่เป็นไปได้

อนุญาตให้ใช้นักแสดงได้:

  • (int), (จำนวนเต็ม) - ส่งเป็นจำนวนเต็ม
  • (บูล), (บูลีน) - ส่งไปยังบูลีน
  • (ลอย), (คู่), (จริง) - โยนให้ลอย
  • (สตริง) - ส่งไปยังสตริง
  • (array) - ส่งไปยังอาร์เรย์
  • (วัตถุ) - ส่งไปยังวัตถุ
  • (ไม่ได้ตั้งค่า) - ส่งเป็น NULL (PHP 5)

คุณจะต้องเขียน Mapperที่ทำการหล่อจาก stdClass ไปยังคลาสคอนกรีตอื่น ไม่ควรทำยากเกินไป

หรือหากคุณอยู่ในอารมณ์แฮ็กคุณสามารถปรับรหัสต่อไปนี้:

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

ซึ่งหลอกเอาอาร์เรย์ไปยังออบเจ็กต์ของคลาสหนึ่ง ๆ สิ่งนี้ทำงานโดยการทำให้อนุกรมอาร์เรย์เป็นลำดับแรกจากนั้นจึงเปลี่ยนข้อมูลที่เป็นอนุกรมเพื่อให้แสดงถึงคลาสที่แน่นอน ผลลัพธ์จะถูกยกเลิกการกำหนดค่าเป็นอินสแตนซ์ของคลาสนี้แล้ว แต่อย่างที่บอกว่ามันแฮ็กดังนั้นคาดว่าจะมีผลข้างเคียง

สำหรับวัตถุที่จะคัดค้านรหัสจะเป็น

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

9
การแฮ็กนี้เป็นเทคนิคที่ชาญฉลาด จะไม่ใช้มันเนื่องจากวิธีการแก้ปัญหาในปัจจุบันของฉันมีเสถียรภาพมากขึ้น แต่ก็น่าสนใจ
The Mighty Rubber Duck

1
คุณจบลงด้วย__PHP_Incomplete_Classวัตถุโดยใช้วิธีนี้ (อย่างน้อยที่สุดคือ PHP 5.6)
TiMESPLiNTER

1
@TiMESPLiNTER ไม่คุณทำไม่ได้ ดูcodepad.org/spGkyLzL ตรวจสอบให้แน่ใจว่าคลาสที่จะแคสต์รวมอยู่ก่อนที่จะเรียกใช้ฟังก์ชัน
Gordon

@TiMESPLiNTER ไม่แน่ใจว่าคุณหมายถึงอะไร มันได้ผล มันเป็นตัวอย่าง \ Foo ตอนนี้
Gordon

ใช่ปัญหาคือมันต่อท้ายคุณสมบัติ stdClass ดังนั้นคุณจึงมีสองfooBars (หนึ่งส่วนตัวจากexample\Fooและหนึ่งสาธารณะจาก stdClass) แทนค่านั้นแทน
TiMESPLiNTER

53

คุณสามารถใช้ฟังก์ชั่นด้านบนสำหรับการคัดเลือกออบเจ็กต์คลาสที่ไม่เหมือนกัน (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;
}

ตัวอย่าง:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

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

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

5
นั่นเป็นวิธีแก้ปัญหาที่สวยหรูต้องบอกเลย! ฉันแค่สงสัยว่ามันชั่งดีแค่ไหน ... การสะท้อนกลับทำให้ฉันกลัว
Theodore R.Smith

เฮ้อดัมวิธีนี้แก้ปัญหาที่คล้ายกันสำหรับฉันที่นี่: stackoverflow.com/questions/35350585/… หากคุณต้องการให้คะแนนคำตอบง่ายๆตรงไปและฉันจะตรวจสอบ ขอบคุณ!
oucil

ใช้ไม่ได้กับคุณสมบัติที่สืบทอดมาจากคลาสพาเรนต์
Toilal

ฉันเพิ่งใช้สิ่งนี้เป็นพื้นฐานในการแก้ปัญหาสำหรับการเริ่มต้นฐานข้อมูลด้วย ID ที่รู้จักสำหรับการทดสอบการทำงานของ API ด้วย Behat ปัญหาของฉันคือ ID ปกติของฉันถูกสร้างขึ้น UUID และฉันไม่ต้องการเพิ่มเมธอด setId () ในเอนทิตีของฉันเพียงเพื่อประโยชน์ของเลเยอร์การทดสอบของฉันและฉันไม่ต้องการโหลดไฟล์การแข่งขันและทำให้การทดสอบช้าลง ตอนนี้ฉันสามารถรวม@Given the user :username has the id :idไว้ในคุณสมบัติของฉันและจัดการกับมันด้วยการสะท้อนในคลาสบริบท
nealio82

2
ทางออกที่ดีฉันต้องการเพิ่มที่$destination = new $destination();สามารถสลับได้$destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();หากคุณต้องการหลีกเลี่ยงการเรียกตัวสร้าง
Scuzzy

15

ในการย้ายคุณสมบัติที่มีอยู่ทั้งหมดของ a stdClassไปยังอ็อบเจ็กต์ใหม่ของชื่อคลาสที่ระบุ:

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

การใช้งาน:

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

เอาท์พุต:

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

สิ่งนี้ถูก จำกัด เนื่องจากตัวnewดำเนินการเนื่องจากไม่ทราบว่าต้องการพารามิเตอร์ใด สำหรับกรณีของคุณอาจเหมาะสม


1
เพื่อแจ้งให้ผู้อื่นพยายามใช้วิธีนี้ มีข้อแม้สำหรับฟังก์ชันนี้คือการวนซ้ำบนอ็อบเจ็กต์ที่สร้างอินสแตนซ์ภายนอกตัวเองจะไม่สามารถตั้งค่าคุณสมบัติส่วนตัวหรือได้รับการป้องกันภายในวัตถุที่ถูกร่าย เช่น: การตั้งค่าสาธารณะ $ authKey = ''; เป็นส่วนตัว $ authKey = ''; ผลลัพธ์ใน E_ERROR: type 1 - ไม่สามารถเข้าถึงคุณสมบัติส่วนตัว RestQuery :: $ authKey
Will B.

stdClass ที่มีคุณสมบัติส่วนตัว?
Frug

@Frug OP ระบุข้อกำหนดนี้โดยเฉพาะ ... โยน / แปลงวัตถุ stdClass เป็นวัตถุเต็มรูปแบบของประเภทที่กำหนด
oucil

11

ฉันมีปัญหาที่คล้ายกันมาก โซลูชันการสะท้อนที่เรียบง่ายใช้งานได้ดีสำหรับฉัน:

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

หวังว่าจะมีใครรู้ว่ามีประโยชน์บ้าง

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

คุณอธิบายได้ไหมว่าทำไม "is_array ($ object) || is_array ($ object)"
kuk Peninsula

5

เปลี่ยนฟังก์ชันสำหรับการหล่อแบบลึก (โดยใช้การเรียกซ้ำ)

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

และอีกแนวทางหนึ่งโดยใช้รูปแบบมัณฑนากรและ PHPs magic getter & setters:

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

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

จากนั้นคุณสามารถสร้าง BusinessClass ใหม่จาก $ stdClass:

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

1

อีกวิธีหนึ่ง

ต่อไปนี้เป็นไปได้ด้วย 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

ในตัวอย่างนี้ $ foo จะถูกเตรียมใช้งานเป็นคลาสที่ไม่ระบุชื่อที่ใช้อาร์เรย์หรือ stdClass เป็นพารามิเตอร์เดียวสำหรับตัวสร้าง

ในที่สุดเราวนซ้ำแต่ละรายการที่มีอยู่ในวัตถุที่ส่งผ่านและกำหนดแบบไดนามิกให้กับคุณสมบัติของวัตถุ

ในการทำให้เหตุการณ์ Approch นี้เป็นแบบทั่วไปคุณสามารถเขียนอินเทอร์เฟซหรือ Trait ที่คุณจะนำไปใช้ในคลาสใดก็ได้ที่คุณต้องการให้สามารถใช้ stdClass ได้


0

BTW: การแปลงมีความสำคัญอย่างมากหากคุณเป็นซีเรียลไลเซชันส่วนใหญ่เป็นเพราะการแยกอนุกรมแบ่งประเภทของวัตถุและเปลี่ยนเป็น stdclass รวมถึงวัตถุ DateTime

ฉันอัปเดตตัวอย่างของ @Jadrovski ตอนนี้อนุญาตให้ออบเจ็กต์และอาร์เรย์

ตัวอย่าง

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

อาร์เรย์ตัวอย่าง

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

รหัส: (เรียกซ้ำ) อย่างไรก็ตามฉันไม่รู้ว่ามันวนซ้ำกับอาร์เรย์หรือไม่ อาจเป็นเพราะไม่มี 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

แปลงเป็นอาร์เรย์ส่งคืนองค์ประกอบแรกของอาร์เรย์นั้นและตั้งค่าพารามิเตอร์ส่งกลับเป็นคลาสนั้น ตอนนี้คุณควรได้รับการเติมข้อความอัตโนมัติสำหรับคลาสนั้นเนื่องจากจะกำหนดค่าใหม่เป็นคลาสนั้นแทน 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];
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.