Перетворити / передати об’єкт stdClass в інший клас


89

Я використовую сторонню систему зберігання, яка повертає мені лише об’єкти stdClass незалежно від того, що я ввожу з якихось незрозумілих причин. Тому мені цікаво дізнатись, чи є спосіб передати / перетворити об’єкт stdClass у повноцінний об’єкт даного типу.

Наприклад, щось на зразок:

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

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

Примітка: Мене не цікавить відповідь типу «Змінити систему зберігання», оскільки це не цікавить. Будь ласка, розгляньте це більше як академічне питання про мовні здібності.

Ура


Це пояснюється в моєму дописі після зразка псевдокоду. Я кидаю в масив і подаю до автоматизованого конструктора.
Могутня гумова качка

Відповідь @Adam Puza набагато краща, ніж хак, показаний у прийнятій відповіді. хоча я впевнений, що картограф все ще буде кращим методом
Кріс,

Ну як PDOStatement::fetchObjectвиконує це завдання?
Вільям Ентрікен

Відповіді:


88

Дивіться посібник з жонглювання типу щодо можливих ролей.

Дозволені ролі:

  • (int), (integer) - перекласти на ціле число
  • (bool), (boolean) - переведення в логічне значення
  • (float), (double), (real) - кинути плавати
  • (рядок) - перекласти на рядок
  • (масив) - приведення в масив
  • (об'єкт) - кинути на об'єкт
  • (не встановлено) - транслювати на 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 . Переконайтеся, що до виклику функції був включений клас, до якого потрібно передати.
Гордон,

@TiMESPLiNTER не впевнений, що ти маєш на увазі. це спрацювало. це приклад \ Foo зараз.
Гордон,

Так, проблема в тому, що він додає властивість 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
Це досить елегантне рішення, треба сказати! Мені просто цікаво, як добре це масштабується ... Відображення лякає мене.
Теодор Р. Сміт,

Привіт, Адаме, це рішення вирішило для мене подібну проблему тут: stackoverflow.com/questions/35350585/ ... Якщо ви хочете отримати легку відповідь, зверніться, і я перевірю це. Дякую!
oucil

Це не працює для властивостей, успадкованих від батьківських класів.
Toilal

Я щойно використав це як основу свого рішення для посіву своєї бази даних із відомими ідентифікаторами для функціонального тестування API за допомогою Behat. Моя проблема полягала в тому, що мої звичайні ідентифікатори генеруються UUID, і я не хотів додавати метод setId () у свою сутність лише заради мого рівня тестування, і я не хотів завантажувати файли світильників і гальмувати тести. Тепер я можу включити @Given the user :username has the id :idу свою функцію та обробляти це з відображенням у контекстному класі
nealio82

2
Чудове рішення, я хочу додати, що $destination = new $destination();можна поміняти місцями, $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();якщо вам потрібно уникати виклику конструктора.
Scuzzy

14

Щоб перемістити всі існуючі властивості 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: тип 1 - Не вдається отримати доступ до приватної власності RestQuery :: $ authKey
fyrye

Клас stdClass із приватними властивостями?
Frug

@Frug ОП спеціально вказує на цю вимогу ... кинути / перетворити об’єкт 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

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:

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

тоді ви можете створити новий BusinessClass з $ stdClass:

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

Зрештою, ми перебираємо всі елементи, що містяться в переданому об'єкті, і динамічно призначаємо їх властивості.

Щоб зробити цю подію Approch більш загальною, ви можете написати інтерфейс або 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];
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.