Наилучшая практика: магические методы PHP __set и __get

Возможный дубликат:
Являются ли методы магии лучшей практикой в ​​PHP?

Это простые примеры, но представьте, что у вас больше свойств, чем у двух в вашем классе.

Что было бы лучшей практикой?

a) Использование __get и __set

class MyClass { private $firstField; private $secondField; public function __get($property) { if (property_exists($this, $property)) { return $this->$property; } } public function __set($property, $value) { if (property_exists($this, $property)) { $this->$property = $value; } } } $myClass = new MyClass(); $myClass->firstField = "This is a foo line"; $myClass->secondField = "This is a bar line"; echo $myClass->firstField; echo $myClass->secondField; /* Output: This is a foo line This is a bar line */ 

б) Использование традиционных сеттеров и геттеров

 class MyClass { private $firstField; private $secondField; public function getFirstField() { return $this->firstField; } public function setFirstField($firstField) { $this->firstField = $firstField; } public function getSecondField() { return $this->secondField; } public function setSecondField($secondField) { $this->secondField = $secondField; } } $myClass = new MyClass(); $myClass->setFirstField("This is a foo line"); $myClass->setSecondField("This is a bar line"); echo $myClass->getFirstField(); echo $myClass->getSecondField(); /* Output: This is a foo line This is a bar line */ 

В этой статье: http://blog.webspecies.co.uk/2011-05-23/the-new-era-of-php-frameworks.html

Автор утверждает, что использование магических методов не является хорошей идеей:

Прежде всего, тогда было очень популярно использовать магические функции PHP (__get, __call и т. Д.). С ними нет ничего плохого в первом взгляде, но они действительно опасны. Они делают API неясным, автоматическое завершение невозможным и, самое главное, они медленны. Для них был взлом PHP, чтобы делать то, чего он не хотел. И это сработало. Но произошли плохие вещи.

Но я хотел бы услышать больше об этом.

Я был в вашем случае в прошлом. И я пошел на магические методы.

Это была ошибка, в последней части вашего вопроса говорится все:

  • это медленнее (чем геттеры / сеттеры)
  • автоматическое завершение (и это на самом деле серьезная проблема) и управление типом IDE для рефакторинга и просмотра кода (в Zend Studio / PhpStorm это можно обрабатывать с помощью аннотации @property phpdoc, но это требует их поддержки : довольно боль)
  • документация (phpdoc) не соответствует тому, как предполагается использовать ваш код, и просмотр вашего класса также не приносит много ответов. Это смущает.
  • добавлено после редактирования: наличие getters для свойств более согласовано с «реальными» методами, где getXXX() не только возвращает частную собственность, но и выполняет реальную логику. У вас одинаковое название. Например, у вас есть $user->getName() (возвращает частное свойство) и $user->getToken($key) (вычисляется). В тот день, когда ваш получатель получает больше, чем получатель, и ему нужно сделать какую-то логику, все по-прежнему непротиворечиво.

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

Геттеры и сеттеры – это боль, чтобы писать (я их ненавижу), но они того стоят.

Вам нужно всего лишь использовать магию, если объект действительно «волшебный». Если у вас есть классический объект с фиксированными свойствами, то используйте сеттеры и геттеры, они работают нормально.

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

Я использую __get (и общедоступные свойства) как можно больше, потому что они делают код более читаемым. Для сравнения:

этот код недвусмысленно говорит, что я делаю:

 echo $user->name; 

этот код заставляет меня чувствовать себя глупо, что мне не нравится:

 function getName() { return $this->_name; } .... echo $user->getName(); 

Разница между этими двумя особенно очевидна при одновременном доступе к нескольким свойствам.

 echo " Dear $user->firstName $user->lastName! Your purchase: $product->name $product->count x $product->price " 

а также

 echo " Dear " . $user->getFirstName() . " " . $user->getLastName() . " Your purchase: " . $product->getName() . " " . $product->getCount() . " x " . $product->getPrice() . " "; 

Должен ли «$ a-> b» действительно что- то делать или просто вернуть значение, является обязанностью вызываемого. Для вызывающего абонента «$ user-> name» и «$ user-> accountBalance» должны выглядеть одинаково, хотя последние могут включать сложные вычисления. В моих классах данных я использую следующий небольшой метод:

  function __get($p) { $m = "get_$p"; if(method_exists($this, $m)) return $this->$m(); user_error("undefined property $p"); } 

когда кто-то называет «$ obj-> xxx», и класс имеет «get_xxx», этот метод будет неявно вызываться. Таким образом, вы можете определить геттер, если он вам нужен, при этом ваш интерфейс будет однородным и прозрачным. В качестве дополнительного бонуса это обеспечивает элегантный способ запоминания расчетов:

  function get_accountBalance() { $result = <...complex stuff...> // since we cache the result in a public property, the getter will be called only once $this->accountBalance = $result; } .... echo $user->accountBalance; // calculate the value .... echo $user->accountBalance; // use the cached value 

Итог: php – это динамический язык сценариев, используйте его таким образом, не делайте вид, что вы делаете Java или C #.

Я делаю сочетание ответа edem и вашего второго кода. Таким образом, у меня есть преимущества общих getter / seters (завершение кода в вашей среде IDE), простота кодирования, если я хочу, исключения из-за несуществующих свойств (отлично подходит для обнаружения опечаток: $foo->naem вместо $foo->name ), только для чтения свойств и составных свойств.

 class Foo { private $_bar; private $_baz; public function getBar() { return $this->_bar; } public function setBar($value) { $this->_bar = $value; } public function getBaz() { return $this->_baz; } public function getBarBaz() { return $this->_bar . ' ' . $this->_baz; } public function __get($var) { $func = 'get'.$var; if (method_exists($this, $func)) { return $this->$func(); } else { throw new InexistentPropertyException("Inexistent property: $var"); } } public function __set($var, $value) { $func = 'set'.$var; if (method_exists($this, $func)) { $this->$func($value); } else { if (method_exists($this, 'get'.$var)) { throw new ReadOnlyException("property $var is read-only"); } else { throw new InexistentPropertyException("Inexistent property: $var"); } } } } 

Я голосую за третье решение. Я использую это в своих проектах, и Symfony тоже использует что-то вроде этого:

 public function __call($val, $x) { if(substr($val, 0, 3) == 'get') { $varname = strtolower(substr($val, 3)); } else { throw new Exception('Bad method.', 500); } if(property_exists('Yourclass', $varname)) { return $this->$varname; } else { throw new Exception('Property does not exist: '.$varname, 500); } } 

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

Вы должны использовать stdClass, если вам нужны магические члены, если вы пишете класс – определите, что он содержит.

Наилучшей практикой было бы использование традиционных геттеров и сеттеров из-за интроспекции или размышления. В PHP есть способ (точно так же, как в Java), чтобы получить имя метода или всех методов. Такая вещь вернет «__get» в первом случае и «getFirstField», «getSecondField» во втором (плюс сеттеры).

Подробнее об этом: http://php.net/manual/en/book.reflection.php

Я возвращаюсь к сеттерам и геттерам, но я также помещаю геттеры и сеттеры в магические методы и __set. Таким образом, у меня есть поведение по умолчанию, когда я делаю это

$ Class-> вар;

Это просто вызовет getter, который я установил в __get. Обычно я просто использую getter напрямую, но есть еще некоторые примеры, когда это просто проще.

Второй пример кода – гораздо более правильный способ сделать это, потому что вы полностью контролируете данные, которые предоставляются class . Есть случаи, когда __set и __get полезны, но не в этом случае.