Konwertuj / rzutuj obiekt stdClass na inną klasę

90

Używam systemu pamięci masowej innej firmy, który zwraca mi tylko obiekty stdClass bez względu na to, co wprowadzam z jakiegoś niejasnego powodu. Jestem więc ciekawy, czy istnieje sposób na rzutowanie / konwertowanie obiektu stdClass na pełnoprawny obiekt danego typu.

Na przykład coś w rodzaju:

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

Po prostu rzutuję stdClass na tablicę i przesyłam ją do konstruktora BusinessClass, ale może jest sposób na przywrócenie początkowej klasy, której nie jestem świadomy.

Uwaga: nie interesują mnie odpowiedzi typu „Zmień system przechowywania”, ponieważ nie jest to punkt zainteresowania. Proszę potraktować to bardziej jako akademickie pytanie dotyczące umiejętności językowych.

Twoje zdrowie

Potężna gumowa kaczka
źródło
Jest to wyjaśnione w moim poście po przykładzie pseudokodu. Rzucam do tablicy i przekazuję do automatycznego konstruktora.
The Mighty Rubber Duck,
Odpowiedź @Adama Puza jest znacznie lepsza niż hack pokazany w zaakceptowanej odpowiedzi. chociaż jestem pewien, że twórca map nadal byłby preferowaną metodą
Chris
Jak PDOStatement::fetchObjectzrealizować to zadanie?
William Entriken

Odpowiedzi:

90

Zobacz podręcznik Żonglowania typami na temat możliwych rzutów.

Dozwolone rzuty to:

  • (int), (integer) - rzut na liczbę całkowitą
  • (bool), (boolean) - rzutowanie na wartość logiczną
  • (float), (double), (real) - rzut na float
  • (string) - rzut na string
  • (tablica) - rzut do tablicy
  • (obiekt) - rzut na obiekt
  • (nieustawione) - rzut na NULL (PHP 5)

Musiałbyś napisać Mappera, który wykonuje rzutowanie ze stdClass na inną konkretną klasę. Nie powinno być to zbyt trudne.

Lub, jeśli jesteś w nastroju hackerskim, możesz dostosować następujący kod:

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

który pseudokastruje tablicę do obiektu określonej klasy. Działa to najpierw poprzez serializację tablicy, a następnie zmianę zserializowanych danych tak, aby reprezentowały określoną klasę. Wynik jest następnie odserializowany do instancji tej klasy. Ale jak powiedziałem, jest hackerski, więc spodziewaj się efektów ubocznych.

W przypadku obiektu do obiektu kod byłby

function objectToObject($instance, $className) {
    return unserialize(sprintf(
        'O:%d:"%s"%s',
        strlen($className),
        $className,
        strstr(strstr(serialize($instance), '"'), ':')
    ));
}
Gordon
źródło
9
Ten hack to sprytna technika. Nie będę go używać, ponieważ mój obecny sposób rozwiązania problemu jest bardziej stabilny, ale mimo to interesujący.
The Mighty Rubber Duck,
1
Otrzymujesz __PHP_Incomplete_Classobiekt używając tej metody (przynajmniej od PHP 5.6).
TIMESPLiNTER
1
@TiMESPLiNTER nie, nie musisz. Zobacz codepad.org/spGkyLzL . Upewnij się, że klasa do rzutowania została uwzględniona przed wywołaniem funkcji.
Gordon
@TiMESPLiNTER nie jestem pewien, co masz na myśli. zadziałało. to jest przykład \ Foo teraz.
Gordon
Tak, problem polega na tym, że dołącza właściwość stdClass. Więc masz dwa fooBars (prywatny od example\Fooi publiczny od stdClass). Zamiast tego zastępuje wartość.
TiMESPLiNTER
53

Możesz użyć powyższej funkcji do rzutowania nie podobnych obiektów klas (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;
}

PRZYKŁAD:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

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

$x = cast('A',$b);
$x = cast('B',$a);
Adam Puza
źródło
5
Muszę powiedzieć, że to całkiem eleganckie rozwiązanie! Zastanawiam się tylko, jak dobrze się skaluje ... Refleksja mnie przeraża.
Theodore R. Smith
Hej Adam, to rozwiązanie rozwiązało podobny problem dla mnie tutaj: stackoverflow.com/questions/35350585/… Jeśli chcesz otrzymać łatwą odpowiedź, idź dalej, a ja to zaznaczę. Dzięki!
oucil
Nie działa dla właściwości dziedziczonych z klas nadrzędnych.
Toilal
Właśnie użyłem tego jako podstawy mojego rozwiązania do zapełnienia mojej bazy danych znanymi identyfikatorami do testowania funkcjonalnego API z Behat. Mój problem polegał na tym, że moje normalne identyfikatory są generowanymi identyfikatorami UUID i nie chciałem dodawać metody setId () w mojej encji tylko ze względu na warstwę testową, a nie chciałem ładować plików z osprzętem i spowalniać testów. Teraz mogę włączyć @Given the user :username has the id :iddo mojej funkcji i obsłużyć ją z odbiciami w klasie kontekstu
nealio82
2
Świetne rozwiązanie, chcę dodać, że $destination = new $destination();można je zamienić, $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();jeśli chcesz uniknąć wywoływania konstruktora.
Scuzzy
15

Aby przenieść wszystkie istniejące właściwości a stdClassdo nowego obiektu o określonej nazwie klasy:

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

Stosowanie:

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

Wynik:

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

Jest to ograniczone ze względu na newoperatora, ponieważ nie wiadomo, jakich parametrów będzie potrzebować. Prawdopodobnie pasuje do twojego przypadku.

hakre
źródło
1
Poinformowanie innych osób próbujących skorzystać z tej metody. Istnieje zastrzeżenie dotyczące tej funkcji polegające na tym, że iteracja po utworzonym obiekcie poza nim samym nie będzie w stanie ustawić prywatnych lub chronionych właściwości w rzutowanym obiekcie. EG: Ustawienie public $ authKey = ''; do prywatnego $ authKey = ''; Wyniki w E_ERROR: typ 1 - Nie można uzyskać dostępu do właściwości prywatnej RestQuery :: $ authKey
Will B.
StdClass z prywatnymi właściwościami?
Frug
@Frug OP konkretnie wskazuje na to wymaganie ... rzutowanie / konwertowanie obiektu stdClass na pełnoprawny obiekt danego typu
oucil
11

Mam bardzo podobny problem. Uproszczone rozwiązanie odbicia działało dobrze dla mnie:

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;
}
Siergiej G.
źródło
8

Mam nadzieję, że ktoś uzna to za przydatne

// 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;
}
Wizzard
źródło
Czy możesz wyjaśnić, dlaczego „is_array ($ object) || is_array ($ object)”?
kukwysep
5

Zmieniono funkcję do głębokiego rzutowania (przy użyciu rekursji)

/**
 * 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;
        }
    }
}
Jadrovski
źródło
2

I jeszcze jedno podejście wykorzystujące wzorzec dekoratora i magiczne metody pobierające i ustawiające 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);
Benni
źródło
1

rozważ dodanie nowej metody do 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;
}

następnie możesz utworzyć nową BusinessClass z $ stdClass:

$converted = BusinessClass::fromStdClass($stdClass);
pgee70
źródło
1

Jeszcze inne podejście.

Dzięki najnowszej wersji PHP 7 możliwe jest teraz to.

$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

W tym przykładzie $ foo jest inicjowane jako klasa anonimowa, która przyjmuje jedną tablicę lub stdClass jako jedyny parametr dla konstruktora.

Ostatecznie przechodzimy przez każdy element zawarty w przekazanym obiekcie i dynamicznie przypisujemy go do właściwości obiektu.

Aby uczynić to wydarzenie bardziej ogólnym, możesz napisać interfejs lub cechę, którą zaimplementujesz w dowolnej klasie, w której chcesz mieć możliwość rzutowania stdClass.

asiby
źródło
0

BTW: Konwersja jest bardzo ważna, jeśli jesteś serializowany, głównie dlatego, że deserializacja przerywa typ obiektów i zamienia się w klasę standardową, w tym obiekty DateTime.

Zaktualizowałem przykład @Jadrovski, teraz zezwala na obiekty i tablice.

przykład

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

przykładowa tablica

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

kod: (rekurencyjny). Jednak nie wiem, czy jest to cykliczne z tablicami. Może brakować dodatkowej tablicy 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;
            }
        }
    }
}
magallanes
źródło
0

Przekonwertuj go na tablicę, zwróć pierwszy element tej tablicy i ustaw parametr powrotu na tę klasę. Teraz powinieneś otrzymać autouzupełnianie dla tej klasy, ponieważ zarejestruje ją jako tę klasę zamiast standardowej.

/**
 * @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];
}
SPRZĘGŁO
źródło