Методы магии PHP __get и __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
.
Ответы
Ответ 1
__get
, __set
, __call
и __callStatic
вызывается, когда метод или свойство недоступны. Ваш $bar
является общедоступным и, следовательно, недоступен.
Смотрите раздел в разделе Перегрузка свойств в руководстве:
-
__set()
запускается при записи данных в недоступные свойства. -
__get()
используется для чтения данных из недоступных свойств.
Магические методы не заменяют геттеры и сеттеры. Они просто позволяют вам обрабатывать вызовы методов или доступ к свойствам, которые в противном случае приводили бы к ошибке. Таким образом, гораздо больше связано с обработкой ошибок. Также обратите внимание, что они значительно медленнее, чем при использовании правильных методов получения и настройки или прямых вызовов метода.
Ответ 2
Я бы рекомендовал использовать массив для хранения всех значений через __set()
.
class foo {
protected $values = array();
public function __get( $key )
{
return $this->values[ $key ];
}
public function __set( $key, $value )
{
$this->values[ $key ] = $value;
}
}
Таким образом вы убедитесь, что вы не можете получить доступ к переменным по-другому (обратите внимание, что $values
защищен), чтобы избежать конфликтов.
Ответ 3
Из руководства PHP:
- __ set() запускается при записи данных в недоступные свойства.
- __ get() используется для чтения данных из недоступных свойств.
Это называется только для чтения/записи недоступных свойств. Однако ваша собственность является общедоступной, что означает, что она доступна. Изменение модификатора доступа для защиты решает проблему.
Ответ 4
Чтобы расширить ответ Berry, установка уровня доступа для защиты позволяет использовать __get и __set с явно объявленными свойствами (при доступе за пределами класса, по крайней мере), а скорость значительно медленнее, я приведу комментарий от другого вопроса на эту тему и в любом случае сделать это для использования:
Я согласен с тем, что __get работает медленнее с пользовательской функцией get (делая то же самое), это 0.0124455 время для __get(), и это 0.0024445 для пользовательского get() после 10000 циклов. - Melsi Nov 23 '12 at 22:32 Лучшая практика: магические методы PHP __set и __get
Согласно испытаниям Melsi, значительно медленнее примерно в 5 раз медленнее. Это, безусловно, значительно медленнее, но также обратите внимание, что тесты показывают, что вы все равно можете получить доступ к свойству с этим методом 10 000 раз, считая время для итерации цикла примерно в 1/100 секунды. Это значительно медленнее по сравнению с фактическими методами получения и набора, и это является преуменьшением, но в великой схеме вещей, даже в 5 раз медленнее, никогда не замедляется.
Время вычисления операции по-прежнему незначительно и не стоит рассматривать в 99% реальных приложений. Единственный раз, когда его действительно следует избегать, - это когда вы на самом деле собираетесь получать доступ к свойствам более 10 000 раз за один запрос. Высокие сайты трафика делают что-то действительно неправильное, если они не могут позволить себе бросить еще несколько серверов, чтобы поддерживать работу своих приложений. Однострочное текстовое объявление на нижнем колонтитуле сайта с высоким трафиком, где скорость доступа становится проблемой, вероятно, может заплатить за ферму из 1000 серверов с этой строкой текста. Конечный пользователь никогда не будет щелкать пальцами, задаваясь вопросом, что заставляет страницу так долго загружаться, потому что ваш доступ к свойствам приложения занимает миллионную долю секунды.
Я говорю, что это говорит как разработчик, исходящий из фона в .NET, но невидимые методы получения и установки для потребителя - это не изобретение .NET. Они просто не являются свойствами без них, и эти магические методы - это PHP-разработчик, экономящий из-за того, что даже называет свою версию свойств "свойствами" вообще. Кроме того, расширение Visual Studio для PHP действительно поддерживает intellisense с защищенными свойствами, с учетом этого трюка, подумал я. Я бы подумал, что с достаточным количеством разработчиков, используя магические методы __get и __set таким образом, разработчики PHP настроят время выполнения, чтобы удовлетворить сообщество разработчиков.
Edit: теоретически защищенные свойства выглядели так, как будто они будут работать в большинстве ситуаций. На практике выясняется, что вы много раз захотите использовать свои геттеры и сеттеры при доступе к свойствам в определении класса и расширенных классах. Лучшее решение - это базовый класс и интерфейс для расширения других классов, поэтому вы можете просто скопировать несколько строк кода из базового класса в класс реализации. Я делаю немного больше с базовым классом проекта, поэтому у меня нет интерфейса для предоставления прямо сейчас, но вот непроверенное разделенное определение класса с получением и настройкой магического свойства с использованием отражения для удаления и перемещения свойств в защищенный массив:
/** 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;
}
}
Извиняюсь, если в коде есть ошибки.
Ответ 5
Это потому, что $bar является общедоступной.
$foo->bar = 'test';
При запуске выше не нужно вызывать метод магии.
Удаление public $bar;
из вашего класса должно исправить это.
Ответ 6
Оставьте объявление public $bar;
и оно должно работать как ожидалось.
Ответ 7
Наилучшее использование магии 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