Магические методы PHP __get и __set

85

Если я не совсем ошибаюсь, __getи __setметоды должны допускать перегрузки → getи set.

Например, следующие операторы должны вызывать __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() используется для чтения данных из недоступных свойств.

Магические методы не заменяют геттеры и сеттеры. Они просто позволяют обрабатывать вызовы методов или доступ к свойствам, что в противном случае привело бы к ошибке. Таким образом, с обработкой ошибок связано гораздо больше. Также обратите внимание, что они значительно медленнее, чем использование правильных методов получения и установки или прямых вызовов методов.

Гордон
источник
5
Чтобы уточнить это, просто удалите «public $ bar», чтобы свойство больше не существовало и оно будет работать как шарм.
Штеффен Мюллер
Спасибо. Это то, что я получил за то, что не внимательно прочитал руководство и вместо этого посмотрел на примеры этого в блогах некоторых парней;) Тем не менее, все еще жду настоящей перегрузки оператора в PHP.
airbear
@airbear есть старый пакет PECL от Сары Големон, который позволяет вам перегружать операторы . Не знаю, насколько он совместим с PHP.current.
Гордон
1
@Pooya Это потому, что nodeэто не свойство $ foo, а свойство doesNotExist. Итак, если 'doesNotExist' не является объектом (который реализует __set или имеет общедоступное свойство, называемое node), он не будет работать.
Тиви
1
@ Аллен, да, это так.
Гордон
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 () используется для чтения данных из недоступных свойств.

Это вызывается только при чтении / записи недоступных свойств. Однако ваша собственность является общедоступной, что означает, что она доступна. Изменение модификатора доступа на защищенный решает проблему.

Берри Лангерак
источник
7

Чтобы расширить ответ Берри, установка уровня доступа на protected позволяет использовать __get и __set с явно объявленными свойствами (по крайней мере, при доступе за пределами класса), а скорость значительно ниже, я процитирую комментарий из другого вопроса по этой теме и в любом случае приведите аргументы в пользу его использования:

Я согласен с тем, что __get медленнее настраивается на пользовательскую функцию get (делает то же самое), это 0,0124455 время для __get (), а 0,0024445 - для настраиваемого get () после 10000 циклов. - Мелси, 23 ноя., 2012, в 22:32 Лучшая практика: магические методы PHP __set и __get

Согласно тестам Мелси, значительно медленнее - примерно в 5 раз медленнее. Это определенно значительно медленнее, но также обратите внимание, что тесты показывают, что вы все еще можете получить доступ к свойству с помощью этого метода 10 000 раз, считая время для итерации цикла, примерно за 1/100 секунды. Это значительно медленнее по сравнению с определенными фактическими методами get и set, и это еще не все, но в целом даже в 5 раз медленнее никогда не бывает.

Время вычисления операции по-прежнему ничтожно и не стоит учитывать в 99% реальных приложений. Единственный раз, когда этого действительно следует избегать, - это когда вы фактически собираетесь обращаться к свойствам более 10 000 раз за один запрос. Сайты с высоким трафиком делают что-то действительно неправильно, если они не могут позволить себе добавить еще несколько серверов, чтобы их приложения работали. Однострочное текстовое объявление в нижнем колонтитуле сайта с высоким трафиком, где скорость доступа становится проблемой, вероятно, могло бы заплатить за ферму из 1000 серверов с этой строкой текста. Конечный пользователь никогда не будет постукивать пальцами, задаваясь вопросом, почему страница так долго загружается, потому что доступ к свойствам вашего приложения занимает миллионную долю секунды.

Я говорю это, говоря как разработчик, имеющий опыт работы с .NET, но невидимые для потребителя методы get и set не являются изобретением .NET. Они просто не являются свойствами без них, и эти волшебные методы - спасительная изящество разработчика PHP, которое даже может назвать свою версию свойств «свойствами». Кроме того, расширение Visual Studio для PHP поддерживает intellisense с защищенными свойствами, я думаю, имея в виду этот трюк. Я бы подумал, что с достаточным количеством разработчиков, использующих магические методы __get и __set таким образом, разработчики PHP настроят время выполнения, чтобы удовлетворить потребности сообщества разработчиков.

Изменить: Теоретически защищенные свойства казалось, что они будут работать в большинстве ситуаций. На практике оказывается, что во многих случаях вы захотите использовать свои геттеры и сеттеры при доступе к свойствам в определении класса и расширенных классах. Лучшее решение - это базовый класс и интерфейс для расширения других классов, поэтому вы можете просто скопировать несколько строк кода из базового класса в реализующий класс. Я делаю немного больше с базовым классом моего проекта, поэтому у меня нет интерфейса, который я мог бы предоставить прямо сейчас, но вот непроверенное урезанное определение класса с получением и настройкой магического свойства с использованием отражения для удаления и перемещения свойств в защищенный массив:

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

Приношу свои извинения, если в коде есть ошибки.

Джейсон Энсингер
источник
Я обнаружил проблему с 'access' => $ property-> getModifier ()
macki
5

Это потому, что $ bar - это общедоступное свойство.

$foo->bar = 'test';

При выполнении вышеуказанного нет необходимости вызывать волшебный метод.

Удаление public $bar;из вашего класса должно исправить это.

Мэтт Лоуден
источник
1

Лучше всего использовать магические методы set / get с предопределенными пользовательскими методами set / get, как в примере ниже. Таким образом вы сможете объединить лучшее из двух миров. Что касается скорости, я согласен, что они немного медленнее, но вы даже можете почувствовать разницу. В приведенном ниже примере также проверяется соответствие массива данных предопределенным сеттерам.

«Магические методы не заменяют геттеры и сеттеры. Они просто позволяют вам обрабатывать вызовы методов или доступ к свойствам, которые в противном случае привели бы к ошибке».

Вот почему мы должны использовать оба.

КЛАСС ПУНКТ ПРИМЕР

    /*
    * 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
DevWL
источник
0

Отбросьте public $bar;объявление, и оно должно работать должным образом.

Аликс Аксель
источник
-6

Intenta con:

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

_SET($k,$v){
 return $this->$k = $v;
}
Чарли
источник