stdClassオブジェクトを別のクラスに変換/キャストする


89

何らかの理由で何を入力しても、stdClassオブジェクトのみを返すサードパーティのストレージシステムを使用しています。したがって、stdClassオブジェクトを特定のタイプの完全な本格的なオブジェクトにキャスト/変換する方法があるかどうか知りたいです。

たとえば、次のようなものがあります。

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

stdClassを配列にキャストしてBusinessClassコンストラクターにフィードしているだけですが、気付いていない初期クラスを復元​​する方法があるかもしれません。

注:「ストレージシステムの変更」タイプの回答は興味の対象ではないため、私は興味がありません。言語能力に関する学術的な質問と考えてください。

乾杯


疑似コードサンプルの後の私の投稿で説明されています。私は配列にキャストして、自動コンストラクターにフィードしています。
強大なゴム製のアヒル

@Adam Puzaの回答は、受け入れられた回答に示されているハックよりもはるかに優れています。私はマッパーがまだ望ましい方法だと確信していますが
Chris

さて、PDOStatement::fetchObjectこのタスクはどのようにして達成しますか?
William Entriken

回答:


88

タイプジャグリングマニュアルを参照してください可能なキャストの。

許可されるキャストは次のとおりです。

  • (int)、(integer)-整数にキャスト
  • (ブール)、(ブール)-ブールにキャスト
  • (float)、(double)、(real)-floatにキャストします
  • (文字列)-文字列にキャスト
  • (配列)-配列にキャスト
  • (オブジェクト)-オブジェクトにキャスト
  • (設定解除)-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
このハックは巧妙なテクニックです。問題を解決する私の現在の方法はより安定していますが、それでも興味深いので、それを使用しないでください。
マイティラバーダック

1
__PHP_Incomplete_Classこのメソッドを使用すると、オブジェクトが作成されます(少なくともPHP 5.6以降)。
TiMESPLiNTER 2015年

1
@TiMESPLiNTERいいえ、あなたはしません。codepad.org/spGkyLzLを参照してください。関数を呼び出す前に、キャストするクラスが含まれていることを確認してください。
ゴードン

@TiMESPLiNTER意味がわからない 出来た。これはexample \ Fooです。
ゴードン

ええ、問題はstdClassプロパティを追加することです。したがって、2つfooBarのがあります(example\FoosdClassからのプライベートとsdClassからのパブリック)。代わりに、値を置き換えます。
TiMESPLiNTER 2015年

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
それはかなりエレガントなソリューションです、私は言わなければなりません!どれだけうまく拡大縮小できるのか気になります... Reflectionは私を怖がらせます。
セオドアR.スミス

:ねえアダム、この解決策はここに私のために同様の問題を解決しstackoverflow.com/questions/35350585/...を あなたが簡単な答えを獲得したい場合は、以上の頭と私はそれをオフにチェックします。ありがとう!
oucil

親クラスから継承したプロパティでは機能しません。
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、2018年

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:public $ authKey = ''を設定する; プライベート$ authKey = ''に; E_ERRORの結果:タイプ1-プライベートプロパティRestQuery :: $ authKeyにアクセスできません
fyrye

プライベートプロパティを持つstdClassですか?
Frug

@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のマジックゲッター&セッターを使用したさらに別のアプローチ:

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

ところで、シリアル化されている場合、主に非シリアル化によってオブジェクトのタイプが破壊され、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

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は、1つの配列またはstdClassをコンストラクターの唯一のパラメーターとして取る匿名クラスとして初期化されています。

最終的には、渡されたオブジェクトに含まれる各アイテムをループして、オブジェクトのプロパティに動的に割り当てます。

このapprochイベントをより一般的にするために、stdClassをキャストできるようにしたいクラスに実装するインターフェースまたはトレイトを作成できます。


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.