关于php:Getter和Setter?

Getter and Setter?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
    }
}

或者只是公共领域:

1
2
3
4
class MyClass {
    public $firstField;
    public $secondField;
}

谢谢


您可以使用php magic方法__get__set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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;
  }
}
?>


为什么要使用getter和setter?

  • 可伸缩性:重构getter比搜索项目代码中的所有var分配更容易。
  • 调试:可以在setter和getter上放置断点。
  • Cleaner:魔术函数并不能很好地解决写得少的问题,您的IDE不会建议使用代码。最好使用模板快速写入getter。
  • direct assignment and getters/setters


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

    没有getter和setter优化php

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

  • 它们很难调试。
  • 这会对性能产生负面影响。
  • 他们需要写更多的代码。
  • PHP不是Java、C++或C语言。PHP是不同的,具有不同的角色。


    封装在任何OO语言中都很重要,流行与此无关。在动态类型语言(如PHP)中,它特别有用,因为在不使用setter的情况下,几乎没有办法确保属性是特定类型的。

    在PHP中,这是有效的:

    1
    2
    3
    4
    5
    class Foo {
       public $bar; // should be an integer
    }
    $foo = new Foo;
    $foo->bar ="string";

    在Java中,它不:

    1
    2
    3
    4
    5
    class Foo {
       public int bar;
    }
    Foo myFoo = new Foo();
    myFoo.bar ="string"; // error

    使用magic方法(__get__set也可以,但只有在访问可见性低于当前范围的属性时才能访问。如果使用不当,在调试时很容易让人头疼。


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

    PHP没有getter和setter语法。正如Dave指出的,它提供了子类或魔术方法来允许"挂接"并覆盖属性查找过程。

    magic允许我们懒惰的程序员在我们积极参与一个项目并密切了解它的时候用更少的代码做更多的工作,但通常以牺牲可读性为代价。

    性能在PHP中强制使用getter/setter类型的代码体系结构所导致的每一个不必要的函数,在调用时都涉及到它自己的内存堆栈帧,这是在浪费CPU周期。

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

    偏好:就我个人而言,根据我的经验,我认为静态代码分析的失败这是一个避免走上魔幻之路的标志,只要在那个时候明显的长期利益不复存在。

    谬误:

    一个常见的参数是可读性。例如,$someobject->width$someobject->width()更容易读取。然而,不同于行星的circumferencewidth,可以假设为static,物体的实例(如$someobject)需要宽度函数,可能需要测量物体的实例宽度。因此,可读性的提高主要是由于断言的命名方案,而不是通过将输出给定属性值的函数隐藏起来。

    _获取/设置使用:

    • 财产价值的预验证和预卫生处理

    • 字符串,例如

      1
      2
      3
      4
      5
      6
      "
      some {mathsobj1->generatelatex} multi
      line text {mathsobj1->latexoutput}
      with lots of variables for {mathsobj1->generatelatex}
       some reason
      "

      在这种情况下,generatelatex将遵循actionname+methodname的命名方案。

    • 特殊、明显情况

      1
      2
      $dnastringobj->homeobox($one_rememberable_parameter)->gattaca->findrelated()
      $dnastringobj->homeobox($one_rememberable_parameter)->gttccaatttga->findrelated()

    注意:PHP选择不实现getter/setter语法。我并不是说getter/setter通常是坏的。


    如果您预先选择使用_uuu调用函数,则可以使用此方法。它与

    • GET=>$this->property()
    • set=>$this->property($value)
    • GET=>$this->getProperty()
    • set=>$this->setProperty($value)

    卡尔斯达斯

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    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;
        }
    }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    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确实有神奇的方法__get__set__isset__unset,这总是一个开始。唉,很恰当(明白了吗?)OO属性不仅仅是魔术方法。PHP实现的主要问题是对所有不可访问的属性调用了magic方法。这意味着在确定名称是否是对象的属性时,您必须在魔力方法中重复自己(例如,通过调用property_exists())。而且,除非所有类都继承自IE.ClassWithProperties,否则用基类无法真正解决这个一般问题,因为PHP缺少多个继承。

    相反,python新样式类提供了property(),它允许您显式定义所有属性。C有特殊的语法。

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


    我用魔法方法做了一个实验。不确定我是否应该发布它(因为其他答案和评论中的所有"不要使用魔法方法"警告),但我将把它留在这里。以防万一有人发现它有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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];
        }
    }

    只需在类中添加上述方法,现在可以键入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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

    这样,您就可以获取/设置类中的所有内容(如果它存在的话),如果您只需要一些特定的元素,您可以使用"白名单"作为过滤器。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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"。您还可以使用该"白名单"分配自定义名称以访问var。例如,

    1
    2
    3
    4
    5
    private $callWhiteList = array(
       "myfoo" =>"foo",
       "zim" =>"bom",
        // ...
    );

    使用该列表,您现在可以键入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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

    ...这就是全部。

    Doc:_调用()是在对象上下文中调用不可访问的方法时触发的。


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

    作为一般规则,您不会总是为所有属性定义setter,特别是"内部"属性(信号量、内部标志…)。显然,只读属性没有setter,因此某些属性只有getter;这就是uuget()收缩代码的地方:

    • 为所有相似的属性定义一个uu get()(神奇的全局getter),
    • 将它们按数组分组,以便:
      • 它们将具有共同的特征:货币价值将/可能得到正确的格式、特定布局中的日期(ISO、US、INTL)等。
      • 代码本身可以验证使用这种神奇的方法只读取现有的和允许的属性。
      • 每当需要创建一个新的类似属性时,只需声明它并将其名称添加到适当的数组中,即可完成。这比定义一个新的getter快得多,可能在类代码中反复地重复一些代码行。

    对!我们也可以编写一个私有方法来实现这一点,但同样,我们将有许多方法声明为(++内存),最终调用另一个方法,总是相同的方法。为什么不写一个单一的方法来控制它们呢?是的!双关语绝对是有意的!:)

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

    关于可读性…好。。。这是另一个争论:我不喜欢被绑定到IDE的使用上(事实上,我不使用它们,它们倾向于告诉我(并强迫我)如何写…我喜欢编写"美丽"的代码。我在命名上倾向于前后一致,所以使用ctags和其他一些辅助工具就足够了…不管怎样:一旦所有的魔法设置器和getter都完成了,我就会编写其他的设置器,它们太具体或"特殊",无法在uu set()方法中进行一般化。这涵盖了我获取和设置属性所需要的一切。当然:这并不总是有共同点,或者有这么一些属性不值得费劲地编写一个神奇的方法,然后还有一对传统的优秀的setter/getter。

    编程语言就是这样:人类人工语言。所以,他们每个人都有自己的语调或口音,语法和味道,所以我不会假装写一个Ruby或Python代码使用相同的"口音"比Java或C,我也不会写一个JavaScript或PHP类似Perl或SQL…按照他们的使用方式使用它们。


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


    这篇文章不是关于__get__set的,而是关于__call的,除了方法调用外,它的思想是相同的。通常,我会远离任何类型的魔法方法,因为注释和帖子中列出的原因而允许重载。但是,我最近遇到了一个使用服务和子服务的第三方API,例如:

    1
    http://3rdparty.api.com?service=APIService.doActionOne&apikey=12341234

    其中重要的一点是,除了子操作,这个API的所有内容都是相同的,在本例中是doActionOne。其思想是开发人员(我自己和其他使用此类的人)可以按名称调用子服务,而不是像这样:

    1
    $myClass->doAction(array('service'=>'doActionOne','args'=>$args));

    我可以这样做:

    1
     $myClass->doActionOne($args);

    要进行硬编码,这将是大量重复(本例非常松散地类似于代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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()的神奇方法,我可以使用动态方法访问所有服务:

    1
    2
    3
    4
    5
    6
    public function __call($name, $arguments)
        {
            $this->args     =   $arguments;
            $this->response =   $this->executeCoreCall("APIService.{$name}");  
            return $this;
        }

    这种对返回数据的动态调用的好处是,如果供应商添加了另一个子服务,我不必在类中添加另一个方法或创建扩展类等。我不确定这是否对任何人有用,但我想我将展示一个示例,其中__set__get__call等可能是主要功能是返回数据。

    编辑:

    巧合的是,我在发布几天后看到了这一点,它正好勾勒出了我的场景。我指的不是API,但方法的应用是相同的:

    我是否正确使用API?


    验证+格式化/派生值

    setter允许您验证数据,getter允许您格式化或派生数据。对象允许您将数据及其验证和格式化代码封装到一个整洁的包中,这会导致数据变干。

    例如,考虑下面包含出生日期的简单类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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中进行验证,因为这样无论对象来自应用程序的哪个部分,您都会知道对象包含有效的出生日期,如果要添加更多的验证,则可以将其添加到s in中。格尔普广场。

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

    当您扩展对象时,getter和setter也很有用。例如,假设您的应用程序需要在某些地方(而不是在其他地方)允许150年以上的出生日期。解决这个问题而不重复任何代码的一种方法是扩展BirthDate对象并在setter中进行额外的验证。

    1
    2
    3
    4
    5
    6
    7
    8
    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);
        }
    }


    在NetBeans约定中,有许多方法可以创建源代码。这很好。它使思考变得如此容易==错误。只需使用Traditionel,特别是在您不确定应该封装哪个属性和不封装哪个属性的情况下。我知道,这是一个博伊……解放军…代码,但对于调试工作和许多其他人认为这是更好的,明确的方式。不要花太多的时间在千篇一律的艺术上,如何制作简单的打手和二传手。如果你使用魔术师,你就不能实现太多的设计模式,比如德米特规则等等。在特定的情况下,您可以使用magic_呼叫,也可以使用小型、快速和清晰的解决方案。当然,你也可以用这种方式为设计模式制定解决方案,但是为什么要让你活得更困难呢?


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

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

    我的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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;
              }
         }
    }