programing

PHP-오버로드 된 속성의 간접 수정

copyandpastes 2021. 1. 17. 12:33
반응형

PHP-오버로드 된 속성의 간접 수정


이 질문이 여러 번 요청되었음을 알고 있지만 해결 방법에 대한 실제 답변은 없습니다. 내 특정 경우에 하나가있을 수 있습니다.

매직 메서드 __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는 마법 사용하므로 객체는 '역할'이라는 속성이없는 __get()객체를 생성하는 방법을 그리고 그것은 '역할'개체에서 해당 속성을 반환합니다.

하지만 '역할 이름'을 수정하려고 할 때 :

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

그런 다음 다음 오류가 발생합니다.

주의 : 오버로드 된 속성의 간접 수정은 효과가 없습니다.

이것이 여전히 PHP의 버그인지 또는 "예상 된 동작"인지 확실하지 않지만 어쨌든 내가 원하는 방식으로 작동하지 않습니다. 이것은 정말로 나를위한 쇼 스토퍼입니다 ... 왜 내가 지연로드 된 객체의 속성을 변경할 수 있기 때문입니까 ??


편집하다:

실제 문제는 여러 객체를 포함하는 배열을 반환 할 때만 발생하는 것 같습니다.

문제를 재현하는 예제 코드를 추가했습니다.

http://codepad.org/T1iPZm9t

실제로 PHP 환경에서 실행해야 실제로 '오류'가 표시됩니다. 하지만 여기서 정말 흥미로운 일이 벌어지고 있습니다.

객체의 속성을 변경하려고하는데 '과부화 속성을 변경할 수 없습니다'라는 알림이 표시됩니다. 하지만 그 후 속성을 에코하면 실제로 값이 변경되었음을 알 수 있습니다 ... 정말 이상합니다 ...


당신이 나에게 놀 수있는 것을 주었다는 것이 좋습니다.

운영

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

__get 함수 앞에 "&"를 추가하여 참조로 전달하기 만하면됩니다.

public function &__get ( $index )

잠시 동안 이것으로 고생했습니다.


이 같은 오류가 발생했습니다. 전체 코드가 없으면 정확히 수정하는 방법을 찾아 내기가 어렵지만 __set 함수가 없기 때문에 발생합니다.

내가 과거에이 문제를 해결 한 방식은 다음과 같은 일을했습니다.

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

이제 이렇게하면 :

echo $user->role->rolename;

'테스트'가 표시되어야합니다.


이 토론에서 매우 늦었지만 향후 누군가에게 유용 할 것이라고 생각했습니다.

비슷한 상황에 직면했습니다. 변수 설정을 해제하고 재설정하는 것을 신경 쓰지 않는 사람들을위한 가장 쉬운 해결 방법은 그렇게하는 것입니다. 이것이 작동하지 않는 이유는 다른 답변과 php.net 매뉴얼에서 분명합니다. 나를 위해 일한 가장 간단한 해결 방법은

인수:

  1. $object오버로드 __get되고 __set기본 클래스에서 가져온 개체이며 수정할 자유가 없습니다.
  2. shippingData 예를 들어 필드를 수정하려는 배열입니다 :-phone_number

 

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

참고 :이 솔루션은 문제를 해결하고 변수를 복사 할 수있는 빠른 해결 방법 중 하나입니다. 배열이 너무 넉넉한 경우 __get메서드를 강제로 다시 작성하여 큰 배열을 복사하는 데 비용이 많이 드는 참조를 반환하는 것이 좋습니다.


다음과 같은 이유로이 통지를 받았습니다.

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

이 문제가 해결되었습니다.

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

I agree with VinnyD that what you need to do is add "&" in front of your __get function, as to make it to return the needed result as a reference:

public function &__get ( $propertyname )

But be aware of two things:

1) You should also do

return &$something;

or you might still be returning a value and not a reference...

2) Remember that in any case that __get returns a reference this also means that the corresponding __set will NEVER be called; this is because php resolves this by using the reference returned by __get, which is called instead!

So:

$var = $object->NonExistentArrayProperty; 

means __get is called and, since __get has &__get and return &$something, $var is now, as intended, a reference to the overloaded property...

$object->NonExistentArrayProperty = array(); 

works as expected and __set is called as expected...

But:

$object->NonExistentArrayProperty[] = $value;

or

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

works as expected in the sense that the element will be correctly added or modified in the overloaded array property, BUT __set WILL NOT BE CALLED: __get will be called instead!

These two calls would NOT work if not using &__get and return &$something, but while they do work in this way, they NEVER call __set, but always call __get.

This is why I decided to return a reference

return &$something;

when $something is an array(), or when the overloaded property has no special setter method, and instead return a value

return $something;

when $something is NOT an array or has a special setter function.

In any case, this was quite tricky to understand properly for me! :)


This is occurring due to how PHP treats overloaded properties in that they are not modifiable or passed by reference.

See the manual for more information regarding overloading.

To work around this problem you can either use a __set function or create a createObject method.

Below is a __get and __set that provides a workaround to a similar situation to yours, you can simply modify the __set to suite your needs.

Note the __get never actually returns a variable. and rather once you have set a variable in your object it no longer is overloaded.

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

Which will allow for:

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

I have run into the same problem as w00, but I didn't had the freedom to rewrite the base functionality of the component in which this problem (E_NOTICE) occured. I've been able to fix the issue using an ArrayObject in stead of the basic type array(). This will return an object, which will defaulty be returned by reference.

ReferenceURL : https://stackoverflow.com/questions/10454779/php-indirect-modification-of-overloaded-property

반응형