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

当前回答

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

其他回答

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

没有getter和setter优化PHP

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

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

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

你可以使用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)也可以,但只有当访问的属性的可见性低于当前作用域可以访问的时候。如果使用不当,调试时很容易让您头疼。

验证+格式化/导出值

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

如果你喜欢使用__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;
    }
}