Добытчик и Сеттер?
Я не разработчик 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;
}
Спасибо
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;
}
}
?>
Зачем использовать геттеры и сеттеры?
- Масштабируемость: Проще рефакторировать геттер, чем искать все назначения var в коде проекта.
- Отладка: Вы можете устанавливать точки останова в установщиках и получателях.
- Очиститель: Магические функции не являются хорошим решением для записи меньше, ваша среда разработки не предложит код. Лучше использовать шаблоны для быстрого написания геттеров.
Google уже опубликовал руководство по оптимизации PHP, и вывод был таким:
Нет добытчика и сеттера Оптимизация PHP
И нет, вы не должны использовать магические методы . Для PHP Магический метод - это зло. Почему?
- Их трудно отлаживать.
- Существует негативное влияние на производительность.
- Они требуют написания большего количества кода.
PHP - это не Java, C++ или C#. PHP отличается и играет разные роли.
Инкапсуляция важна в любом языке 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
) также работает, но только при доступе к свойству, которое имеет более низкую видимость, чем доступ к текущей области. Оно может легко доставит вам головную боль при попытке отладки, если он не используется должным образом.
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
Готово!
Если вы предпочитаете использовать функцию __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;
}
}
В дополнение к уже замечательным и уважаемым ответам здесь, я хотел бы подробнее рассказать о 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. Я не утверждаю, что геттеры/сеттеры, как правило, плохие.
Ну, PHP действительно имеет магические методы __get
, __set
, __isset
& __unset
, что всегда является началом. Увы, правильно (понимаете?) Свойства OO - это больше, чем магические методы. Основная проблема с реализацией PHP заключается в том, что магические методы вызываются для всех недоступных свойств. Это означает, что вы должны повторить себя (например, вызвав property_exists()) в магических методах при определении того, является ли имя на самом деле свойством вашего объекта. И вы действительно не можете решить эту проблему общая проблема с базовым классом, если только все ваши классы не наследуются от ie. ClassWithProperties, так как PHP не имеет множественного наследования.
Напротив, Python новые классы стилей дают вам property()
, что позволяет явно определять все ваши свойства. C# имеет особый синтаксис.
Прочитав другие советы, я склонен сказать, что:
В качестве ОБЩЕГО правила вы не всегда будете определять установщики для ВСЕХ свойств, особенно "внутренних" (семафоры, внутренние флаги...). Доступные только для чтения свойства, очевидно, не будут иметь установщиков, поэтому у некоторых свойств будут только получатели; вот где __get() сокращает код:
- определите __get() (магические глобальные добытчики) для всех тех свойств, которые подобно,
- сгруппируйте их в массивы так, чтобы:
- у них будут общие характеристики: денежные значения будут/могут быть правильно отформатированы, даты в определенном формате (ISO, США, Международный) и т. Д.
- сам код может проверить, что с помощью этого волшебного метода считываются только существующие и разрешенные свойства.
- всякий раз, когда вам нужно создать новое аналогичное свойство, просто объявите его и добавьте его имя в соответствующий массив, и все готово. Это намного БЫСТРЕЕ, чем определение новый геттер, возможно, с некоторыми строками кода, повторяющимися снова и снова по всему коду класса.
Да! мы также могли бы написать частный метод для этого, но опять же, у нас будет МНОГО объявленных методов (память++), которые в конечном итоге вызовут другой, всегда один и тот же метод. Почему бы просто не написать ОДИН метод, чтобы управлять ими всеми...? [ага! каламбур абсолютно намеренный! :)]
Установщики магии также могут реагировать ТОЛЬКО на определенные свойства, поэтому все типы дат свойства могут быть проверены на наличие недопустимых значений только одним методом. Если свойства типа даты были перечислены в массиве, их установщики можно легко определить. Просто пример, конечно. существует слишком много ситуаций.
О удобочитаемости ... Хорошо... Это еще один спор: мне не нравится быть привязанным к использованию IDE (на самом деле, я их не использую, они, как правило, говорят мне (и заставляютменя), как писать... и у меня есть свои симпатии по поводу кодирования "красоты"). Я склонен быть последовательным что касается именования, то для меня достаточно использовать ctags и пару других вспомогательных средств... В любом случае: как только все эти волшебные сеттеры и геттеры будут выполнены, я напишу другие сеттеры, которые слишком специфичны или "особенные", чтобы их можно было обобщить в методе __set(). И это охватывает все, что мне нужно для получения и настройки свойств. Конечно: не всегда есть точки соприкосновения, или есть такое количество свойств, которые не стоят того, чтобы кодировать магический метод, и тогда все еще остается старый добрый традиционный пара сеттер/геттер.
Языки программирования - это просто человеческие искусственные языки. Итак, у каждого из них своя интонация или акцент, синтаксис и вкус, поэтому я не буду притворяться, что пишу код на Ruby или Python, используя тот же "акцент", что и Java или C#, и я бы не написал JavaScript или PHP, чтобы походить на Perl или SQL... Используйте их так, как они предназначены для использования.
Я провел эксперимент, используя магический метод __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
.
.
.
Это все.
Док: __вызов() запускается при вызове недоступных методов в контексте объекта.
Вообще говоря, первый способ в целом более популярен, потому что те, у кого есть предварительные знания в области программирования, могут легко перейти на PHP и выполнять работу объектно-ориентированным способом. Первый способ более универсален. Мой совет состоял бы в том, чтобы придерживаться того, что проверено и верно на многих языках. Затем, когда и если вы будете использовать другой язык, вы будете готовы добиться чего-то ( вместо того, чтобы тратить время на изобретение колеса).
Существует множество способов создания исходного кода в соглашении netbeans. Это мило. Это делает мысли такими легкими === ЛОЖНЫМИ. Просто используйте traditionel, особенно если вы не уверены, какое из свойств следует инкапсулировать, а какое нет. Я знаю, это boi....pla... код, но для отладки - работает, и многие другие считают, что это лучший, понятный способ. Не тратьте много времени на изучение тысячи искусств, как создавать простых добытчиков и сеттеров. Вы тоже не можете реализовать какой-то дизайн шаблоны, такие как правило деметры и так далее, если вы используете магию. В конкретной ситуации вы можете использовать magic_calls или для небольших, быстрых и понятных решений. Конечно, вы тоже могли бы таким образом создавать решения для шаблонов дизайна, но зачем усложнять вам жизнь?
Проверка +Форматирование/Получение значений
Установщики позволяют проверять данные, а получатели позволяют форматировать или извлекать данные. Объекты позволяют вам инкапсулировать данные и их код проверки и форматирования в аккуратный пакет, который поощряет СУХОСТЬ.
Например, рассмотрим следующий простой класс, содержащий дату рождения.
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);
}
}
Этот пост не посвящен конкретно __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, которым я был ссылаясь на то, что применение методов идентично:
Обновление: Не используйте этот ответ, так как это очень глупый код, который я нашел, пока учился. Просто используйте простой геттер и сеттер, это намного лучше.
Я обычно использую это имя переменной в качестве имени функции и добавляю необязательный параметр в эту функцию, поэтому, когда этот необязательный параметр заполняется вызывающим, затем устанавливаю его в свойство и возвращаю $этот объект (цепочка), а затем, когда этот необязательный параметр не указан вызывающим, я просто возвращаю свойство вызывающему объекту. вызывающий абонент.
Мой пример:
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;
}
}
}