# PHP 中的面向对象编程 II

> 原文： [https://zetcode.com/lang/php/oopii/](https://zetcode.com/lang/php/oopii/)

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

## PHP `static`关键字

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

`staticmethod.php`

```php
<?php

class Sys {

    public static function println($string) {

        echo "$string\n";
    }
}

Sys::println("PHP");
Sys::println("PERL");
Sys::println("Python");
Sys::println("Pike");

```

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

```php
Sys::println("PHP");

```

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

```php
$ php static1.php 
PHP
PERL
Python
Pike

```

这是脚本的输出。

`staticvariable.php`

```php
<?php

class Math {

    public static $PI = 3.14159265359;
}

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

```

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

```php
echo Math::$PI . "\n";

```

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

## PHP `final`关键字

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

`finalmethod.php`

```php
<?php

class Base {

    final function say() {
        echo "Base class";
    }
}

class Derived extends Base {

    function say() {
        echo "Derived class";
    }
}

```

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

`finalclass.php`

```php
<?php

final class Math {

    static function getPI() {
        return 3.141592;
    }
}

class DerivedMath extends Math {

    function say() {
        echo "DerivedMath class";
    }
}

```

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

## PHP 深拷贝与浅拷贝

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

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

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

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

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

`shallowcopy.php`

```php
<?php

class Object {

    public $id;
    public $size;
    public $color;

    function __construct($id, $size, $color) {

        $this->id = $id;
        $this->size = $size;
        $this->color = $color;
    }
}

class Color {

    public $red;
    public $green;
    public $blue;

    function __construct($red, $green, $blue) {

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

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

$object1 = new Object(23, "small", $color);
$object2 = clone $object1;

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

print_r($object1);
print_r($object2);

```

在上面的 PHP 脚本中，我们定义了两个自定义对象：`Object`和`Color`。 `Object`对象将具有对`Color`对象的引用。

```php
$color = new Color(23, 42, 223);

```

我们创建`Color`对象的实例。

```php
$object1 = new Object(23, "small", $color);

```

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

```php
$object2 = clone $object1;

```

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

```php
$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

```

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

```php
print_r($object1);
print_r($object2);

```

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

```php
$ php shallowcopy.php 
Object Object
(
    [id] => 23
    [size] => small
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)
Object Object
(
    [id] => 24
    [size] => big
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)

```

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

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

`deepcopy.php`

```php
<?php

class Object {

    public $id;
    public $size;
    public $color;

    function __construct($id, $size, $color) {

        $this->id = $id;
        $this->size = $size;
        $this->color = $color;
    }

    function __clone() {

        $red = $this->color->red;
        $green = $this->color->green;
        $blue = $this->color->blue;
        $this->color = new Color($red, $green, $blue);
    }
}

class Color {

    public $red;
    public $green;
    public $blue;

    function __construct($red, $green, $blue) {

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

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

$object1 = new Object(23, "small", $color);
$object2 = clone $object1;

$object2->id++;
$object2->color->red = 255;
$object2->size = "big";

print_r($object1);
print_r($object2);

```

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

```php
function __clone() {
    $red = $this->color->red;
    $green = $this->color->green;
    $blue = $this->color->blue;
    $this->color = new Color($red, $green, $blue);
}

```

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

```php
$ php deepcopy.php 
Object Object
(
    [id] => 23
    [size] => small
    [color] => Color Object
        (
            [red] => 23
            [green] => 42
            [blue] => 223
        )

)
Object Object
(
    [id] => 24
    [size] => big
    [color] => Color Object
        (
            [red] => 255
            [green] => 42
            [blue] => 223
        )

)

```

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

## PHP 异常

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

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

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

`zerodiv.php`

```php
<?php

set_error_handler("error_handler");

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

try {

    $a = 0;
    $b = 32;
    $c = $b / $a;
} catch(ErrorException $e) {

   echo "Error occurred\n";
   echo $e->getMessage(), "\n";
}

```

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

```php
set_error_handler("error_handler");

```

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

```php
function error_handler($errno, $errstring, $errfile, $line, $trace) {
    throw new ErrorException($errstring, $errno, 0,  $errfile, $line);
}

```

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

```php
try {

    $a = 0;
    $b = 32;
    $c = $b / $a;
}

```

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

```php
} catch(Exception $e) {
   echo $e->getMessage();
}

```

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

```php
$ php zerodiv.php 
Error occurred
Division by zero

```

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

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

`myexception.php`

```php
<?php

define("LIMIT", 333);

class BigValueException extends Exception {

    public function __construct($message) {
        parent::__construct($message);
    }
}

$a = 34325;

try {
    if ($a > LIMIT) {
        throw new BigValueException("Exceeded the maximum value allowed\n");   
    }
} catch (BigValueException $e) {
    echo $e->getMessage();   
}

```

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

```php
define("LIMIT", 333);

```

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

```php
class BigValueException extends Exception {

```

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

```php
public function __construct($message) {
    parent::__construct($message);
}

```

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

```php
if ($a > LIMIT) {
    throw new BigValueException("Exceeded the maximum value allowed\n");   
}

```

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

```php
} catch (BigValueException $e) {
    echo $e->getMessage();   
}

```

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

## PHP 构造器重载

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

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

`constructors.php`

```php
<?php

class Book {

    private $author = "not specified";
    private $title = "not specified";
    private $year = "not specified";

    public function __construct() {

        $args = func_get_args();

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

            if(empty($args)) {
                break;
            }

            $this->$item = array_shift($args);
        }
    }

    public function __toString() {
        return "Author: $this->author\nTitle: $this->title\nPublished: $this->year\n\n";
    }
}

$book1 = new Book("Stephen Prata", "C Primer Plus");
echo $book1;

$book2 = new Book("Joshua Bloch", "Effective Java", 2008);  
echo $book2;

```

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

```php
private $author = "not specified";
private $title = "not specified";
private $year = "not specified";

```

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

```php
$args = func_get_args();

```

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

```php
foreach(["title", "author", "year"] as $item) {

```

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

```php
$this->$item = array_shift($args);

```

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

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

```

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

```php
$ php constructors.php
Author: C Primer Plus
Title: Stephen Prata
Published: not specified

Author: Effective Java
Title: Joshua Bloch
Published: 2008

```

这是脚本的结果。

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

`factory.php`

```php
<?php

class Cat {

    private $name = "unspecified";
    private $age = "unspecified";

    public static function withName($name) {

        $cat = new Cat();
        $cat->name = $name;

        return $cat;
    }

    public static function withAge($age) {

        $cat = new Cat();
        $cat->age = $age;

        return $cat;
    }

    public static function fullCat($name, $age) {

        $cat = new Cat();
        $cat->name = $name;
        $cat->age = $age;

        return $cat;
    }

    public function __toString() {
        return "Name: $this->name, Age: $this->age\n";
    }
}

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

$missy = Cat::withAge(6);
echo $missy;

$lucky = Cat::fullCat("Lucky", 4);
echo $lucky;

```

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

```php
private $name = "unspecified";
private $age = "unspecified";

```

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

```php
public static function withName($name) {
    $cat = new Cat();
    $cat->name = $name;

    return $cat;
}

```

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

```php
$cici = Cat::withName("Cici");
echo $cici;

```

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

```php
$ php factory.php 
Name: Cici, Age: unspecified
Name: unspecified, Age: 6
Name: Lucky, Age: 4

```

脚本的输出。

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