Преобразование / приведение объекта stdClass к другому классу - PullRequest
83 голосов
/ 14 июля 2010

Я использую стороннюю систему хранения, которая возвращает мне только объекты stdClass, независимо от того, что я использую по какой-то непонятной причине. Поэтому мне любопытно узнать, есть ли способ привести / преобразовать объект stdClass в полноценный объект данного типа.

Например, что-то вроде:

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

Я просто преобразую stdClass в массив и передаю его конструктору BusinessClass, но, возможно, есть способ восстановить исходный класс, о котором я не знаю.

Примечание. Меня не интересует тип ответов «Измените свою систему хранения», поскольку это не представляет интереса. Пожалуйста, рассмотрите более академический вопрос о языковых способностях.

Приветствия

Ответы [ 9 ]

86 голосов
/ 14 июля 2010

См. Руководство о типе жонглирования о возможных приведениях.

Допустимые приведения:

  • (int), (целое число) - приведение к целому числу
  • (bool), (логическое) - приведение к логическому
  • (плавание), (двойное), (реальное) - приведение к плаванию
  • (строка) - приведение кстрока
  • (массив) - приведение к массиву
  • (объект) - приведение к объекту
  • (неустановлено) - приведение к 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), '"'), ':')
    ));
}
50 голосов
/ 22 марта 2012

Вы можете использовать вышеуказанную функцию для приведения не похожих объектов класса (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);
14 голосов
/ 20 января 2012

Чтобы переместить все существующие свойства 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, так как неизвестно, какие параметры ему понадобятся. Для вашего случая, вероятно, подходит.

10 голосов
/ 28 августа 2012

У меня очень похожая проблема. Упрощенное решение для отражения работало просто отлично для меня:

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 голосов
/ 12 декабря 2014

Надеюсь, что кто-нибудь найдет это полезным

// 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;
}
5 голосов
/ 17 июля 2013

Изменена функция для глубокого приведения (с использованием рекурсии)

/**
 * 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;
        }
    }
}
0 голосов
/ 30 мая 2019

Еще один подход.

Благодаря последней версии 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 в качестве единственного параметра для конструктора.

В конце концов, мы перебираем все элементы, содержащиеся в переданном объекте, и затем динамически присваиваем свойство объекта.

Чтобы сделать это событие-аппроксимацию более общим, вы можете написать интерфейс или черту, которую вы будете реализовывать в любом классе, где вы хотите иметь возможность приводить stdClass.

0 голосов
/ 12 ноября 2018

рассмотрите возможность добавления нового метода в 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);
0 голосов
/ 16 января 2018

Кстати: преобразование очень важно, если вы сериализованы, главным образом потому, что десериализация разбивает тип объектов и превращается в стандартный класс, включая объекты 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;
            }
        }
    }
}
...