使用 getter 和 setter

乍一看,定义具有公共属性的类似乎是有意义的,这些属性可以直接被读取或写入。然而,我们认为最好的做法是让属性受保护,然后为每个属性定义一个getter和setter。顾名思义,getter用于检索属性的值。setter用于设置值。

{% hint style=”info” %} 最佳实践

将属性定义为受保护的,以防止意外的外部访问。使用公共的 get* 和 __set* 方法来提供对这些属性的访问。通过这种方式,不仅可以更精确地控制访问,还可以在获取和设置属性的同时对其进行格式和数据类型的更改。 {% endhint %}

如何做…

1.当获取或设置值时,Getters和setters提供了额外的灵活性。如果需要的话,你可以添加一个额外的逻辑层,如果你直接读取或写入一个公共属性,这是不可能的。你需要做的就是创建一个前缀为 getset 的公共方法。属性的名称就成了后缀。惯例是让变量的第一个字母大写。因此,如果属性是 $testValue ,那么getter将是 getTestValue()

  1. 在这个例子中,我们定义了一个带有保护属性 $date 的类。请注意,getset 方法允许作为 DateTime 对象或字符串来处理。在任何情况下,该值实际上都是作为一个 DateTime 实例存储的。
  1. $a = new class() {
  2. protected $date;
  3. public function setDate($date)
  4. {
  5. if (is_string($date)) {
  6. $this->date = new DateTime($date);
  7. } else {
  8. $this->date = $date;
  9. }
  10. }
  11. public function getDate($asString = FALSE)
  12. {
  13. if ($asString) {
  14. return $this->date->format('Y-m-d H:i:s');
  15. } else {
  16. return $this->date;
  17. }
  18. }
  19. };
  1. getters和setters允许你过滤进入或流出的数据。在下面的例子中,有两个属性,$intVal$arrVal,它们被设置为默认的初始值 NULL。注意,不仅获取者的返回值是数据类型的,而且还提供了默认值。设置器还强制执行传入的数据类型,或者将传入的值类型化为某种数据类型。
  1. <?php
  2. class GetSet
  3. {
  4. protected $intVal = NULL;
  5. protected $arrVal = NULL;
  6. // note the use of the null coalesce operator to return a default value
  7. public function getIntVal() : int
  8. {
  9. return $this->intVal ?? 0;
  10. }
  11. public function getArrVal() : array
  12. {
  13. return $this->arrVal ?? array();
  14. }
  15. public function setIntVal($val)
  16. {
  17. $this->intVal = (int) $val ?? 0;
  18. }
  19. public function setArrVal(array $val)
  20. {
  21. $this->arrVal = $val ?? array();
  22. }
  23. }
  1. 如果有一个有很多很多属性的类,为每个属性定义一个不同的getter和setter可能会变得很乏味。在这种情况下,你可以使用神奇的 __call() 方法定义一种回调。下面这个类定义了九个不同的属性。我们不必定义九个getter和九个setter,而是定义了一个方法 __call(),它可以确定使用方式是 get 还是 set。如果是 get,它从内部数组中检索键。如果是set,它将值存储在内部数组中。

{% hint style=”info” %} __call() 方法是一个神奇的方法,如果应用程序对一个不存在的方法进行调用,它就会被执行。 {% endhint %}

  1. <?php
  2. class LotsProps
  3. {
  4. protected $firstName = NULL;
  5. protected $lastName = NULL;
  6. protected $addr1 = NULL;
  7. protected $addr2 = NULL;
  8. protected $city = NULL;
  9. protected $state = NULL;
  10. protected $province = NULL;
  11. protected $postalCode = NULL;
  12. protected $country = NULL;
  13. protected $values = array();
  14. public function __call($method, $params)
  15. {
  16. preg_match('/^(get|set)(.*?)$/i', $method, $matches);
  17. $prefix = $matches[1] ?? '';
  18. $key = $matches[2] ?? '';
  19. $key = strtolower($key);
  20. if ($prefix == 'get') {
  21. return $this->values[$key] ?? '---';
  22. } else {
  23. $this->values[$key] = $params[0];
  24. }
  25. }
  26. }

如何运行…

将步骤1中提到的代码复制到一个新的文件中,chap_10_oop_using_getters_and_setters.php。为了测试该类,添加以下内容。

  1. // set date using a string
  2. $a->setDate('2015-01-01');
  3. var_dump($a->getDate());
  4. // retrieves the DateTime instance
  5. var_dump($a->getDate(TRUE));
  6. // set date using a DateTime instance
  7. $a->setDate(new DateTime('now'));
  8. var_dump($a->getDate());
  9. // retrieves the DateTime instance
  10. var_dump($a->getDate(TRUE));

在输出中(如下图所示),可以看到 $date 属性可以使用字符串或实际的 DateTime 实例来设置。当执行 getDate() 时,你可以根据 $asString 标志的值返回一个字符串或一个 DateTime 实例。

使用 getter 和 setter - 图1

接下来,看一下第2步中定义的代码。将这段代码复制到文件chap_10_oop_using_getters_and_setters_defaults.php 中,并添加以下内容。

  1. // create the instance
  2. $a = new GetSet();
  3. // set a "proper" value
  4. $a->setIntVal(1234);
  5. echo $a->getIntVal();
  6. echo PHP_EOL;
  7. // set a bogus value
  8. $a->setIntVal('some bogus value');
  9. echo $a->getIntVal();
  10. echo PHP_EOL;
  11. // NOTE: boolean TRUE == 1
  12. $a->setIntVal(TRUE);
  13. echo $a->getIntVal();
  14. echo PHP_EOL;
  15. // returns array() even though no value was set
  16. var_dump($a->getArrVal());
  17. echo PHP_EOL;
  18. // sets a "proper" value
  19. $a->setArrVal(['A','B','C']);
  20. var_dump($a->getArrVal());
  21. echo PHP_EOL;
  22. try {
  23. $a->setArrVal('this is not an array');
  24. var_dump($a->getArrVal());
  25. echo PHP_EOL;
  26. } catch (TypeError $e) {
  27. echo $e->getMessage();
  28. }
  29. echo PHP_EOL;

正如你可以从下面的输出中看到的,设置一个合适的整数值和预期的一样,非数字值默认为0。有趣的是,如果你提供一个布尔值 true 作为参数给settingVal(),它就会被插值为1

如果你调用getArrVal()而不设置一个值,默认是一个空数组。设置一个数组值的工作原理和预期一样。然而,如果你提供一个非数组值作为参数,数组的类型提示会导致抛出TypeError,这个错误可以如这里所示。

使用 getter 和 setter - 图2

最后,把步骤3中定义的LotsProps类放到一个单独的文件中,chap_10_oop_using_getters_and_setters_magic_call.php。现在添加代码来设置值。当然,会发生的是调用了神奇的方法__call()。在运行preg_match()后,不存在的属性的剩余部分,在字母设置后,将成为内部数组$values中的一个键。

  1. $a = new LotsProps();
  2. $a->setFirstName('Li\'l Abner');
  3. $a->setLastName('Yokum');
  4. $a->setAddr1('1 Dirt Street');
  5. $a->setCity('Dogpatch');
  6. $a->setState('Kentucky');
  7. $a->setPostalCode('12345');
  8. $a->setCountry('USA');
  9. ?>

然后,你可以定义HTML,使用相应的 get 方法显示这些值。这些方法又会从内部数组中返回键。

  1. <div class="container">
  2. <div class="left blue1">Name</div>
  3. <div class="right yellow1">
  4. <?= $a->getFirstName() . ' ' . $a->getLastName() ?></div>
  5. </div>
  6. <div class="left blue2">Address</div>
  7. <div class="right yellow2">
  8. <?= $a->getAddr1() ?>
  9. <br><?= $a->getAddr2() ?>
  10. <br><?= $a->getCity() ?>
  11. <br><?= $a->getState() ?>
  12. <br><?= $a->getProvince() ?>
  13. <br><?= $a->getPostalCode() ?>
  14. <br><?= $a->getCountry() ?>
  15. </div>
  16. </div>

这是最后的输出。

使用 getter 和 setter - 图3