web-dev-qa-db-ja.com

PHP-オーバーロードされたプロパティの間接的な変更

私はこの質問が何度も聞かれたことを知っていますが、回避策に対する本当の答えはありません。たぶん私の特定のケースに1つあります。

マジックメソッド__get()を使用して他のオブジェクトを遅延ロードするマッパークラスを作成しています。次のようになります。

_public function __get ( $index )
{
    if ( isset ($this->vars[$index]) )
    {
        return $this->vars[$index];
    }

    // $index = 'role';
    $obj = $this->createNewObject ( $index );

    return $obj;
}
_

私のコードでは:

_$user = createObject('user');
$user->role->rolename;
_

これは今のところ機能します。 Userオブジェクトには 'role'というプロパティがないため、マジック__get()メソッドを使用してそのオブジェクトを作成し、 'role'オブジェクトからプロパティを返します。

しかし、「ロール名」を変更しようとすると:

_$user = createUser();
$user->role->rolename = 'Test';
_

次に、次のエラーが表示されます。

注意:オーバーロードされたプロパティの間接的な変更は効果がありません

これがPHPのバグなのか、それとも "期待される動作"なのかはわかりませんが、いずれにしても期待どおりに動作しません。 ..遅延読み込みオブジェクトのプロパティをどのように変更できるのですか??


編集:

実際の問題は、複数のオブジェクトを含む配列を返すときにのみ発生するようです。

問題を再現するサンプルコードを追加しました。

http://codepad.org/T1iPZm9t

これを実際にPHP環境で実行する必要があります。実際に 'エラー'が表示されます。しかし、ここで本当に興味深いことがあります。

オブジェクトのプロパティを変更しようとすると、「オーバーロードされたプロパティを変更できない」という通知が表示されます。しかし、その後プロパティをエコーすると、実際にDID値を変更する...本当に奇妙な...

51
w00

よろしくお願いします

走る

class Sample extends Creator {

}

$a = new Sample ();
$a->role->rolename = 'test';
echo  $a->role->rolename , PHP_EOL;
$a->role->rolename->am->love->php = 'w00';
echo  $a->role->rolename  , PHP_EOL;
echo  $a->role->rolename->am->love->php   , PHP_EOL;

出力

test
test
w00

使用クラス

abstract class Creator {
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        return $this->{$name};
    }

    public function __set($name, $value) {
        $this->{$name} = new Value ( $name, $value );
    }



}

class Value extends Creator {
    private $name;
    private $value;
    function __construct($name, $value) {
        $this->name = $name;
        $this->value = $value;
    }

    function __toString()
    {
        return (string) $this->value ;
    }
}      

編集:要求に応じて新しいアレイをサポート

class Sample extends Creator {

}

$a = new Sample ();
$a->role = array (
        "A",
        "B",
        "C" 
);


$a->role[0]->Nice = "OK" ;

print ($a->role[0]->Nice  . PHP_EOL);

$a->role[1]->Nice->ok = array("foo","bar","die");

print ($a->role[1]->Nice->ok[2]  . PHP_EOL);


$a->role[2]->Nice->raw = new stdClass();
$a->role[2]->Nice->raw->name = "baba" ;

print ($a->role[2]->Nice->raw->name. PHP_EOL);

出力

 Ok die baba

変更されたクラス

abstract class Creator {
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        return $this->{$name};
    }

    public function __set($name, $value) {
        if (is_array ( $value )) {
            array_walk ( $value, function (&$item, $key) {
                $item = new Value ( $key, $item );
            } );
        }
        $this->{$name} = $value;

    }

}

class Value {
    private $name ;
    function __construct($name, $value) {
        $this->{$name} = $value;
        $this->name = $value ;
    }

    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }

        if ($name == $this->name) {
            return $this->value;
        }

        return $this->{$name};
    }

    public function __set($name, $value) {
        if (is_array ( $value )) {
            array_walk ( $value, function (&$item, $key) {
                $item = new Value ( $key, $item );
            } );
        }
        $this->{$name} = $value;
    }

    public function __toString() {
        return (string) $this->name ;
    }   
}
15
Baba

必要なことは、__get関数の前に「&」を追加して、参照として渡すことだけです。

public function &__get ( $index )

しばらくこれに苦労しました。

89
VinnyD

これと同じエラーが発生しました。コード全体がなければ、修正方法を正確に特定することは困難ですが、それは__set関数がないためです。

私が過去にそれを回避した方法は、次のようなことをしたことです。

$user = createUser();
$role = $user->role;
$role->rolename = 'Test';

これを行う場合:

echo $user->role->rolename;

「テスト」が表示されます

9
Phil W

私はこれを行うためにこの通知を受け取っていました:

$var = reset($myClass->my_magic_property);

これにより修正されました。

$tmp = $myClass->my_magic_property;
$var = reset($tmp);
3
Andrew

私はこの議論に非常に遅れていますが、これは将来誰かに役立つかもしれないと思いました。

私は同様の状況に直面していました。変数の設定解除とリセットを気にしない人にとって最も簡単な回避策はそうすることです。これが機能しない理由は、他の回答とphp.netマニュアルから明らかです。私のために働いた最も簡単な回避策は

仮定、想定。推測:

  1. $objectは、基本クラスからオーバーロードされた__getおよび__setを持つオブジェクトであり、変更する自由はありません。
  2. shippingDataは、たとえばフィールドを変更したい配列です。 :- 電話番号

// First store the array in a local variable.
$tempShippingData = $object->shippingData;

unset($object->shippingData);

$tempShippingData['phone_number'] = '888-666-0000' // what ever the value you want to set

$object->shippingData = $tempShippingData; // this will again call the __set and set the array variable

unset($tempShippingData);

注:この解決策は、問題を解決して変数をコピーするための可能な迅速な回避策の1つです。配列が非常に巨大な場合は、__getメソッドを強制的に書き換えて、大きな配列のかなり高価な参照を返すようにしてください。

3
Althaf

W00と同じ問題に遭遇しましたが、この問題(E_NOTICE)が発生したコンポーネントの基本機能を書き換える自由がありませんでした。基本タイプ array() の代わりに ArrayObject を使用して問題を修正できました。これによりオブジェクトが返され、デフォルトで参照により返されます。

1

VinnyD に同意します。必要な結果を参照として返すようにするために、__ get関数の前に "&"を追加する必要があります。

public function &__get ( $propertyname )

ただし、次の2つに注意してください。

1)またする必要があります

return &$something;

または、まだ参照ではなく値を返している可能性があります...

2)__getが参照を返す場合、これは対応する__setが決して呼び出されないことを意味することを忘れないでください。これは、代わりに呼び出される__getによって返される参照を使用してphpがこれを解決するためです!

そう:

$var = $object->NonExistentArrayProperty; 

__getが呼び出され、__ getには&__ getがあり、&$ somethingが返されるため、$ varは、意図したとおり、オーバーロードされたプロパティへの参照になります...

$object->NonExistentArrayProperty = array(); 

期待どおりに動作し、__ setが期待どおりに呼び出されます...

しかし:

$object->NonExistentArrayProperty[] = $value;

または

$object->NonExistentArrayProperty["index"] = $value;

オーバーロードされた配列プロパティで要素が正しく追加または変更されるという意味で期待どおりに動作しますが、__ setは呼び出されません:代わりに__getが呼び出されます!

これらの2つの呼び出しは、&__ getを使用せずに&$ somethingを返す場合は機能しませんが、この方法で機能している間は、__ setを呼び出すことはなく、常に__getを呼び出します。

これが私が参照を返すことにした理由です

return &$something;

$ somethingがarray()の場合、またはオーバーロードされたプロパティに特別なセッターメソッドがなく、代わりに値を返す場合

return $something;

$ somethingが配列ではない場合、または特別な設定関数がある場合。

いずれにせよ、これは私にとって適切に理解するのは非常に難しいものでした! :)

1
Shores

これは、PHPがオーバーロードされたプロパティを、変更可能または参照渡しではないという点で扱う方法が原因で発生しています。

オーバーロードの詳細については、 manual をご覧ください。

この問題を回避するには、__set関数を使用するか、createObjectメソッドを作成します。

以下は__get__setであり、あなたと同じような状況への回避策を提供します。必要に応じて__setを変更するだけです。

__getは実際には変数を返さないことに注意してください。オブジェクトに変数を設定すると、オーバーロードされなくなります。

/**
 * Get a variable in the event.
 *
 * @param  mixed  $key  Variable name.
 *
 * @return  mixed|null
 */
public function __get($key)
{
    throw new \LogicException(sprintf(
        "Call to undefined event property %s",
        $key
    ));
}

/**
 * Set a variable in the event.
 *
 * @param  string  $key  Name of variable
 *
 * @param  mixed  $value  Value to variable
 *
 * @return  boolean  True
 */
public function __set($key, $value)
{
    if (stripos($key, '_') === 0 && isset($this->$key)) {
        throw new \LogicException(sprintf(
            "%s is a read-only event property", 
            $key
        ));
    }
    $this->$key = $value;
    return true;
}

以下が可能になります:

$object = new obj();
$object->a = array();
$object->a[] = "b";
$object->v = new obj();
$object->v->a = "b";
1
Nick