Добытчик и Сеттер?


Я не разработчик 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;
}

Спасибо

Author: David, 2010-12-18

15 answers

Вы можете использовать волшебные методы 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;
  }
}
?>
 192
Author: Dave, 2011-05-19 01:06:30

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

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

direct assignment and getters/setters

 106
Author: Wiliam, 2012-01-21 18:25:09

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

Нет добытчика и сеттера Оптимизация PHP

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

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

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

 30
Author: magallanes, 2018-08-27 20:37:37

Инкапсуляция важна в любом языке OO, популярность не имеет к этому никакого отношения. В динамически типизированных языках, таких как 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) также работает, но только при доступе к свойству, которое имеет более низкую видимость, чем доступ к текущей области. Оно может легко доставит вам головную боль при попытке отладки, если он не используется должным образом.

 12
Author: netcoder, 2010-12-18 15:44:24
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

Готово!

 6
Author: joas, 2012-11-06 14:23:36

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

  • ПОЛУЧИТЬ => $this->property()
  • УСТАНОВИТЬ => $this->property($value)
  • ПОЛУЧИТЬ => $this->getProperty()
  • УСТАНОВИТЬ => $this->setProperty($value)

Кальсдас

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;
    }
}
 6
Author: J-Rou, 2013-08-20 20:05:43

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

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

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

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

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

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

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

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

__ получить /__установить использование:

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

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

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

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

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

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

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

 6
Author: Lorenz Lo Sauer, 2017-03-20 10:29:34

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

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

Http://en.wikipedia.org/wiki/Property_ (программирование)

 5
Author: Emanuel Landeholm, 2018-08-14 13:32:13

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

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

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

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

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

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

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

 2
Author: Manuel, 2015-01-27 16:20:08

Я провел эксперимент, используя магический метод __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".
Вы также можете использовать этот "белый список" для назначения пользовательских имен для доступа к вашим vars.
Например,

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

.
.
.
Это все.


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

 2
Author: Saba, 2015-11-24 08:13:16

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

 1
Author: Anthony Rutledge, 2015-04-09 14:14:14

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

 0
Author: Dennis Komnenovic, 2013-03-10 10:05:56

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

Установщики позволяют проверять данные, а получатели позволяют форматировать или извлекать данные. Объекты позволяют вам инкапсулировать данные и их код проверки и форматирования в аккуратный пакет, который поощряет СУХОСТЬ.

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

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);
    }
}
 0
Author: FuzzyTree, 2014-09-25 02:09:46

Этот пост не посвящен конкретно __get и __set, а скорее __call, что является той же идеей, за исключением вызова метода. Как правило, я держусь подальше от любых магических методов, которые допускают перегрузку по причинам, изложенным в комментариях и сообщениях ОДНАКО Недавно я столкнулся с сторонним API, который я использую, который использует СЛУЖБУ и ВСПОМОГАТЕЛЬНУЮ СЛУЖБУ, пример:

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?

 0
Author: Rasclatt, 2017-05-23 12:18:01

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


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

Мой пример:

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;
          }
     }
}
 -2
Author: ajiyakin, 2017-12-15 02:23:08