Лучшая практика: Волшебные методы PHP устанавливают и получают [дубликат]


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

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

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

А) Используя __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, чтобы делать то, чего он не хотел. И это сработало. Но заставлял случаться плохие вещи.

Но я хотелось бы услышать больше мнений по этому поводу.

Author: Community, 2011-05-31

9 answers

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

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

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

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

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

 137
Author: Matthieu Napoli, 2013-05-30 06:41:19

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

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

 104
Author: vbence, 2013-12-06 12:59:48

Я использую __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#.

 78
Author: user187291, 2018-09-04 11:23:00

Я делаю смесь ответа эдема и вашего второго кода. Таким образом, у меня есть преимущества обычных средств получения/установки (завершение кода в вашей среде 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");
            }
        }
    }
}
 4
Author: Carlos Campderrós, 2011-05-31 08:09:10

Я голосую за третье решение. Я использую это в своих проектах, и 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);
    }
}

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

 0
Author: Adam Arold, 2011-05-31 07:44:19

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

 -2
Author: Wesley van Opdorp, 2011-05-31 07:38:50

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

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

 -3
Author: SteeveDroz, 2011-05-31 07:43:24

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

$класс->var;

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

 -3
Author: AntonioCS, 2011-05-31 09:18:18

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

 -3
Author: skowron-line, 2015-10-17 04:43:38