我不是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;
}

你可以使用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;
  }
}
?>

封装在任何面向对象语言中都很重要,与受欢迎程度无关。在动态类型语言(如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)也可以,但只有当访问的属性的可见性低于当前作用域可以访问的时候。如果使用不当,调试时很容易让您头疼。


Well, PHP does have magic methods __get, __set, __isset & __unset, which is always a start. Alas proper (get it?) OO properties is more than magic methods. The main problem with PHP's implementation is that magic methods are called for all inaccessible properties. Which means you have to Repeat Yourself (eg. by calling property_exists()) in the magic methods when determining if name is actually a property of your object. And you can't really solve this general problem with a base class unless all your classes inherit from ie. ClassWithProperties, since PHP lacks multiple inheritance.

相比之下,Python的新样式类提供了property(),它允许显式地定义所有属性。c#有特殊的语法。

http://en.wikipedia.org/wiki/Property_(编程)


为什么使用getter和setter ?

可伸缩性:重构getter比在项目代码中搜索所有的var赋值更容易。 调试:可以在setter和getter中设置断点。 更简洁:神奇的函数不是很好的解决方案,你的IDE不会建议代码。最好使用模板来快速编写getter。


如果你喜欢使用__call函数,你可以使用这个方法。它适用于

GET => $this->属性() SET => $this->属性($value) getProperty() => $this-> 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;
    }
}

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

准备好了!


There are many ways to create sourcecode in a netbeans-convention. This is nice. It makes thinks such easyer === FALSE. Just use the traditionel, specially if you are not sure which one of the properties should be encapsuled and which one not. I know, it is a boi.... pla... code, but for debugging-works and many other thinks it is the better, clear way. Dont spend to much time with thousend of arts how to make simple getters and setters. You cannot implement too some design patterns like the demeter-rule and so on, if you use magics. In specific situation you can use magic_calls or for small, fast and clear solutions. Sure you could make solutions for design-patters in this way too, but why to make you live more difficult.


谷歌已经发布了一个关于PHP优化的指南,结论是:

没有getter和setter优化PHP

不,你不能使用魔法方法。对于PHP来说,魔法方法是邪恶的。为什么?

它们很难调试。 会对性能产生负面影响。 它们需要编写更多的代码。

PHP不是Java、c++或c#。PHP是不同的,它遵循不同的规则。


除了这里已经很好的和受人尊敬的答案之外,我还想扩展一下没有setter /getter的PHP。

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

魔术让我们这些懒惰的程序员在积极参与一个项目并熟悉它的时候,用更少的代码做更多的事情,但通常是以可读性为代价的。

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

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

偏好:个人而言,作为我的经验法则,我接受静态代码分析的失败 作为避免走上这条神奇之路的标志,只要我当时没有明显的长期利益。

谬论:

一个常见的论点是可读性。例如,$someobject->width比$someobject->width()更容易读取。然而,与行星的周长或宽度可以假定为静态的不同,对象的实例(例如$someobject)需要一个width函数,可能需要测量对象的实例宽度。 因此,可读性的提高主要是因为确定的命名方案,而不是通过隐藏输出给定属性值的函数。

__get / __set使用:

属性值的预验证和预清理 字符串。 " 一些{mathsobj1->generatelatex}多 行文本{mathsobj1->latexoutput} {mathsobj1->generatelatex}有很多变量 一些原因 " 在这种情况下,generatelatex将遵循actionname + methodname的命名方案 特殊的,明显的案例 dnastringobj - >同源框(one_rememberable_parameter美元)- >千钧一发——> findrelated () dnastringobj - >同源框(one_rememberable_parameter美元)- > gttccaatttga - > findrelated ()

注意:PHP选择不实现getter/setter语法。我并不是说getter /setter一般都不好。


验证+格式化/导出值

setter允许您验证数据,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);
    }
}

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

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

define a __get() (magical global getters) for all those properties which are alike, group them in arrays so: they'll share common characteristics: monetary values will/may come up properly formatted, dates in an specific layout (ISO, US, Intl.), etc. the code itself can verify that only existing & allowed properties are being read using this magical method. whenever you need to create a new similar property, just declare it and add its name to the proper array and it's done. That's way FASTER than defining a new getter, perhaps with some lines of code REPEATED again and again all over the class code.

是的!我们也可以写一个私有方法来做到这一点,但话又说回来,我们将声明许多方法(++内存),最终调用另一个总是相同的方法。为什么不写一个单一的方法来管理它们呢?(是的!双关语!)):

Magic setter也可以只响应特定的属性,因此可以单独在一个方法中筛选所有日期类型属性的无效值。如果日期类型属性列在数组中,则可以轻松定义它们的setter。当然,这只是一个例子。有太多的情况。

About readability... Well... That's another debate: I don't like to be bound to the uses of an IDE (in fact, I don't use them, they tend to tell me (and force me) how to write... and I have my likes about coding "beauty"). I tend to be consistent about naming, so using ctags and a couple of other aids is sufficient to me... Anyway: once all this magic setters and getters are done, I write the other setters that are too specific or "special" to be generalized in a __set() method. And that covers all I need about getting and setting properties. Of course: there's not always a common ground, or there are such a few properties that is not worth the trouble of coding a magical method, and then there's still the old good traditional setter/getter pair.

编程语言就是人类的人工语言。因此,它们都有自己的语调或重音、语法和风格,所以我不会假装用与Java或c#相同的“重音”来编写Ruby或Python代码,也不会编写类似Perl或SQL的JavaScript或PHP……用他们该用的方式去使用他们。


一般来说,第一种方法总体上更受欢迎,因为具有编程知识的人可以轻松地转换到PHP,并以面向对象的方式完成工作。第一种方法更为普遍。我的建议是坚持使用在许多语言中都行之有效的方法。然后,当你使用另一种语言时,你将准备好完成一些事情(而不是花时间重新发明轮子)。


我做了一个实验,使用神奇的方法__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

. . . 这是所有。


道格: 在对象上下文中调用不可访问的方法时触发__call()。


更新:不要使用这个答案,因为这是我在学习过程中发现的非常愚蠢的代码。使用普通的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;
          }
     }
}

这篇文章不是专门关于__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 ?