PHP __get 및 __set 매직 메서드


85

내가 완전히 착각하지 않는 한 __get__set메소드는 → getset.

예를 들어, 다음 명령문은 __get메소드를 호출해야합니다 .

echo $foo->bar;
$var = $foo->bar;

그리고 다음은 __set방법을 사용해야합니다 .

$foo->bar = 'test';

이것은 내 코드에서 작동하지 않았으며 다음 간단한 예제로 재현 할 수 있습니다.

class foo {

    public $bar;
    public function __get($name) {

        echo "Get:$name";
        return $this->$name;
    }

    public function __set($name, $value) {

        echo "Set:$name to $value";
        $this->$name = $value;
    }
}


$foo = new foo();

echo $foo->bar;
$foo->bar = 'test';

echo "[$foo->bar]";

결과는 다음과 같습니다.

[test]

die()거기에 몇 가지 전화를 걸면 전혀 타격을 입지 않는다는 것을 보여줍니다.

지금은 그냥 나사를 조이고 __get지금은 필요한 곳에 수동으로 사용 하고 있지만 이는 매우 동적이 아니며 특별히 호출되지 않는 한 '오버로드 된'코드가 실제로 호출되지 않는다는 지식이 필요합니다. 이것이 내가 이해 한 방식으로 작동하지 않는지 또는 왜 작동하지 않는지 알고 싶습니다.

이것은에서 실행 중입니다 php 5.3.3.

답변:


164

__get, __set, __call__callStatic메소드 나 속성에 액세스 할 수없는 경우 호출됩니다. 귀하 $bar는 공개되어 있으므로 액세스 할 수 없습니다.

매뉴얼의 속성 오버로딩 섹션을 참조하십시오 .

  • __set() 액세스 할 수없는 속성에 데이터를 쓸 때 실행됩니다.
  • __get() 액세스 할 수없는 속성에서 데이터를 읽는 데 사용됩니다.

매직 메서드는 게터와 세터를 대체하지 않습니다. 이를 통해 오류가 발생할 수있는 메서드 호출 또는 속성 액세스를 처리 할 수 ​​있습니다. 따라서 오류 처리와 더 관련이 있습니다. 또한 적절한 getter 및 setter 또는 직접 메서드 호출을 사용하는 것보다 상당히 느립니다.


6
이에 대해 자세히 설명하려면 "public $ bar"를 제거하여 속성이 더 이상 존재하지 않고 매력처럼 작동하도록합니다.
Steffen Müller

감사. 그것이 내가 매뉴얼을 철저히 읽지 않고 그 대신 일부 사람들의 블로그 예제를 보는 것에 대한 것입니다.) 그래도 PHP에서 진정한 연산자 오버로딩을 기다리고 있습니다.
airbear

@airbear 연산자를 오버로드 할 수있는 Sara Golemon오래된 PECL 패키지가 있습니다 . 그래도 PHP.current와 어떻게 호환되는지 모르겠습니다.
고든

1
@Pooya 그것은 node$ foo의 속성이 아니라 doesNotExist. 따라서 'doesNotExist'가 객체 (__set를 구현하거나 node라는 공용 속성이 있음)가 아니면 작동하지 않습니다.
Tivie

1
@Allen 예, 그렇습니다.
Gordon

34

을 통해 모든 값을 저장하기 위해 배열을 사용하는 것이 좋습니다 __set().

class foo {

    protected $values = array();

    public function __get( $key )
    {
        return $this->values[ $key ];
    }

    public function __set( $key, $value )
    {
        $this->values[ $key ] = $value;
    }

}

이렇게하면 $values충돌을 방지하기 위해 다른 방법 ( 보호되어 있음) 으로 변수에 액세스 할 수 없도록합니다 .


19

보내는 사람 PHP 매뉴얼 :

  • __set ()은 액세스 할 수없는 속성에 데이터를 쓸 때 실행됩니다.
  • __get ()은 액세스 할 수없는 속성에서 데이터를 읽는 데 사용됩니다.

액세스 할 수없는 속성 읽기 / 쓰기에서만 호출됩니다 . 그러나 귀하의 자산은 공용이므로 액세스 할 수 있습니다. 액세스 한정자를 protected로 변경하면 문제가 해결됩니다.


7

Berry의 답변을 확장하기 위해 액세스 수준을 protected로 설정하면 __get 및 __set을 명시 적으로 선언 된 속성 (적어도 클래스 외부에서 액세스 할 때)과 함께 사용할 수 있고 속도가 상당히 느려진다는 점에서 다른 질문에서 의견을 인용하겠습니다. 이 주제에 대해 어쨌든 그것을 사용하는 사례를 만드십시오.

나는 __get이 사용자 정의 get 함수 (동일한 일을 수행)보다 느리다는 데 동의합니다. 이것은 __get ()의 시간 인 0.0124455이고이 0.0024445는 10000 루프 후 사용자 정의 get ()을위한 것입니다. – Melsi 11 월 23 일 '12 년 11 월 23 일 22:32 모범 사례 : PHP 매직 메서드 __set 및 __get

Melsi의 테스트에 따르면 상당히 느린 것은 약 5 배 느립니다. 이는 확실히 상당히 느리지 만 테스트에서이 메서드를 사용하여 약 1/100 초 내에 루프 반복 시간을 계산하여 10,000 번 속성에 액세스 할 수 있음을 보여줍니다. 정의 된 실제 get 및 set 메서드에 비해 상당히 느리며 이는 과소 표현이지만, 대대적 인 계획에서는 5 배 더 느리더라도 실제로는 느리지 않습니다.

작업의 컴퓨팅 시간은 여전히 ​​미미하며 실제 애플리케이션의 99 %에서 고려할 가치가 없습니다. 실제로 피해야하는 유일한 경우는 단일 요청에서 실제로 10,000 번 이상 속성에 액세스 할 때입니다. 트래픽이 많은 사이트는 애플리케이션을 계속 실행하기 위해 몇 대의 서버를 추가로 투입 할 여유가 없다면 정말 잘못된 일을하고 있습니다. 액세스 속도가 문제가되는 트래픽이 많은 사이트의 바닥 글에 한 줄 텍스트 광고는 해당 텍스트 줄이있는 1,000 대의 서버 팜에 비용을 지불 할 수 있습니다. 최종 사용자는 애플리케이션의 속성 액세스에 백만 분의 1 초가 걸리기 때문에 페이지를로드하는 데 너무 오래 걸리는 것이 무엇인지 궁금해하지 않을 것입니다.

나는 이것이 .NET의 배경에서 온 개발자라고 말하지만, 소비자에게 보이지 않는 get 및 set 메서드는 .NET의 발명이 아닙니다. 그것들은 단순히 그것들이없는 속성이 아니며, 이러한 마법의 방법은 심지어 속성의 버전을 "속성"이라고 부르는 것에 대한 PHP의 개발자의 은혜입니다. 또한 PHP 용 Visual Studio 확장은 보호 된 속성을 사용하여 인텔리 젠스를 지원합니다. 이러한 트릭을 염두에두고 있습니다. 이런 식으로 매직 __get 및 __set 메서드를 사용하는 개발자가 충분하면 PHP 개발자가 개발자 커뮤니티에 맞게 실행 시간을 조정할 수 있다고 생각합니다.

편집 : 이론적으로 보호 된 재산은 대부분의 상황에서 작동하는 것처럼 보였습니다. 실제로 클래스 정의 및 확장 클래스 내에서 속성에 액세스 할 때 getter 및 setter를 사용하려는 경우가 많습니다. 더 나은 솔루션은 다른 클래스를 확장 할 때의 기본 클래스 및 인터페이스이므로 기본 클래스에서 구현 클래스로 코드 몇 줄만 복사하면됩니다. 내 프로젝트의 기본 클래스로 조금 더 작업하고 있으므로 지금 제공 할 인터페이스가 없지만 여기에는 속성을 제거하고 이동하기 위해 리플렉션을 사용하여 설정하고 마법 속성을 가져 오는 테스트되지 않은 제거 된 클래스 정의가 있습니다. 보호 된 어레이 :

/** Base class with magic property __get() and __set() support for defined properties. */
class Component {
    /** Gets the properties of the class stored after removing the original
     * definitions to trigger magic __get() and __set() methods when accessed. */
    protected $properties = array();

    /** Provides property get support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default get method. When
     * overriding, call parent::__get($name) first and return if not null,
     * then be sure to check that the property is in the overriding class
     * before doing anything, and to implement the default get routine. */
    public function __get($name) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    return $this->properties[$name]->value;
            }
    }

    /** Provides property set support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default set method. When
     * overriding, call parent::__set($name, $value) first, then be sure to
     * check that the property is in the overriding class before doing anything,
     * and to implement the default set routine. */
    public function __set($name, $value) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    $this->properties[$name]->value = $value;
            }
    }

    /** Constructor for the Component. Call first when overriding. */
    function __construct() {
        // Removing and moving properties to $properties property for magic
        // __get() and __set() support.
        $reflected_class = new ReflectionClass($this);
        $properties = array();
        foreach ($reflected_class->getProperties() as $property) {
            if ($property->isStatic()) { continue; }
            $properties[$property->name] = (object)array(
                'name' => $property->name, 'value' => $property->value
                , 'access' => $property->getModifier(), 'class' => get_class($this));
            unset($this->{$property->name}); }
        $this->properties = $properties;
    }
}

코드에 버그가 있으면 사과드립니다.


나는 '액세스'=> $ property-> getModifier ()에서 문제를 발견했습니다
macki

5

$ bar가 공공 자산이기 때문입니다.

$foo->bar = 'test';

위를 실행할 때 매직 메서드를 호출 할 필요가 없습니다.

public $bar;클래스에서 삭제 하면이 문제가 해결됩니다.


1

아래 예에서와 같이 미리 정의 된 사용자 지정 설정 / 가져 오기 메서드와 함께 매직 설정 / 가져 오기 메서드를 가장 잘 사용합니다. 이렇게하면 두 세계의 장점을 결합 할 수 있습니다. 속도면에서 나는 그들이 조금 느리다는 데 동의하지만 차이를 느낄 수도 있습니다. 아래 예제는 미리 정의 된 setter에 대해 데이터 배열의 유효성도 검사합니다.

"매직 메서드는 getter 및 setter를 대체하지 않습니다. 오류가 발생할 수있는 메서드 호출 또는 속성 액세스를 처리 할 수 ​​있도록합니다."

이것이 우리가 둘 다 사용해야하는 이유입니다.

클래스 항목 예

    /*
    * Item class
    */
class Item{
    private $data = array();

    function __construct($options=""){ //set default to none
        $this->setNewDataClass($options); //calling function
    }

    private function setNewDataClass($options){
        foreach ($options as $key => $value) {
            $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserve camel case convention naming
            if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
                $this->$method($value); //execute the setters function
            }else{
                $this->data[$key] = $value; //create new set data[key] = value without seeters;
            }   
        }
    }

    private function setNameOfTheItem($value){ // no filter
        $this->data['name'] = strtoupper($value); //assign the value
        return $this->data['name']; // return the value - optional
    }

    private function setWeight($value){ //use some kind of filter
        if($value >= "100"){ 
            $value = "this item is too heavy - sorry - exceeded weight of maximum 99 kg [setters filter]";
        }
        $this->data['weight'] = strtoupper($value); //asign the value
        return $this->data['weight']; // return the value - optional
    }

    function __set($key, $value){
        $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserv camell case convention naming
        if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
            $this->$method($value); //execute the seeter function
        }else{
            $this->data[$key] = $value; //create new set data[key] = value without seeters;
        }
    }

    function __get($key){
        return $this->data[$key];
    }

    function dump(){
        var_dump($this);
    }
}

INDEX.PHP

$data = array(
    'nameOfTheItem' => 'tv',
    'weight' => '1000',
    'size' => '10x20x30'
);

$item = new Item($data);
$item->dump();

$item->somethingThatDoNotExists = 0; // this key (key, value) will trigger magic function __set() without any control or check of the input,
$item->weight = 99; // this key will trigger predefined setter function of a class - setWeight($value) - value is valid,
$item->dump();

$item->weight = 111; // this key will trigger predefined setter function of a class - setWeight($value) - value invalid - will generate warning.
$item->dump(); // display object info

산출

object(Item)[1]
  private 'data' => 
    array (size=3)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string '99' (length=2)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0


-6

인 텐타 콘 :

__GET($k){
 return $this->$k;
}

_SET($k,$v){
 return $this->$k = $v;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.