Причины базовой реализации __get - PullRequest
3 голосов
/ 03 декабря 2011

Другие похожие вопросы:

Почему я должен использовать __get () и __set () - магические методы в php? Когда нужно / нужно использовать __construct (), __get (), __set () и __call () в PHP?

Во-первых, я полностью понимаю, как реализовать __get и __set методы в PHP и сталкивались со сценариями, в которых использование этих методов выгодно.

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

class SomeObject {

    protected $data = array();

    public function __set($key, $val) {
        $this->data[$key] = $val;
    }

    public function __get($key) {
        return $this->data[$key];
    }

}

Зачем нам это нужно?Я использовал объекты с не определенными методами __get или __set и все еще могу добавлять неопределенные свойства, извлекать значения, связанные с этими свойствами, вызывать isset() и unset() для этих свойств.

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

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

Ответы [ 6 ]

4 голосов
/ 03 декабря 2011

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

Вы не указали __isset для краткости?Потому что, если вы используете __get и __set, но не предоставляете __isset, вы просто написали себе ошибку.

Посмотрите, что я там делал ?

2 голосов
/ 03 декабря 2011

Для меня пример кода выглядит как «Мы хотим, чтобы все недостатки объекта сочетались со всем потенциалом неправильного использования массива»

Единственная причина, по которой я могу пойти на что-то подобное, - это когда вы начинаете рефакторинг из массивов в объекты.

Вы заменяете весь доступ [''] на ->, но ничего не ломаете, когда он снова компилируется.

На следующем шаге вы можете начать добавлять правила для определенных свойств.

public function __set($key, $val) {
    if($key == "money") { throw new Exception("Can't touch this"); }
    $this->data[$key] = $val;
}

public function __get($key, $val) {
    if($key == "money") { return $this->x * $this->y; }
    $this->data[$key] = $val;
}

Но в целом я не вижу никакой пользы в этом направлении. Создание реальных "объектов-значений" для ОО-структур данных кажется гораздо более полезным.

Делать что-то подобное просто ради "Мне нравится -> лучше, чем [''], не является допустимым вариантом использования в моей книге."


Для большинства случаев использования «Объекты как структуры данных» я бы предпочел:

class SomeObject {

    protected $a, $b, $c;

    public function __construct(TypeA $a, TypeB $b, TypeC $c) {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }

    /** @return TypeA */
    public function getA($key) {
        return $this->a;
    }
    // an so on

}

Это документирует ожидаемые значения. Работает с подсказками нативного типа без необходимости писать, что вы и я - это то, что я ожидал бы получить как «структуру данных».

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

2 голосов
/ 03 декабря 2011

В приведенном вами примере очень просто перезаписать $data новым массивом. Если вы имеете дело со свойствами, вам придется перебирать массив и выполнять $this->$key = $data; (к которому может иметь место утечка данных).

Более подробно:

protected function setData($arr)
{
   $this->data = $arr;
}

против

protected function setData($arr)
{
    foreach($arr as $key => $data)
    {
        $this->$key => $data;
    }
}

В приведенном выше примере клавиши, которые не отображаются в $arr в текущем вызове, но имеют в предыдущих вызовах, не будут перезаписаны, что может привести к нежелательным результатам.

В конце концов, все зависит от того, что для вас наиболее разумно.


В качестве примечания, в приведенном вами примере кода вы должны быть уверены, что реализуете __isset(), когда у вас есть __get(). Если вы этого не сделаете, вы получите неожиданные результаты при использовании таких функций, как isset() или empty().

1 голос
/ 03 декабря 2011

Существует множество причин, по которым вы можете использовать это, все зависит от того, как вы хотите контролировать свой класс. Пара примеров:

1.) У вас есть некоторые атрибуты на вашем объекте, которые связаны с базой данных ключ -> значение. Если добавлено больше значений, вы хотели бы обновить базу данных этими. Таким образом, в этом методе у вас будет какой-то флаг, указывающий на наличие массива $new_data, который необходимо синхронизировать с базой данных

2.) Вы хотите строго установить, что к объекту будут прикреплены только те переменные, которые вы хотите там видеть. Если его не существует, это может вызвать исключение.

3.) Вы абстрагировали базу данных таким образом, что get / sets будет сразу же влиять на их соответствующие значения в ней.

и многие другие.

0 голосов
/ 03 декабря 2011

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

С учетом сказанного я видел несколько умных примеров, когда магические методы оказываются полезными.Один из примеров - вызов регистратора и определение уровня журналирования, на котором вы хотите, чтобы сообщение появлялось.

Примите во внимание следующее (я использую это в своем рабочем коде):

// Only works with php 5.3 or greater (uses __callstatic)
class MyCustom_Api_Log
{
    /**
     * Wrapper method for getting a symfony logger object
     *
     * @return object
     */
    public static function getLogger()
    {
        return sfContext::getInstance()->getLogger();
    }

    /**
     * Magic method for logging all supported log levels
     *
     * @param string
     * @param array
     */
     public static function __callStatic($level, $params)
     {
         // Only concerned with message at this time
         // However any number of params can be passed in
         $message = shift($params);

        self::getLogger()->$level($message);
     }
}

// All these calls are made to __callStatic, so no need for individual methods
MyCustom_Api_Log::err(array('Oh noes something went wrong'));
MyCustom_Api_Log::warn(array('Oh noes something went wrong'));
MyCustom_Api_Log::notice(array('Oh noes something went wrong'));

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

0 голосов
/ 03 декабря 2011

В вашем примере нет, нет.

Однако, когда мы перейдем к более сложным классам, вы можете определить сеттеры и геттеры.Пример __set будет тогда:

function __set($key, $val) {
    if (method_exists($this, 'set'.ucfirst($key))) {
        $this -> {'set'.ucfirst($key)}($val);
    } else {
        $this -> data[$key] = $val;
    }
}

С помощью этого кода вы можете просто определить функцию setTestKey, которая установит $this->data['testKey'] в значение в $value, и вызвать ее через $storage->testKey = 1;

...