我不是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)中,它特别有用,因为如果不使用设置符,几乎没有办法确保属性是特定类型的。

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

其他回答

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

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_(编程)

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

准备好了!

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

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

没有getter和setter优化PHP

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

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

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