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

Если я не ошибаюсь, __set методы __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 .

__get , __set , __call и __call вызывается, когда метод или свойство недоступны. Ваш $bar является общедоступным и, следовательно, недоступен.

См. Раздел «Перегрузка свойств» в руководстве:

  • __set() запускается при записи данных в недоступные свойства.
  • __get() используется для чтения данных из недоступных свойств.

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

Я бы рекомендовал использовать массив для хранения всех значений через __set() .

 class foo { protected $values = array(); public function __get( $key ) { return $this->values[ $key ]; } public function __set( $key, $value ) { $this->values[ $key ] = $value; } } 

Таким образом, вы убедитесь, что вы не можете получить доступ к переменным по-другому (обратите внимание, что $values защищены), чтобы избежать столкновений.

Из руководства по PHP :

  • __set () запускается при записи данных в недоступные свойства.
  • __get () используется для чтения данных из недоступных свойств.

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

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

Я согласен с тем, что __get более медленна для пользовательской функции get (делая то же самое), это 0.0124455 время для __get (), и это 0.0024445 для пользовательского get () после 10000 циклов. – Melsi Nov 23 '12 at 22:32 Лучшая практика: PHP Magic Methods __set и __get

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

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

Я говорю это как разработчик, исходящий из фона в .NET, но невидимые методы получения и установки для потребителя не являются изобретением .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; } } с /** 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; } } 

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

Это потому, что $ bar является публичной собственностью.

 $foo->bar = 'test'; 

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

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

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

Наилучший способ использования magic set / get с предопределенными настраиваемыми настройками / 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 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; } } } private function setNameOfTheItem($value){ // no filter $this->data['name'] = strtoupper($value); //asign 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 - exided 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 triger magic function __set() without any controll 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