Наилучшая практика: магические методы 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
*/
b) Использование традиционных сеттеров и геттеров
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, чтобы делать то, чего он не хотел. И это сработало. Но произошли плохие вещи.
Но я хотел бы услышать больше об этом.
Ответы
Ответ 1
Я был в вашем случае в прошлом. И я пошел на магические методы.
Это была ошибка, последняя часть вашего вопроса говорит все:
- это медленнее (чем getters/seters)
- нет автозавершения (и это действительно серьезная проблема) и управление типом с помощью IDE для рефакторинга и просмотра кода (под Zend Studio/PhpStorm это можно обработать с помощью аннотации
@property
phpdoc, но это требует их поддержки: довольно боль).
- документация (phpdoc) не соответствует тому, как предполагается использовать ваш код, и просмотр вашего класса также не приносит много ответов. Это запутывает.
- добавлено после редактирования: наличие getters для свойств больше соответствует "реальным" методам, где
getXXX()
возвращает не только частную собственность, но и делает реальную логику. У вас одинаковое название. Например, у вас есть $user->getName()
(возвращает частное свойство) и $user->getToken($key)
(вычисляется). В тот день, когда ваш получатель получает больше, чем получатель, и ему нужно сделать какую-то логику, все по-прежнему непротиворечиво.
Наконец, и это самая большая проблема IMO: это волшебство. И магия очень плохая, потому что вы должны знать, как магия работает, чтобы использовать ее правильно. Это проблема, с которой я встречался в команде: каждый должен понимать магию, а не только вас.
Getters и seters - это боль, чтобы писать (я их ненавижу), но они того стоят.
Ответ 2
Вам нужно всего лишь использовать магию, если объект действительно "волшебный". Если у вас есть классический объект с фиксированными свойствами, то используйте сеттеры и геттеры, они отлично работают.
Если ваш объект имеет динамические свойства, например, он является частью уровня абстракции базы данных, а его параметры заданы во время выполнения, тогда вам действительно нужны магические методы для удобства.
Ответ 3
Я использую __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 или С#.
Ответ 4
Я делаю смешанный ответ 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");
}
}
}
}
Ответ 5
Я проголосую за третье решение. Я использую это в своих проектах, и 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 (вы также можете писать сеттеры), и вам нужно писать только новые методы, если есть специальный случай для переменной-члена.
Ответ 6
Вы должны использовать stdClass, если вам нужны магические члены, если вы пишете класс - определите, что он содержит.
Ответ 7
Второй пример кода - гораздо более правильный способ сделать это, потому что вы полностью контролируете данные, которые даны class
.
Бывают случаи, когда __set
и __get
полезны, но не в этом случае.
Ответ 8
Лучшей практикой было бы использование традиционного геттеров и сеттеров из-за самоанализа или рефлексии. В PHP есть способ (точно так же, как в Java), чтобы получить имя метода или всех методов. Такая вещь вернет "__get" в первом случае и "getFirstField", "getSecondField" во втором (плюс сеттеры).
Подробнее об этом: http://php.net/manual/en/book.reflection.php
Ответ 9
Теперь я возвращаюсь к сеттерам и геттерам, но я также помещаю геттеры и сеттеры в магические методы __get и __set. Таким образом, у меня есть поведение по умолчанию, когда я это делаю
$class- > вар;
Это просто вызовет getter, который я установил в __get. Обычно я просто использую getter напрямую, но есть еще некоторые примеры, когда это просто проще.