将stdClass对象转换/广播到另一个类


89

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

例如:

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

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

注意:我对“更改存储系统”类型的答案不感兴趣,因为它不是关注点。请考虑更多有关语言能力的学术问题。

干杯


在伪代码示例之后的文章中对此进行了解释。我正在转换为数组并提供给自动构造函数。
强大的橡皮鸭2010年

@Adam Puza的答案比接受的答案中显示的破解要好得多。尽管我相信映射器仍将是首选方法
克里斯(Chris

那么如何PDOStatement::fetchObject完成这项任务?
William Entriken '18年

Answers:


88

有关可能的演员,请参见“ 类型变戏法手册

允许的强制转换为:

  • (int),(integer)-转换为整数
  • (布尔),(布尔)-转换为布尔
  • (float),(double),(real)-强制转换为float
  • (字符串)-强制转换为字符串
  • (数组)-强制转换为数组
  • (对象)-投射到对象
  • (未设置)-强制转换为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
这种技巧是一种聪明的技术。不会使用它,因为我目前解决问题的方式更加稳定,但仍然很有趣。
强大的橡皮鸭

1
__PHP_Incomplete_Class使用此方法最终可以得到一个对象(至少从PHP 5.6起)。
TiMESPLiNTER

1
@TiMESPLiNTER不,您不知道。参见codepad.org/spGkyLzL。确保在调用函数之前包含要转换为的类。
哥顿

@TiMESPLiNTER不确定您的意思。有效。这是一个示例\ Foo。
哥顿

是的,问题在于它附加了stdClass属性。因此,您有两个fooBars(example\Foo来自stdClass 的私有一个和来自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
我必须说,这是一个非常优雅的解决方案!我只是想知道它的缩放效果如何...反射使我恐惧。
西奥多·史密斯

嗨,亚当,这个解决方案在这里为我解决了类似的问题:stackoverflow.com/questions/35350585 / ... 如果您想获得一个简单的答案,请继续,我将对其进行核对。谢谢!
oucil

它不适用于从父类继承的属性。
托拉勒

我只是以此作为我的解决方案的基础,为我的数据库播种了具有已知ID的数据库,以便使用Behat进行API功能测试。我的问题是我的普通ID是生成的UUID,我不想只是为了测试层在我的实体中添加setId()方法,也不想加载Fixture文件并减慢测试速度。现在,我可以将其包含@Given the user :username has the id :id在功能中,并在上下文类中进行反射处理
nealio82 '18

2
很棒的解决方案,如果您需要避免调用构造函数,我确实想添加$destination = new $destination();可以互换的解决方案$destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();
Scuzzy

14

要将的所有现有属性移动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
通知其他尝试使用此方法的人。此功能有一个警告,即在其自身外部的实例化对象上进行迭代将无法在强制转换的对象内设置私有或受保护的属性。EG:设置公共$ authKey =''; 私人$ authKey =''; E_ERROR中的结果:类型1-无法访问私有属性RestQuery :: $ authKey
fyrye 2014年

有私有属性的stdClass吗?
Frug 2015年

@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)”吗?
kukinsula 2015年

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

还有另一种使用装饰器模式和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);

0

顺便说一句:如果要序列化,转换就非常重要,主要是因为反序列化会破坏对象的类型并变成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

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

0

还有另一种方法。

由于最新的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作为构造函数的唯一参数。

最终,我们遍历传递的对象中包含的每个项目,然后将其动态分配给对象的属性。

为了使该事件更加通用,您可以编写一个接口或一个Trait,该接口或Trait将在您希望能够转换stdClass的任何类中实现。


0

将其转换为数组,返回该数组的第一个元素,然后将return参数设置为该类。现在,您应该获得该类的自动完成功能,因为它将把它重新调节为该类而不是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];
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.