Getter和Setter?

时间:2010-12-18 15:29:32

标签: php oop coding-style

我不是PHP开发人员,所以我想知道在PHP中使用显式getter / setter,以纯OOP样式,私有字段(我喜欢的方式)是否更受欢迎:

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 个答案:

答案 0 :(得分:208)

您可以使用php magic methods __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 :(得分:107)

为什么要使用getter和setter?

  1. 可伸缩性:比搜索项目代码中的所有var分配更容易重构getter。
  2. 调试:您可以在设置者和获取者处设置断点。
  3. 清洁:Magic函数不是写入较少的好解决方案,您的IDE不会建议代码。更好地使用模板来快速写入getter。
  4. direct assignment and getters/setters

答案 2 :(得分:36)

Google已经发布了PHP优化指南,结论是:

没有getter和setter Optimizing PHP

不,你必须不使用魔术方法。对于PHP,Magic Method是邪恶的。为什么呢?

  1. 他们很难调试。
  2. 对性能产生负面影响。
  3. 他们需要编写更多代码。
  4. PHP不是Java,C ++或C#。 PHP是不同的,扮演不同的角色。

答案 3 :(得分:12)

封装在任何OO语言中都很重要,流行度与它无关。在动态类型语言(如PHP)中,它特别有用,因为没有使用setter的方法可以确保属性属于特定类型。

在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)也有效,但仅在访问可见性低于当前范围的属性时才能访问。如果使用不当,它很容易让您在尝试调试时感到头疼。

答案 4 :(得分:7)

如果您想使用__call函数,则可以使用此方法。它适用于

  • GET =&gt; $this->property()
  • SET =&gt; $this->property($value)
  • GET =&gt; $this->getProperty()
  • SET =&gt; $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;
    }
}

答案 5 :(得分:7)

除了这里已经很好的和受尊重的答案之外,我想扩展PHP没有setter / getters。

PHP没有getter和setter语法 。它提供了子类或 magic 方法,允许“挂钩”并覆盖属性查找过程,如 Dave 所指出。

魔术允许us lazy programmers在我们积极参与项目并密切了解项目的情况下使用更少的代码执行更多操作,但通常会牺牲可读性。

性能由于在PHP中强制使用类似getter / setter的代码体系结构而导致的每个不必要的函数都会在调用时涉及自己的内存堆栈帧并浪费CPU周期。

可读性:代码库会产生膨胀的代码行,这会影响代码导航,因为更多的LOC意味着更多的滚动,。

偏好:就个人而言,作为我的经验法则,我采取了静态代码分析的失败 只要当时没有明显的长期利益,就可以避免走上神奇的道路。

<强>谬误:

一个常见的论点是可读性。例如,$someobject->width$someobject->width()更容易阅读。然而,与行星circumferencewidth(可以假设为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语法。我并不是说getter / setter一般都不好。

答案 6 :(得分:6)

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

就绪!

答案 7 :(得分:5)

嗯, PHP 确实有魔术方法__get__set__isset&amp; __unset,这始终是一个开始。唉正确(得到它?)OO属性不仅仅是魔术方法。 PHP实现的主要问题是为所有不可访问的属性调用了魔术方法。这意味着在确定 name 实际上是否是对象的属性时,您必须在魔术方法中重复自己(例如,通过调用property_exists())。除非你的所有类继承自ie,否则你无法用基类真正解决这个一般性问题。 ClassWithProperties,因为PHP缺少多重继承。

相比之下, Python 新样式类为您提供了property(),可让您明确定义所有属性。 C#具有特殊语法。

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

答案 8 :(得分:3)

我使用魔法__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”和“费用” 您还可以使用该“白名单”来指定自定义名称以访问您的变量 例如,

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




就是这样。

<强>文件: 在对象上下文中调用不可访问的方法时会触发__call()

答案 9 :(得分:2)

在阅读其他建议后,我倾向于这样说:

作为 GENERIC 规则,您不会始终为所有属性定义设置器,特别是“内部”属性(信号量,内部标志...)。显然,只读属性不会有setter,所以有些属性只有getter;这就是__get()缩小代码的地方:

  • 为所有相似的属性定义__get()(神奇的全局getter),
  • 将它们分组在数组中:
    • 他们将分享共同的特征:货币价值将/可能正确格式化,日期在特定布局(ISO,US,Intl。)等。
    • 代码本身可以验证只有现有的&amp;正在使用这种神奇的方法阅读允许的属性。
    • 每当你需要创建一个新的类似属性时,只需声明它并将其名称添加到正确的数组即可。这就像 FASTER 一样,而不是定义一个新的getter,可能会在代码中一遍又一遍地重复一些代码行。

是的!我们可以编写一个私有方法来做到这一点,但是,我们将再次声明许多方法(++内存),最终调用另一个方法,总是相同的方法。为什么不写一个 SINGLE 方法来统治所有 ...? [没错!双关语绝对有意! :)]

魔术设置器也可以仅响应特定属性,因此可以仅在一个方法中针对无效值筛选所有日期类型属性。如果数组中列出了日期类型属性,则可以轻松定义其setter。当然,只是一个例子。有太多的情况。

关于可读性 ......嗯......这是另一个争论:我不喜欢受限于IDE的使用(事实上,我不使用它们,它们倾向于告诉我(和强迫我)如何写...我喜欢编码“美女”。我倾向于对命名保持一致,所以使用ctags和其他一些辅助工具对我来说已经足够了......无论如何:一旦所有这些神奇的制定者和吸气者都完成了,我就会编写其他太具体或“特殊”的定位器在__set()方法中推广。这涵盖了获取和设置属性所需的全部内容。当然:并不总是有一个共同点,或者有这么几个属性不值得编写一个神奇的方法的麻烦,然后仍然有旧的传统的setter / getter对。

编程语言就是这样的:人工语言。所以,它们每个都有自己的语调或重音,语法和风格,所以我不会假装使用与Java或C#相同的“重音”来编写Ruby或Python代码,也不会编写类似于JavaScript或PHP的代码。 Perl或SQL ...按照它们的使用方式使用它们。

答案 10 :(得分:1)

一般来说,第一种方式总体上更受欢迎,因为具有先前编程知识的人可以轻松过渡到PHP并以面向对象的方式完成工作。第一种方式更普遍。我的建议是坚持使用多种语言的尝试和真实。然后,当你使用另一种语言时,你就会准备好完成一些事情(而不是花时间重新发明轮子)。

答案 11 :(得分:0)

有许多方法可以在netbeans-convention中创建源代码。这很好。它使得认为这样的宽松= = FALSE。只需使用传统,特别是如果您不确定哪个属性应该被封装而哪个属性不被封装。我知道,这是一个boi .... pla ...代码,但对于调试工作和许多其他人认为这是更好,更明确的方式。不要花太多时间与艺术家如何制作简单的吸气剂和制定者。如果你使用魔法,你不能实现像demeter-rule等一些设计模式。在特定情况下,您可以使用magic_calls或小型,快速和清晰的解决方案。当然,你也可以用这种方式为设计模型制作解决方案,但为什么要让你的生活变得更加困难。

答案 12 :(得分:0)

验证+格式化/派生值

Setters可让您验证数据和getter,让您格式化或派生数据。对象允许您将数据及其验证和格式化代码封装到一个鼓励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;
    }
}

您需要验证所设置的值是否为

  • 有效日期
  • 不是将来

并且您不希望在整个应用程序(或多个应用程序)上进行此验证。相反,更容易使成员变量受保护或私有(为了使setter成为唯一的访问点)并在setter中进行验证,因为这样你就知道该对象包含有效的出生日期无论对象来自哪个应用程序部分,如果您想添加更多验证,那么您可以将它添加到一个地方。

您可能希望添加对同一成员变量(即getAge()getDaysUntilBirthday()进行操作的多个格式化程序,并且您可能希望根据区域设置在getBirthDate()中强制实施可配置格式。因此,我希望始终通过getter访问值,而不是将$date->getAge()$date->birth_date混合。

扩展对象时,

getter和setter也很有用。例如,假设您的应用程序需要在某些地方允许超过150年的出生日期,而在其他地方则不允许。解决问题而不重复任何代码的一种方法是扩展BirthDate对象并将其他验证放入setter中。

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);
    }
}

答案 13 :(得分:0)

这篇文章并非专门针对__get__set,而是__call,除了方法调用之外,这个想法是相同的。作为一项规则,我远离任何类型的魔术方法,允许超载由于评论和帖子 HOWEVER 中概述的原因,我最近遇到了我使用的第三方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,但方法的应用是相同的:

Am I using api correctly?

答案 14 :(得分:-2)

更新:不要使用这个答案,因为这是我学习时发现的非常愚蠢的代码。只需使用普通的getter和setter,它就会好得多。

我通常使用该变量名作为函数名,并向该函数添加可选参数,以便当调用者填充该可选参数时,将其设置为属性并返回$ this对象(链接),然后当该可选参数没有被调用者指定,我只是将属性返回给调用者。

我的例子:

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