【问题标题】:Convert/cast an stdClass object to another class将 stdClass 对象转换/转换为另一个类
【发布时间】:2011-03-15 16:58:18
【问题描述】:

我正在使用第三方存储系统,无论我出于某种模糊的原因输入什么,它都只会返回我的 stdClass 对象。所以我很想知道是否有办法将 stdClass 对象转换/转换为给定类型的完整对象。

例如:

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

我只是将 stdClass 转换为一个数组并将其提供给 BusinessClass 构造函数,但也许有一种方法可以恢复我不知道的初始类。

注意:我对“更改您的存储系统”类型的答案不感兴趣,因为它不是兴趣点。请把它当作一个关于语言能力的学术问题。

干杯

【问题讨论】:

  • 在伪代码示例之后我的帖子中有解释。我正在转换为一个数组并提供给一个自动构造函数。
  • @Adam Puza 的答案比接受的答案中显示的黑客要好得多。虽然我确信映射器仍然是首选方法
  • 那么PDOStatement::fetchObject 是如何完成这项任务的?

标签: php stdclass


【解决方案1】:

请参阅manual on Type Juggling 了解可能的演员表。

允许的演员表是:

  • (int), (integer) - 转换为整数
  • (bool), (boolean) - 转换为布尔值
  • (float), (double), (real) - 转换为浮点数
  • (字符串)- 转换为字符串
  • (数组) - 转换为数组
  • (对象)- 转换为对象
  • (未设置)- 强制转换为 NULL (PHP 5)

您必须 write a Mapper 执行从 stdClass 到另一个具体类的转换。应该不会太难做。

或者,如果你心情不好,你可以修改以下代码:

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

将数组伪转换为某个类的对象。这通过首先序列化数组然后更改序列化数据使其代表某个类来工作。然后将结果反序列化为此类的实例。但就像我说的那样,它是 hackish,所以期待副作用。

对于对象到对象,代码是

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

【讨论】:

  • 这个hack是一个聪明的技术。不会使用它,因为我目前解决问题的方法更稳定,但仍然很有趣。
  • 使用此方法(至少从 PHP 5.6 开始),您最终会得到一个 __PHP_Incomplete_Class 对象。
  • @TiMESPLiNTER 不,你没有。见codepad.org/spGkyLzL。确保在调用函数之前包含要转换的类。
  • @TiMESPLiNTER 不确定您的意思。有效。现在是一个例子\Foo。
  • 是的,问题是它附加了 stdClass 属性。所以你有两个fooBars(来自example\Foo 的私有一个和来自stdClass 的公共一个)。相反,它会替换值。
【解决方案2】:

您可以使用上述函数来转换不相似的类对象 (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);

【讨论】:

  • 这是一个相当优雅的解决方案,我必须说!我只是想知道它的扩展性如何......反射让我害怕。
  • 嘿亚当,这个解决方案在这里为我解决了一个类似的问题:stackoverflow.com/questions/35350585/… 如果你想得到一个简单的答案,请继续前进,我会检查它。谢谢!
  • 它不适用于从父类继承的属性。
  • 很好的解决方案,我想补充一点,如果您需要避免调用构造函数,可以将$destination = new $destination();$destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor(); 交换。
  • 正如@Scuzzy 指出的那样,您可能希望避免调用构造函数,因为它可能会引发异常或导致错误。毕竟我认为这个答案被低估了。
【解决方案3】:

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 运算符而受到限制,因为它不知道它需要哪些参数。可能适合您的情况。

【讨论】:

  • 通知其他尝试使用此方法的人。此函数有一个警告,即迭代自身外部的实例化对象将无法在强制转换的对象中设置私有或受保护的属性。 EG:设置 public $authKey='';到私人 $authKey='';结果 E_ERROR : type 1 -- 无法访问私有属性 RestQuery::$authKey
  • 一个具有私有属性的标准类?
  • @Frug OP 明确指出了这一要求... 将 stdClass 对象转换/转换为给定类型的完整对象
【解决方案4】:

我有一个非常相似的问题。简化的反射解决方案对我来说效果很好:

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

【讨论】:

    【解决方案5】:

    希望有人觉得这很有用

    // 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)" 吗?
    【解决方案6】:

    改变深度转换的功能(使用递归)

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

    【讨论】:

      【解决方案7】:

      考虑向 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;
      }
      

      然后你可以从 $stdClass 创建一个新的 BusinessClass:

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

      【讨论】:

        【解决方案8】:

        还有另一种使用装饰器模式和 PHP 魔术 getter & setter 的方法:

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

        【讨论】:

          【解决方案9】:

          另一种方法。

          由于最近的 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 的任何类中实现它。

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              将其转换为数组,返回该数组的第一个元素,并将返回参数设置为该类。现在您应该获得该类的自动完成功能,因为它会将其重新设置为该类而不是 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];
              }
              

              【讨论】:

                猜你喜欢
                • 2023-04-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-10-30
                • 2021-08-14
                • 2011-04-10
                • 2013-09-18
                • 2011-11-08
                相关资源
                最近更新 更多