原文: https://zetcode.com/lang/php/oopii/

在 PHP 教程的这一章中,我们将继续描述 PHP 中的 OOP。

PHP static关键字

我们可以将类属性和方法声明为staticstatic属性和方法不属于该类的实例。 他们属于阶级本身。 可通过范围解析运算符::访问它们。

staticmethod.php

  1. <?php
  2. class Sys {
  3. public static function println($string) {
  4. echo "$string\n";
  5. }
  6. }
  7. Sys::println("PHP");
  8. Sys::println("PERL");
  9. Sys::println("Python");
  10. Sys::println("Pike");

在上面的 PHP 脚本中,我们有一个静态的println()方法。 它打印一个字符串并开始新的一行。 此示例受 Java 语言启发。

  1. Sys::println("PHP");

我们不需要对象来调用println()方法。 我们通过指定类名称,后跟双冒号运算符和方法名称来调用static方法。

  1. $ php static1.php
  2. PHP
  3. PERL
  4. Python
  5. Pike

这是脚本的输出。

staticvariable.php

  1. <?php
  2. class Math {
  3. public static $PI = 3.14159265359;
  4. }
  5. echo Math::$PI . "\n";

现在,我们有一个带有static变量的示例。

  1. echo Math::$PI . "\n";

我们通过指定类名,范围解析运算符和变量名来访问变量。

PHP final关键字

最终方法不能被覆盖,最终类不能被扩展。 final关键字取决于应用的设计。 某些类不应扩展,某些方法不应重写。 此行为由final关键字强制执行。

finalmethod.php

  1. <?php
  2. class Base {
  3. final function say() {
  4. echo "Base class";
  5. }
  6. }
  7. class Derived extends Base {
  8. function say() {
  9. echo "Derived class";
  10. }
  11. }

该 PHP 脚本无法编译。 我们收到一个错误“无法覆盖最终方法Base::say()”。

finalclass.php

  1. <?php
  2. final class Math {
  3. static function getPI() {
  4. return 3.141592;
  5. }
  6. }
  7. class DerivedMath extends Math {
  8. function say() {
  9. echo "DerivedMath class";
  10. }
  11. }

在先前的 PHP 脚本中,我们有一个原型基础Math类。 该类的唯一目的是为程序员提供一些有用的方法和常量。 (出于简单起见,在我们的例子中,我们只有一种方法。)它不是为了扩展而创建的。 为了防止不知情的其他程序员从此类中派生,创建者创建了final类。 如果您尝试运行此 PHP 脚本,则会出现以下错误:“致命错误:类DerivedMath可能不会从最终类(Math)继承”。

PHP 深拷贝与浅拷贝

数据复制是编程中的重要任务。 对象是 OOP 中的复合数据类型。 对象中的成员字段可以按值或按引用存储。 可以以两种方式执行复制。

浅表副本将所有值和引用复制到新实例中。 引用所指向的数据不会被复制; 仅指针被复制。 新的引用指向原始对象。 对引用成员的任何更改都会影响两个对象。

深拷贝将所有值复制到新实例中。 如果成员存储为引用,则深层副本将对正在引用的数据执行深层副本。 创建引用对象的新副本,并存储指向新创建的对象的指针。 对这些引用对象的任何更改都不会影响该对象的其他副本。 深拷贝是完全复制的对象。

在 PHP 中,我们有一个copy关键字,默认情况下会执行浅表复制。 它调用对象的__clone()方法。 我们可以实现创建自定义深层副本的方法。 在 PHP 中,所有对象都是通过引用分配的。

接下来的两个示例对对象执行浅复制和深复制。

shallowcopy.php

  1. <?php
  2. class Object {
  3. public $id;
  4. public $size;
  5. public $color;
  6. function __construct($id, $size, $color) {
  7. $this->id = $id;
  8. $this->size = $size;
  9. $this->color = $color;
  10. }
  11. }
  12. class Color {
  13. public $red;
  14. public $green;
  15. public $blue;
  16. function __construct($red, $green, $blue) {
  17. $this->red = $red;
  18. $this->green = $green;
  19. $this->blue = $blue;
  20. }
  21. }
  22. $color = new Color(23, 42, 223);
  23. $object1 = new Object(23, "small", $color);
  24. $object2 = clone $object1;
  25. $object2->id++;
  26. $object2->color->red = 255;
  27. $object2->size = "big";
  28. print_r($object1);
  29. print_r($object2);

在上面的 PHP 脚本中,我们定义了两个自定义对象:ObjectColorObject对象将具有对Color对象的引用。

  1. $color = new Color(23, 42, 223);

我们创建Color对象的实例。

  1. $object1 = new Object(23, "small", $color);

创建对象对象的实例。 它将Color对象的实例传递给其构造器。

  1. $object2 = clone $object1;

我们执行Object对象的浅表副本。

  1. $object2->id++;
  2. $object2->color->red = 255;
  3. $object2->size = "big";

在这里,我们修改克隆对象的成员字段。 我们增加 id,更改颜色对象的红色部分,并将大小更改为big

  1. print_r($object1);
  2. print_r($object2);

我们使用print_r()函数比较结果。

  1. $ php shallowcopy.php
  2. Object Object
  3. (
  4. [id] => 23
  5. [size] => small
  6. [color] => Color Object
  7. (
  8. [red] => 255
  9. [green] => 42
  10. [blue] => 223
  11. )
  12. )
  13. Object Object
  14. (
  15. [id] => 24
  16. [size] => big
  17. [color] => Color Object
  18. (
  19. [red] => 255
  20. [green] => 42
  21. [blue] => 223
  22. )
  23. )

我们可以看到 ID 不同:23 与 24。大小不同:smallbig。 但是,这两个实例的颜色对象的红色部分相同:255。更改克隆对象的成员值不会影响原始对象。 更改引用对象的成员也影响了原始对象。 换句话说,两个对象都引用内存中的同一颜色对象。

要更改此行为,我们接下来将做一个深层复制。

deepcopy.php

  1. <?php
  2. class Object {
  3. public $id;
  4. public $size;
  5. public $color;
  6. function __construct($id, $size, $color) {
  7. $this->id = $id;
  8. $this->size = $size;
  9. $this->color = $color;
  10. }
  11. function __clone() {
  12. $red = $this->color->red;
  13. $green = $this->color->green;
  14. $blue = $this->color->blue;
  15. $this->color = new Color($red, $green, $blue);
  16. }
  17. }
  18. class Color {
  19. public $red;
  20. public $green;
  21. public $blue;
  22. function __construct($red, $green, $blue) {
  23. $this->red = $red;
  24. $this->green = $green;
  25. $this->blue = $blue;
  26. }
  27. }
  28. $color = new Color(23, 42, 223);
  29. $object1 = new Object(23, "small", $color);
  30. $object2 = clone $object1;
  31. $object2->id++;
  32. $object2->color->red = 255;
  33. $object2->size = "big";
  34. print_r($object1);
  35. print_r($object2);

在此 PHP 脚本中,我们实现了__clone()方法。

  1. function __clone() {
  2. $red = $this->color->red;
  3. $green = $this->color->green;
  4. $blue = $this->color->blue;
  5. $this->color = new Color($red, $green, $blue);
  6. }

__clone()方法内部,我们复制红色,绿色和蓝色成员字段并创建一个新的Color对象。 现在,$color字段指向另一个Color对象。

  1. $ php deepcopy.php
  2. Object Object
  3. (
  4. [id] => 23
  5. [size] => small
  6. [color] => Color Object
  7. (
  8. [red] => 23
  9. [green] => 42
  10. [blue] => 223
  11. )
  12. )
  13. Object Object
  14. (
  15. [id] => 24
  16. [size] => big
  17. [color] => Color Object
  18. (
  19. [red] => 255
  20. [green] => 42
  21. [blue] => 223
  22. )
  23. )

现在,引用的Color对象的红色部分不相同。 原始对象保留了其先前的 23 值。

PHP 异常

异常是为处理异常的发生而设计的,这些特殊情况会改变程序执行的正常流程。 引发或引发异常并引发异常。

在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 互联网连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃。 为避免发生这种情况,我们必须应对可能发生的所有可能的错误。 为此,我们可以使用异常处理。

从 PHP 5 开始,可以使用异常。大多数 PHP 错误仍然使用旧的错误报告,而不是异常。 使用set_error_handler(),我们可以解决此问题。

zerodiv.php

  1. <?php
  2. set_error_handler("error_handler");
  3. function error_handler($errno, $errstring, $errfile, $line, $trace) {
  4. throw new ErrorException($errstring, $errno, 0, $errfile, $line);
  5. }
  6. try {
  7. $a = 0;
  8. $b = 32;
  9. $c = $b / $a;
  10. } catch(ErrorException $e) {
  11. echo "Error occurred\n";
  12. echo $e->getMessage(), "\n";
  13. }

在上面的 PHP 脚本中,我们有意将数字除以零。 这会导致错误。 该错误也不是异常,并且不会被catch关键字捕获。

  1. set_error_handler("error_handler");

set_error_handler()函数设置用户定义的错误处理器函数。

  1. function error_handler($errno, $errstring, $errfile, $line, $trace) {
  2. throw new ErrorException($errstring, $errno, 0, $errfile, $line);
  3. }

set_error_handler()函数内部,我们抛出了ErrorException。 稍后,此异常由catch关键字捕获。

  1. try {
  2. $a = 0;
  3. $b = 32;
  4. $c = $b / $a;
  5. }

我们正在检查错误的代码放在try关键字之后的块中。

  1. } catch(Exception $e) {
  2. echo $e->getMessage();
  3. }

catch关键字用于捕获异常。 要了解更多信息,我们在异常对象上调用getMessage()方法。

  1. $ php zerodiv.php
  2. Error occurred
  3. Division by zero

这是我们的 PHP 脚本的输出。

Exception是所有异常的基类。 我们可以创建派生自该基类的异常。

myexception.php

  1. <?php
  2. define("LIMIT", 333);
  3. class BigValueException extends Exception {
  4. public function __construct($message) {
  5. parent::__construct($message);
  6. }
  7. }
  8. $a = 34325;
  9. try {
  10. if ($a > LIMIT) {
  11. throw new BigValueException("Exceeded the maximum value allowed\n");
  12. }
  13. } catch (BigValueException $e) {
  14. echo $e->getMessage();
  15. }

假设我们处于无法处理大量数字的情况。

  1. define("LIMIT", 333);

大于此常量的数字在我们的 PHP 脚本中被视为big

  1. class BigValueException extends Exception {

我们有一个BigValueException类。 此类通过extends关键字从Exception类派生。

  1. public function __construct($message) {
  2. parent::__construct($message);
  3. }

在构造器内部,我们称为父级的构造器。

  1. if ($a > LIMIT) {
  2. throw new BigValueException("Exceeded the maximum value allowed\n");
  3. }

如果该值大于限制,则抛出自定义异常。 我们给异常消息"Exceeded the maximum value allowed\n"

  1. } catch (BigValueException $e) {
  2. echo $e->getMessage();
  3. }

我们捕获到异常并将其消息打印到控制台。

PHP 构造器重载

PHP 不支持直接的构造器重载。 换句话说,每个类只能定义一个构造器。 许多已经知道 Java 或 C# 语言的程序员都在寻找 PHP 中的类似功能。 我们有两种方法可以处理此问题。

第一种解决方案基于func_get_args()函数。 第二种解决方案使用工厂模式。

constructors.php

  1. <?php
  2. class Book {
  3. private $author = "not specified";
  4. private $title = "not specified";
  5. private $year = "not specified";
  6. public function __construct() {
  7. $args = func_get_args();
  8. foreach(["title", "author", "year"] as $item) {
  9. if(empty($args)) {
  10. break;
  11. }
  12. $this->$item = array_shift($args);
  13. }
  14. }
  15. public function __toString() {
  16. return "Author: $this->author\nTitle: $this->title\nPublished: $this->year\n\n";
  17. }
  18. }
  19. $book1 = new Book("Stephen Prata", "C Primer Plus");
  20. echo $book1;
  21. $book2 = new Book("Joshua Bloch", "Effective Java", 2008);
  22. echo $book2;

在上面的脚本中,我们有一个Book类。 我们使用 2 和 3 参数实例化该类。

  1. private $author = "not specified";
  2. private $title = "not specified";
  3. private $year = "not specified";

我们定义了三个成员字段。 它们的初始值为"not specified"

  1. $args = func_get_args();

func_get_args()函数返回一个包含函数的参数列表的数组。 这样的想法是:构造器中的代码是动态的; 它取决于传递给它的参数。

  1. foreach(["title", "author", "year"] as $item) {

我们使用foreach关键字浏览所有成员字段。

  1. $this->$item = array_shift($args);

构造器中最基本的任务之一是初始化类的成员字段。 这是通过上面的代码行完成的。 array_shift()函数从数组中删除第一项并返回它。

  1. $book1 = new Book("Stephen Prata", "C Primer Plus");
  2. ...
  3. $book2 = new Book("Joshua Bloch", "Effective Java", 2008);

我们有两个不同的构造器。 第一个带有 2 个参数,第二个带有 3 个参数。

  1. $ php constructors.php
  2. Author: C Primer Plus
  3. Title: Stephen Prata
  4. Published: not specified
  5. Author: Effective Java
  6. Title: Joshua Bloch
  7. Published: 2008

这是脚本的结果。

下一个代码示例使用工厂模式模拟构造器重载。 它是 OOP 中的创建模式之一。 该模式创建对象时未指定要创建的对象的确切类。 更一般地,术语工厂方法通常用于指代主要目的是创建对象的任何方法。

factory.php

  1. <?php
  2. class Cat {
  3. private $name = "unspecified";
  4. private $age = "unspecified";
  5. public static function withName($name) {
  6. $cat = new Cat();
  7. $cat->name = $name;
  8. return $cat;
  9. }
  10. public static function withAge($age) {
  11. $cat = new Cat();
  12. $cat->age = $age;
  13. return $cat;
  14. }
  15. public static function fullCat($name, $age) {
  16. $cat = new Cat();
  17. $cat->name = $name;
  18. $cat->age = $age;
  19. return $cat;
  20. }
  21. public function __toString() {
  22. return "Name: $this->name, Age: $this->age\n";
  23. }
  24. }
  25. $cici = Cat::withName("Cici");
  26. echo $cici;
  27. $missy = Cat::withAge(6);
  28. echo $missy;
  29. $lucky = Cat::fullCat("Lucky", 4);
  30. echo $lucky;

上面的 PHP 脚本中有一个Cat工厂类。 它具有三种不同的静态函数。 它们每个都返回一个特定的Cat对象。

  1. private $name = "unspecified";
  2. private $age = "unspecified";

我们有两个成员字段。 它们的初始值为"not specified"

  1. public static function withName($name) {
  2. $cat = new Cat();
  3. $cat->name = $name;
  4. return $cat;
  5. }

这是静态的withName()函数。 此函数创建Cat类的实例。 它设置name成员字段并返回对象。

  1. $cici = Cat::withName("Cici");
  2. echo $cici;

我们使用其中一种工厂方法创建猫的实例。 我们回荡对象。 即调用该类的__toString()方法。

  1. $ php factory.php
  2. Name: Cici, Age: unspecified
  3. Name: unspecified, Age: 6
  4. Name: Lucky, Age: 4

脚本的输出。

在 PHP 教程的这一部分中,我们继续讨论 PHP 中的面向对象编程。