Геттер и сеттер?

Я не PHP-разработчик, поэтому мне интересно, если в PHP более популярно использовать явные методы получения / установки, в чистом стиле ООП, с закрытыми полями (как мне нравится):

class MyClass {
    private $firstField;
    private $secondField;

    public function getFirstField() {
        return $this->firstField;
    }
    public function setFirstField($x) {
        $this->firstField = $x;
    }
    public function getSecondField() {
        return $this->secondField;
    }
    public function setSecondField($x) {
        $this->secondField = $x;
    }
}

или просто публичные поля:

class MyClass {
    public $firstField;
    public $secondField;
}

Спасибо

16 ответов

Решение

Вы можете использовать магические методы php __get а также __set,

<?php
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;
    }

    return $this;
  }
}
?>

Зачем использовать геттеры и сеттеры?

  1. Масштабируемость: проще выполнить рефакторинг геттера, чем искать все назначения var в коде проекта.
  2. Отладка: вы можете поставить точки останова на установщики и получатели.
  3. Очиститель: магические функции не являются хорошим решением для написания меньшего количества данных, ваша IDE не будет предлагать код. Лучше использовать шаблоны для быстро пишущих геттеров.

прямое назначение и геттеры / сеттеры

Google уже опубликовал руководство по оптимизации PHP и сделал вывод:

Нет геттера и сеттера Оптимизация PHP

И нет, вы не должны использовать магические методы. Для PHP магический метод - зло. Зачем?

  1. Их сложно отлаживать.
  2. Есть негативное влияние на производительность.
  3. Они требуют написания большего количества кода.

PHP не является Java, C++ или C#. PHP отличается и играет с разными ролями.

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

В PHP это работает:

class Foo {
   public $bar; // should be an integer
}
$foo = new Foo;
$foo->bar = "string";

В Java это не так:

class Foo {
   public int bar;
}
Foo myFoo = new Foo();
myFoo.bar = "string"; // error

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

В дополнение к и без того отличным и уважаемым ответам здесь я хотел бы остановиться на PHP без установщиков / получателей.

В PHP нет синтаксиса геттера и сеттера. Он предоставляет подклассы или магические методы, позволяющие "перехватывать" и переопределять процесс поиска свойств, как указал Дэйв.

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

Производительность Каждая ненужная функция, возникающая в результате принудительного использования PHP-архитектуры, подобной геттеру / сеттеру, при вызове использует свой собственный стековый фрейм памяти и тратит впустую циклы ЦП.

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

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

Заблуждения:

Распространенным аргументом является удобочитаемость. Например, что $someobject->width легче читать, чем $someobject->width(), Однако в отличие от планеты circumference или же width, который можно считать staticэкземпляр объекта, такой как $someobject, который требует функции ширины, скорее всего, измеряет ширину экземпляра объекта.
Поэтому удобочитаемость увеличивается в основном из-за утвердительных схем именования, а не из-за того, что функция скрыта, что выводит заданное значение свойства.

__get / __set использует:

  • предварительная проверка и предварительная санация стоимости имущества

  • например, строки

    "
    some {mathsobj1->generatelatex} multi
    line text {mathsobj1->latexoutput}
    with lots of variables for {mathsobj1->generatelatex}
     some reason
    "
    

    В этом случае generatelatex будет придерживаться схемы именования actionname + methodname

  • особые, очевидные случаи

    $dnastringobj->homeobox($one_rememberable_parameter)->gattaca->findrelated()
    $dnastringobj->homeobox($one_rememberable_parameter)->gttccaatttga->findrelated()
    

Примечание: PHP решил не реализовывать синтаксис getter/setter. Я не утверждаю, что геттеры / сеттеры вообще плохие.

Если вы предпочитаете использовать функцию __call, вы можете использовать этот метод. Работает с

  • GET => $this->property()
  • SET => $this->property($value)
  • GET => $this->getProperty()
  • SET => $this->setProperty($value)

kalsdas

public function __call($name, $arguments) {

    //Getting and setting with $this->property($optional);

    if (property_exists(get_class($this), $name)) {


        //Always set the value if a parameter is passed
        if (count($arguments) == 1) {
            /* set */
            $this->$name = $arguments[0];
        } else if (count($arguments) > 1) {
            throw new \Exception("Setter for $name only accepts one parameter.");
        }

        //Always return the value (Even on the set)
        return $this->$name;
    }

    //If it doesn't chech if its a normal old type setter ot getter
    //Getting and setting with $this->getProperty($optional);
    //Getting and setting with $this->setProperty($optional);
    $prefix = substr($name, 0, 3);
    $property = strtolower($name[3]) . substr($name, 4);
    switch ($prefix) {
        case 'get':
            return $this->$property;
            break;
        case 'set':
            //Always set the value if a parameter is passed
            if (count($arguments) != 1) {
                throw new \Exception("Setter for $name requires exactly one parameter.");
            }
            $this->$property = $arguments[0];
            //Always return the value (Even on the set)
            return $this->$name;
        default:
            throw new \Exception("Property $name doesn't exist.");
            break;
    }
}

Я провел эксперимент, используя магический метод __call. Не уверен, стоит ли мне это публиковать (из-за всех предупреждений "НЕ ИСПОЛЬЗУЙТЕ ВОЛШЕБНЫЕ МЕТОДЫ" в других ответах и ​​комментариях), но я оставлю это здесь... на всякий случай, если кто-то посчитает это полезным.


public function __call($_name, $_arguments){
    $action  = substr($_name, 0, 4);
    $varName = substr($_name, 4);

    if (isset($this->{$varName})){
        if ($action === "get_") return $this->{$varName};
        if ($action === "set_") $this->{$varName} = $_arguments[0];
    }
}

Просто добавьте этот метод выше в своем классе, теперь вы можете ввести:

class MyClass{
    private foo = "bar";
    private bom = "bim";
    // ...
    // public function __call(){ ... }
    // ...
}
$C = new MyClass();

// as getter
$C->get_foo(); // return "bar"
$C->get_bom(); // return "bim"

// as setter
$C->set_foo("abc"); // set "abc" as new value of foo
$C->set_bom("zam"); // set "zam" as new value of bom


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

Пример:

private $callWhiteList = array(
    "foo" => "foo",
    "fee" => "fee",
    // ...
);

public function __call($_name, $_arguments){
    $action  = substr($_name, 0, 4);
    $varName = $this->callWhiteList[substr($_name, 4)];

    if (!is_null($varName) && isset($this->{$varName})){
        if ($action === "get_") return $this->{$varName};
        if ($action === "set_") $this->{$varName} = $_arguments[0];
    }
}

Теперь вы можете получить / установить только "foo" и "fee".
Вы также можете использовать этот "белый список" для назначения пользовательских имен для доступа к вашим переменным.
Например,

private $callWhiteList = array(
    "myfoo" => "foo",
    "zim" => "bom",
    // ...
);

С этим списком вы можете напечатать:

class MyClass{
    private foo = "bar";
    private bom = "bim";
    // ...
    // private $callWhiteList = array( ... )
    // public function __call(){ ... }
    // ...
}
$C = new MyClass();

// as getter
$C->get_myfoo(); // return "bar"
$C->get_zim(); // return "bim"

// as setter
$C->set_myfoo("abc"); // set "abc" as new value of foo
$C->set_zim("zam"); // set "zam" as new value of bom

,
,
,
Это все.


Doc: __call () срабатывает при вызове недоступных методов в контексте объекта.

class MyClass {
    private $firstField;
    private $secondField;
    private $thirdField;

    public function __get( $name ) {
        if( method_exists( $this , $method = ( 'get' . ucfirst( $name  ) ) ) )
            return $this->$method();
        else
            throw new Exception( 'Can\'t get property ' . $name );
    }

    public function __set( $name , $value ) {
        if( method_exists( $this , $method = ( 'set' . ucfirst( $name  ) ) ) )
            return $this->$method( $value );
        else
            throw new Exception( 'Can\'t set property ' . $name );
    }

    public function __isset( $name )
    {
        return method_exists( $this , 'get' . ucfirst( $name  ) ) 
            || method_exists( $this , 'set' . ucfirst( $name  ) );
    }

    public function getFirstField() {
        return $this->firstField;
    }

    protected function setFirstField($x) {
        $this->firstField = $x;
    }

    private function getSecondField() {
        return $this->secondField;
    }
}

$obj = new MyClass();

echo $obj->firstField; // works
$obj->firstField = 'value'; // works

echo $obj->getFirstField(); // works
$obj->setFirstField( 'value' ); // not works, method is protected

echo $obj->secondField; // works
echo $obj->getSecondField(); // not works, method is private

$obj->secondField = 'value'; // not works, setter not exists

echo $obj->thirdField; // not works, property not exists

isset( $obj->firstField ); // returns true
isset( $obj->secondField ); // returns true
isset( $obj->thirdField ); // returns false

Готовы!

Ну, в PHP есть магические методы __get, __set, __isset & __unset, который всегда является началом. Увы, правильные (понимаете?) Свойства ОО - это больше, чем магические методы. Основная проблема с реализацией PHP заключается в том, что магические методы вызываются для всех недоступных свойств. Это означает, что вы должны повторять себя (например, вызывая property_exists()) в магических методах, когда определяете, действительно ли name является свойством вашего объекта. И вы не можете решить эту общую проблему с базовым классом, если все ваши классы не наследуются от ie. ClassWithProperties, поскольку в PHP отсутствует множественное наследование.

Напротив, новые классы стиля Python дает вам property(), который позволяет вам явно определить все ваши свойства. C# имеет специальный синтаксис.

http://en.wikipedia.org/wiki/Property_(programming)

Прочитав другие советы, я склонен сказать, что:

Как правило, вы не всегда будете определять установщики для ВСЕХ свойств, особенно "внутренних" (семафоров, внутренних флагов...). Свойства, доступные только для чтения, не будут иметь сеттеров, поэтому некоторые свойства будут иметь только геттеры; вот где __get() сокращает код:

  • определить __get() (магические глобальные геттеры) для всех тех свойств, которые похожи,
  • сгруппируйте их в массивы так:
    • они будут иметь общие характеристики: денежные значения будут / могут подходить надлежащим образом отформатированы, даты в определенном формате (ISO, США, международный) и т. д.
    • сам код может проверить, что с помощью этого магического метода читаются только существующие и разрешенные свойства.
    • всякий раз, когда вам нужно создать новое похожее свойство, просто объявите его и добавьте его имя в соответствующий массив, и все готово. Это гораздо быстрее, чем определение нового метода получения, возможно, с некоторыми строками кода, ПОВТОРЯЕМЫМИ снова и снова по всему коду класса.

Да! мы могли бы написать частный метод, чтобы сделать это, но, опять же, мы будем иметь МНОГИЕ объявленные методы (++memory), которые в конечном итоге вызовут другой, всегда один и тот же метод. Почему бы просто не написать ЕДИНСТВЕННЫЙ метод, чтобы управлять ими всеми...? [Ага! каламбур абсолютно предназначен!:)]

Магические сеттеры также могут реагировать ТОЛЬКО на определенные свойства, поэтому все свойства типа даты могут быть проверены на наличие недопустимых значений только одним методом. Если свойства типа даты были перечислены в массиве, их установщики могут быть легко определены. Просто пример, конечно. Есть слишком много ситуаций.

О читабельности... Ну... Это еще одна дискуссия: мне не нравится связываться с использованием IDE (на самом деле, я не использую их, они, как правило, говорят мне (и заставляют меня), как пиши... а мне нравятся кодировки "красота"). Я склонен быть последовательным в отношении именования, поэтому мне достаточно использования ctags и пары других вспомогательных средств... В любом случае: как только все эти магические сеттеры и геттеры завершены, я пишу другие сеттеры, которые слишком специфичны или "особенные" для быть обобщенным в методе __set(). И это охватывает все, что мне нужно для получения и установки свойств. Конечно: не всегда есть общий язык, или есть несколько таких свойств, которые не стоят проблем при кодировании магического метода, а также есть старая добрая традиционная пара сеттер / геттер.

Языки программирования - это всего лишь человеческие искусственные языки. Итак, у каждого из них есть своя собственная интонация или акцент, синтаксис и вкус, поэтому я не буду притворяться, что пишу код на Ruby или Python, используя тот же "акцент", что и Java или C#, а также я не написал бы JavaScript или PHP, чтобы они напоминали Perl или SQL... Используйте их так, как они предназначены.

Вообще говоря, первый способ в целом более популярен, потому что те, кто уже обладает знаниями в области программирования, могут легко перейти на PHP и выполнить работу объектно-ориентированным способом. Первый способ более универсален. Мой совет - придерживаться того, что проверено и верно на многих языках. Затем, когда и если вы будете использовать другой язык, вы будете готовы сделать что-то (вместо того, чтобы тратить время на переизобретение колеса).

Этот пост не конкретно о __get а также __set скорее __call это та же идея, за исключением вызова метода. Как правило, я держусь подальше от любых магических методов, которые допускают перегрузку по причинам, изложенным в комментариях и сообщениях ОДНАКО, я недавно столкнулся с сторонним API, который я использую, который использует SERVICE и SUB-SERVICE, например:

http://3rdparty.api.com?service=APIService.doActionOne&apikey=12341234

Важной частью этого является то, что этот API имеет все то же самое, кроме подэтапа, в данном случае doActionOne, Идея состоит в том, что разработчик (я и другие, использующие этот класс) могут вызывать вспомогательную службу по имени, а не что-то вроде:

$myClass->doAction(array('service'=>'doActionOne','args'=>$args));

Я мог бы сделать вместо этого:

 $myClass->doActionOne($args);

Для жесткого кодирования это было бы просто дублированием (этот пример очень похож на код):

public function doActionOne($array)
    {
        $this->args     =   $array;
        $name           =   __FUNCTION__;
        $this->response =   $this->executeCoreCall("APIService.{$name}");
    }

public function doActionTwo($array)
    {
        $this->args     =   $array;
        $name           =   __FUNCTION__;
        $this->response =   $this->executeCoreCall("APIService.{$name}");
    }

public function doActionThree($array)
    {
        $this->args     =   $array;
        $name           =   __FUNCTION__;
        $this->response =   $this->executeCoreCall("APIService.{$name}");
    }

protected function executeCoreCall($service)
    {
        $cURL = new \cURL();
        return $cURL->('http://3rdparty.api.com?service='.$service.'&apikey='.$this->api.'&'.http_build_query($this->args))
                    ->getResponse();
    }

Но с волшебным методом __call() Я могу получить доступ ко всем службам с помощью динамических методов:

public function __call($name, $arguments)
    {
        $this->args     =   $arguments;
        $this->response =   $this->executeCoreCall("APIService.{$name}");   
        return $this;
    }

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


РЕДАКТИРОВАТЬ:

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

Я правильно использую API?

Существует много способов создания исходного кода в соглашении netbeans. Это мило. Это делает мысли такими проще === ЛОЖЬ. Просто используйте традицию, особенно если вы не уверены, какое из свойств должно быть заключено в капсулу, а какое нет. Я знаю, что это код boi.... pla..., но для отладки и многим другим он кажется лучшим и понятным. Не тратьте много времени на искусство, как делать простые добытчики и сеттеры. Вы не можете также реализовать некоторые шаблоны проектирования, такие как правило demeter и т. Д., Если используете магию. В конкретной ситуации вы можете использовать magic_calls или для небольших, быстрых и понятных решений. Конечно, вы могли бы также найти решения для дизайнеров, но зачем вам жить сложнее.

Проверка + форматирование / получение значений

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

Например, рассмотрим следующий простой класс, который содержит дату рождения.

class BirthDate {

    private $birth_date;

    public function getBirthDate($format='Y-m-d') {
        //format $birth_date ...
        //$birth_date = ...
        return $birth_date;
    }

    public function setBirthDate($birth_date) {                   
        //if($birth_date is not valid) throw an exception ...          
        $this->birth_date = $birth_date;
    }

    public function getAge() {
        //calculate age ...
        return $age;
    }

    public function getDaysUntilBirthday() {
        //calculate days until birth days
        return $days;
    }
}

Вы хотите проверить, что установленное значение

  • Действительная дата
  • Не в будущем

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

Возможно, вы захотите добавить несколько форматеров, которые работают с одной и той же переменной-членом, т.е. getAge() а также getDaysUntilBirthday() и вы можете применить настраиваемый формат в getBirthDate() в зависимости от локали. Поэтому я предпочитаю постоянный доступ к значениям через геттеры, а не к смешиванию $date->getAge() с $date->birth_date,

геттеры и сеттеры также полезны при расширении объектов. Например, предположим, что ваше приложение должно разрешать 150+ лет рождения в некоторых местах, но не в других. Одним из способов решения проблемы без повторения какого-либо кода является расширение BirthDate объект и поставить дополнительную проверку в сеттер.

class LivingBirthDate extends BirthDate {

    public function setBirthDate($birth_date) {
        //if $birth_date is greater than 150 years throw an exception
        //else pass to parent's setter
        return parent::setBirthDate($birth_date);
    }
}

реализовать класс FaceBookAcc, имеющий оболочку пароля члена данных со свойством, которое должно гарантировать, что он должен содержать Alphabet и number при назначении значения. Добавьте логический элемент данных Loggedin и реализуйте функциональные возможности в свойстве для пароля, которое должно гарантировать, что если Loggedin имеет значение true, только значение может быть Access else должно возвращать null. Измените свойство пароля так, чтобы теперь ему было присвоено значение, и к нему нельзя было получить доступ в C #.

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


Я обычно использую это имя переменной в качестве имени функции и добавляю необязательный параметр к этой функции, чтобы, когда этот необязательный параметр заполнялся вызывающей стороной, затем устанавливали его в свойстве и возвращали $ this object (сцепление), а затем, когда этот необязательный параметр не указан вызывающий, я просто возвращаю свойство вызывающему.

Мой пример:

class Model
{
     private $propOne;
     private $propTwo;

     public function propOne($propVal = '')
     {
          if ($propVal === '') {
              return $this->propOne;
          } else {
              $this->propOne = $propVal;
              return $this;
          }
     }

     public function propTwo($propVal = '')
     {
          if ($propVal === '') {
              return $this->propTwo;
          } else {
              $this->propTwo = $propVal;
              return $this;
          }
     }
}
Другие вопросы по тегам