PHP __get і __set магічні методи


85

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

Магічні методи не замінюють геттерів і сеттерів. Вони просто дозволяють обробляти виклики методів або доступ до властивостей, що в іншому випадку призведе до помилки. Таким чином, існує набагато більше, що стосується обробки помилок. Також зауважте, що вони значно повільніші, ніж використання належних викликів getter та setter або прямих методів.


5
Щоб детальніше розказати про це, просто видаліть "загальнодоступний бар", щоб власність більше не існувала, і це буде працювати як шарм.
Steffen Müller

Дякую. Ось що я отримую за те, що я не ретельно читаю посібник, а натомість переглядаю приклад цього в блозі хлопців;) Однак все ще чекаю справжнього перевантаження оператора в PHP.
airbear


1
@Pooya Це тому, що nodeце не властивість $ foo, а властивість doesNotExist. Отже, якщо 'doesNotExist' не є об'єктом (який реалізує __set або має загальнодоступну властивість, що називається node), він не працюватиме.
Тіві

1
@Allen так, це так.
Гордон

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

Щоб розширити відповідь Беррі, установка рівня доступу на захищений дозволяє використовувати __get і __set із явно оголошеними властивостями (принаймні при доступі поза класом), а швидкість значно менша, я цитую коментар з іншого питання на цю тему та все одно обґрунтуйте її використання:

Я погоджуюсь, що __get повільніше користується користувацькою функцією отримання (виконуючи ті самі дії), це 0.0124455 час для __get (), а це 0.0024445 для користувацького get () після 10000 циклів. - Мельсі, 23 листопада '12 о 22:32 Найкраща практика: PHP Magic Methods __set та __get

Згідно з тестами Мельсі, значно повільніше - приблизно в 5 разів повільніше. Це, безумовно, значно повільніше, але також зауважте, що тести показують, що ви все одно можете отримати доступ до властивості за допомогою цього методу 10000 разів, вважаючи час для ітерації циклу, приблизно в 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

Найкраще використовувати методи магічного набору / отримання із заздалегідь визначеними спеціальними методами набору / отримання, як у прикладі нижче. Таким чином ви можете поєднати найкраще з двох світів. Що стосується швидкості, я погоджуюсь, що вони трохи повільніші, але чи можете ви навіть відчути різницю. Приклад нижче також перевіряє масив даних на основі попередньо визначених установників.

"Магічні методи не замінюють геттерів і сеттерів. Вони просто дозволяють обробляти виклики методів або доступ до властивостей, що в іншому випадку призведе до помилки."

Ось чому ми повинні використовувати обидва.

ПРИКЛАД ПУНКТУ КЛАСУ

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.