Chuyển đổi / truyền một đối tượng stdClass sang một lớp khác


89

Tôi đang sử dụng hệ thống lưu trữ của bên thứ ba chỉ trả về cho tôi các đối tượng stdClass bất kể tôi nạp gì vào vì một số lý do khó hiểu. Vì vậy, tôi tò mò muốn biết liệu có cách nào để ép / chuyển đổi một đối tượng stdClass thành một đối tượng chính thức đầy đủ của một kiểu nhất định hay không.

Ví dụ: một cái gì đó dọc theo dòng:

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

Tôi chỉ đang truyền stdClass vào một mảng và cấp nó cho phương thức khởi tạo BusinessClass, nhưng có thể có một cách để khôi phục lớp ban đầu mà tôi không biết.

Lưu ý: Tôi không quan tâm đến loại câu trả lời 'Thay đổi hệ thống lưu trữ của bạn' vì nó không phải là điểm quan tâm. Vui lòng coi đây là một câu hỏi học thuật về năng lực ngôn ngữ.

Chúc mừng


Nó được giải thích trong bài đăng của tôi sau mẫu mã giả. Tôi đang truyền vào một mảng và cấp cho một hàm tạo tự động.
The Mighty Rubber Duck,

Câu trả lời của @Adam Puza tốt hơn nhiều so với câu trả lời được chấp nhận. mặc dù tôi chắc chắn một mapper vẫn sẽ là phương pháp ưa thích
Chris

Làm thế nào để PDOStatement::fetchObjecthoàn thành nhiệm vụ này?
William Entriken

Câu trả lời:


88

Xem hướng dẫn về Type Juggling để biết các phôi có thể.

Các phôi được phép là:

  • (int), (integer) - ép kiểu sang số nguyên
  • (bool), (boolean) - truyền sang boolean
  • (float), (double), (real) - ép kiểu float
  • (string) - truyền sang chuỗi
  • (mảng) - truyền sang mảng
  • (đối tượng) - truyền tới đối tượng
  • (chưa đặt) - truyền sang NULL (PHP 5)

Bạn sẽ phải viết một Mapper thực hiện việc truyền từ stdClass sang một lớp cụ thể khác. Không quá khó để làm.

Hoặc, nếu bạn đang có tâm trạng khó chịu, bạn có thể điều chỉnh đoạn mã sau:

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

mà giả tạo một mảng cho một đối tượng của một lớp nhất định. Điều này hoạt động bằng cách đầu tiên tuần tự hóa mảng và sau đó thay đổi dữ liệu được tuần tự hóa để nó đại diện cho một lớp nhất định. Sau đó, kết quả là chưa được chứng minh hóa thành một thể hiện của lớp này. Nhưng như tôi đã nói, đó là hackish, vì vậy hãy mong đợi tác dụng phụ.

Đối với đối tượng đối tượng, mã sẽ là

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

9
Hack này là một kỹ thuật thông minh. Sẽ không sử dụng nó vì cách giải quyết vấn đề hiện tại của tôi ổn định hơn, nhưng dù sao cũng rất thú vị.
Mighty Cao su Duck

1
Bạn kết thúc với một __PHP_Incomplete_Classđối tượng bằng cách sử dụng phương pháp này (ít nhất là PHP 5.6).
TiMESPLiNTER

1
@TiMESPLiNTER không, bạn không. Xem codepad.org/spGkyLzL . Đảm bảo rằng lớp để truyền đến đã được bao gồm trước khi gọi hàm.
Gordon

@TiMESPLiNTER không chắc ý bạn. nó đã làm việc. nó là một ví dụ \ Foo bây giờ.
Gordon

Vâng, vấn đề là nó thêm thuộc tính stdClass. Vì vậy, bạn có hai fooBars (riêng tư từ example\Foovà công khai từ stdClass). Thay vào đó nó thay thế giá trị.
TiMESPLiNTER

53

Bạn có thể sử dụng hàm trên để truyền các đối tượng lớp không tương tự (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;
}

THÍ DỤ:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

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

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

5
Đó là một giải pháp khá thanh lịch , tôi phải nói! Tôi chỉ tự hỏi nó có quy mô như thế nào ... Sự phản chiếu làm tôi sợ hãi.
Theodore R. Smith

Này Adam, giải pháp này đã giải quyết một vấn đề tương tự cho tôi ở đây: stackoverflow.com/questions/35350585/… Nếu bạn muốn có một câu trả lời dễ dàng, hãy tiếp tục và tôi sẽ kiểm tra nó. Cảm ơn!
oucil

Nó không hoạt động đối với các thuộc tính được kế thừa từ các lớp cha.
Toilal

Tôi vừa sử dụng điều này làm cơ sở cho giải pháp của tôi để gieo vào cơ sở dữ liệu của tôi với các ID đã biết để kiểm tra chức năng API với Behat. Vấn đề của tôi là các ID thông thường của tôi được tạo UUID và tôi không muốn thêm phương thức setId () vào thực thể của mình chỉ vì lợi ích của lớp thử nghiệm và tôi không muốn tải các tệp cố định và làm chậm các thử nghiệm. Bây giờ tôi có thể bao gồm @Given the user :username has the id :idtrong tính năng của tôi, và xử lý nó với phản xạ trong lớp bối cảnh
nealio82

2
Giải pháp tuyệt vời, tôi muốn thêm nó $destination = new $destination();có thể được hoán đổi với $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();nếu bạn cần tránh gọi hàm tạo.
Scuzzy

14

Để di chuyển tất cả các thuộc tính hiện có của stdClass đối tượng mới của một tên lớp đã chỉ định:

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

Sử dụng:

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

Đầu ra:

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

Điều này bị hạn chế do người newvận hành không biết nó sẽ cần tham số nào. Đối với trường hợp của bạn có lẽ phù hợp.


1
Để thông báo cho những người khác đang cố gắng sử dụng phương pháp này. Có một lưu ý đối với hàm này là việc lặp lại một đối tượng được khởi tạo bên ngoài chính nó sẽ không thể thiết lập các thuộc tính riêng tư hoặc được bảo vệ bên trong đối tượng được ép kiểu. EG: Đặt công khai $ authKey = ''; thành riêng tư $ authKey = ''; Kết quả tìm kiếm trong E_ERROR: loại 1 - Không thể truy cập RestQuery sở hữu tư nhân :: $ authKey
fyrye

Một stdClass với các thuộc tính riêng tư?
Frug

@Frug Các OP đặc biệt chỉ ra yêu cầu này ... dàn diễn viên / chuyển đổi một đối tượng stdClass vào một đối tượng chính thức đầy đủ của một loại nhất định
oucil

11

Tôi có một vấn đề rất tương tự. Giải pháp phản chiếu đơn giản hoạt động tốt đối với tôi:

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

Hy vọng rằng ai đó thấy điều này hữu ích

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

Bạn có thể giải thích tại sao "is_array ($ object) || is_array ($ object)"?
kukinsula,

5

Đã thay đổi chức năng để truyền sâu (sử dụng đệ quy)

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

Và một cách tiếp cận khác bằng cách sử dụng mẫu trang trí và bộ lấy & thiết lập ma thuật PHPs:

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

0

BTW: Chuyển đổi rất quan trọng nếu bạn được tuần tự hóa, chủ yếu là do quá trình hủy tuần tự hóa phá vỡ loại đối tượng và biến thành lớp mẫu, bao gồm các đối tượng DateTime.

Tôi đã cập nhật ví dụ về @Jadrovski, bây giờ nó cho phép các đối tượng và mảng.

thí dụ

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

mảng ví dụ

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

mã: (đệ quy của nó). Tuy nhiên, tôi không biết liệu nó có đệ quy với mảng hay không. Có thể nó thiếu một is_array bổ sung

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

xem xét thêm một phương thức mới vào 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;
}

thì bạn có thể tạo một BusinessClass mới từ $ stdClass:

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

0

Tuy nhiên, một cách tiếp cận khác.

Hiện có thể thực hiện được điều sau nhờ phiên bản PHP 7 gần đây.

$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

Trong ví dụ này, $ foo đang được khởi tạo dưới dạng một lớp ẩn danh lấy một mảng hoặc stdClass làm tham số duy nhất cho hàm tạo.

Cuối cùng, chúng tôi lặp qua từng mục có trong đối tượng đã truyền và gán động sau đó cho thuộc tính của đối tượng.

Để làm cho sự kiện phê duyệt này chung chung hơn, bạn có thể viết một giao diện hoặc một Đặc điểm mà bạn sẽ triển khai trong bất kỳ lớp nào mà bạn muốn để có thể truyền một Phân lớp.


0

Chuyển nó thành một mảng, trả về phần tử đầu tiên của mảng đó và đặt tham số trả về cho lớp đó. Bây giờ bạn sẽ nhận được tự động hoàn thành cho lớp đó vì nó sẽ đăng ký lại nó dưới dạng lớp đó thay vì lớp.

/**
 * @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];
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.