stdClass 객체를 다른 클래스로 변환 / 캐스트


89

모호한 이유로 무엇을 공급하든 stdClass 객체 만 반환하는 타사 스토리지 시스템을 사용하고 있습니다. 그래서 stdClass 객체를 주어진 유형의 완전한 객체로 캐스트 / 변환하는 방법이 있는지 알고 싶습니다.

예를 들어 다음과 같은 내용이 있습니다.

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

stdClass를 배열로 캐스팅하고 BusinessClass 생성자에 공급하고 있지만 알지 못하는 초기 클래스를 복원하는 방법이있을 수 있습니다.

참고 : '스토리지 시스템 변경'유형의 답변은 관심 영역이 아니므로 관심이 없습니다. 언어 능력에 대한 학문적 질문이라고 생각하십시오.

건배


의사 코드 샘플 이후에 내 게시물에 설명되어 있습니다. 배열로 캐스팅하고 자동화 된 생성자에 공급하고 있습니다.
The Mighty Rubber Duck

@Adam Puza의 답변은 수락 된 답변에 표시된 해킹보다 훨씬 낫습니다. 나는 비록 확실히 매퍼는 여전히 선호하는 방법이 될 것입니다
크리스

그럼 어떻게 PDOStatement::fetchObject이 작업을 수행?
William Entriken

답변:


90

가능한 캐스트 에 대해서는 Type Juggling 에 대한 설명서를 참조하십시오 .

허용되는 캐스트는 다음과 같습니다.

  • (int), (integer)-정수로 캐스트
  • (bool), (boolean)-부울로 캐스트
  • (float), (double), (real)-부동으로 캐스팅
  • (문자열)-문자열로 캐스트
  • (배열)-배열로 캐스트
  • (객체)-객체로 캐스트
  • (설정되지 않음)-NULL로 캐스트 (PHP 5)

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을 참조하십시오 . 함수를 호출하기 전에 캐스트 할 클래스가 포함되었는지 확인하십시오.
Gordon

@TiMESPLiNTER가 무슨 뜻인지 잘 모르겠습니다. 작동했습니다. 이제 example \ Foo입니다.
Gordon

예, 문제는 stdClass 속성을 추가한다는 것입니다. 따라서 두 개의 fooBars가 있습니다 ( example\FoostdClass 의 개인 및 공용). 대신 값을 대체합니다.
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
그것은 매우 우아한 해결책입니다. 얼마나 잘 확장되는지 궁금합니다. 반사가 무섭습니다.
Theodore R. Smith

Adam 안녕하세요,이 솔루션은 저에게 비슷한 문제를 해결했습니다. stackoverflow.com/questions/35350585/… 쉬운 답을 얻고 싶다면 계속해서 확인하겠습니다. 감사!
oucil 16.02.12

부모 클래스에서 상속 된 속성에는 작동하지 않습니다.
Toilal

방금 Behat을 사용한 API 기능 테스트를 위해 알려진 ID로 데이터베이스를 시드하기위한 솔루션의 기반으로 이것을 사용했습니다. 내 문제는 내 일반 ID가 생성 된 UUID이고 테스트 레이어를 위해 엔티티에 setId () 메서드를 추가하고 싶지 않았고 픽스처 파일을로드하고 테스트 속도를 늦추고 싶지 않았습니다. 지금은 포함 할 수 있습니다 @Given the user :username has the id :id내 기능에, 컨텍스트 클래스의 반사와 그것을 처리
nealio82

2
훌륭한 솔루션, 생성자를 호출하지 않으려면 $destination = new $destination();교체 할 수있는 추가하고 싶습니다 $destination = ( new ReflectionClass( $destination ) )->newInstanceWithoutConstructor();.
Scuzzy

15

의 모든 기존 속성을 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
이 방법을 사용하려는 다른 사람에게 알리기 위해. 이 함수에는 자체 외부에서 인스턴스화 된 객체를 반복하는 것이 캐스트 된 객체 내에서 개인 또는 보호 속성을 설정할 수 없다는 점에서주의해야합니다. 예 : public $ authKey = ''설정; 개인 $ authKey = ''; E_ERROR 결과 : 유형 1-개인 속성 RestQuery :: $ authKey에 액세스 할 수 없음
Will B.

그래도 개인 속성이있는 stdClass?
Frug

이 요구 사항은 특별히 영업을 나타냅니다 @Frug ... 캐스트 / 지정된 형식의 전체 깃털, object에 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);

1

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에서 새 비즈니스 클래스를 만들 수 있습니다.

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

0

BTW : 직렬화 된 경우 변환은 매우 중요합니다. 주로 역 직렬화가 개체 유형을 분리하고 DateTime 개체를 포함한 stdclass로 변환되기 때문입니다.

@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

또 다른 접근법.

최신 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 이벤트를보다 포괄적으로 만들기 위해 stdClass를 캐스팅 할 수있는 모든 클래스에서 구현할 인터페이스 또는 Trait을 작성할 수 있습니다.


0

배열로 변환하고 해당 배열의 첫 번째 요소를 반환하고 반환 매개 변수를 해당 클래스로 설정합니다. 이제 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];
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.