类和对象

对象是现实生活中各种元素的代表。每个对象都有一组属性,这些属性使其有别于同类的其他对象,并且能够执行一系列操作。类是对对象的外观和功能的定义,就像对象的模式。

让我们以书店为例,想想现实生活中书店里都有哪些对象。我们储存图书,如果有空闲,就可以让人们取走。我们可以认为有两类对象:书籍和顾客。我们可以将这两类对象定义如下:

<?php

class Book {
}

class Customer {
}

类是由关键字 class 和一个有效的类名(与其他 PHP 标签(如变量名)遵循相同的规则)以及代码块定义的。但是,如果我们想拥有一本特定的书,即对象 Book 或类 Book 的实例,我们就必须将其实例化。要实例化一个对象,我们要使用关键字 new,然后跟上类的名称。我们将实例赋值给一个变量,就好像它是一个原始类型一样:

$book = new Book();
$customer = new Customer();

您可以根据需要创建任意多个实例,只要将它们分配给不同的变量即可:

$book1 = new Book();
$book2 = new Book();

类属性

让我们首先考虑一下书籍的属性:它们有标题、作者和 ISBN。它们也可能可用或不可用。 在 Book.php 中写入以下代码:

<?php

class Book {
    public $isbn;
    public $title;
    public $author;
    public $available;
}

前面的代码段定义了一个表示图书属性的类。不要在意 public 这个词,我们将在下一节讨论可见性时解释它的含义。现在,只需将属性视为类中的变量。我们可以在对象中使用这些变量。试着在 Book.php 文件末尾添加以下代码:

$book = new Book();
$book->title = "1984";
$book->author = "George Orwell";
$book->available = true;
var_dump($book);

打印对象会显示其每个属性的值,这种方式与数组显示键值的方式类似。你可以看到,属性在打印时有一个类型,但我们没有明确定义这个类型;相反,变量采用了赋值的类型。这与普通变量的工作方式完全相同。

在创建对象的多个实例并为其属性赋值时,每个对象都有自己的值,因此不会覆盖它们。下一段代码将向你展示这种工作方式:

$book1 = new Book();
$book1->title = "1984";
$book2 = new Book();
$book2->title = "To Kill a Mockingbird";
var_dump($book1, $book2);

类方法

方法是在类中定义的函数。与函数一样,方法也会获取一些参数并执行一些操作,最后返回一个值。方法的优点是可以使用调用方法的对象的属性。因此,在两个不同的对象中调用同一个方法可能会产生两种不同的结果。

尽管将 HTML 与 PHP 混用通常不是个好主意,但为了学习起见,让我们在类 Book 中添加一个方法,返回已经存在的函数 printableTitle 中的书名:

<?php

class Book {
    public $isbn;
    public $title;
    public $author;
    public $available;

    public function getPrintableTitle(): string {
        $result = '<i>' . $this->title . '</i> - ' . $this->author;
        if (!$this->available) {
            $result .= ' <b>Not available</b>';
        }
        return $result;
    }
}

与属性一样,我们在函数开头添加了关键字 public,除此之外,其他部分看起来与普通函数无异。另一个特别之处是使用了 $this:它代表对象本身,并允许访问同一对象的属性和方法。请注意我们是如何引用标题、作者和可用属性的。

您还可以通过其中一个函数更新当前对象的值。让我们将可用属性用作显示可用单位数的整数,而不仅仅是布尔值。这样,我们就可以允许多个客户借阅同一本书的不同副本。让我们添加一个方法,向客户提供一本书的副本,同时更新可用单位数:

public function getCopy(): bool {
    if ($this->available < 1) {
        return false;
    } else {
        $this->available--;
        return true;
    }
}

在前面的方法中,我们首先检查是否至少有一个可用的单元。如果没有,我们就返回 false,让客户知道操作不成功。如果有,我们就减少可用单位的数量,然后返回 true,让客户知道操作成功。让我们看看如何使用该类:

<?php
$book = new Book();
$book->title = "1984";
$book->author = "George Orwell";
$book->isbn = 9785267006323;
$book->available = 12;

if ($book->getCopy()) {
    echo 'Here, your copy.';
} else {
    echo 'I am afraid that book is not available.';
}

最后一段代码会打印什么?没错,Here, your copy.。但可用属性的值是多少呢?是 11,也就是调用 getCopy 的结果。

类构造器

您可能已经注意到,每次实例化 Book 类并设置其所有值看起来都很麻烦。如果我们的类有 30 个属性而不是 4 个呢?希望你永远不会这么做,因为这是非常糟糕的做法。不过,有一种方法可以减轻这种痛苦:constructors

构造函数是在创建类的新实例时调用的函数。它们看起来与普通方法无异,不同的是它们的名称总是 __construct ,而且没有返回语句,因为它们总是必须 return 新实例。我们来看一个例子:

public function __construct(int $isbn, string $title, string $author, int $available) {
    $this->isbn = $isbn;
    $this->title = $title;
    $this->author = $author;
    $this->available = $available;
}

构造函数接收四个参数,然后将其中一个参数的值赋值给实例的每个属性。要实例化 Book 类,我们可以使用下面的方法:

$book = new Book("1984", "George Orwell", 9785267006323, 12);

这个对象与我们手动设置其每个属性值时的对象完全相同。但这个看起来更简洁,对吗?这并不意味着你不能手动为这个对象设置新的值,它只是帮助你构建新的对象。

由于构造函数仍然是一个函数,它可以使用默认参数。试想一下,在创建对象时,单位数通常为 0,随后,图书管理员将在可用时添加单位数。我们可以为构造函数的 $available 参数设置一个默认值,这样,如果我们在创建对象时没有发送单位数,对象就会以默认值实例化:

public function __construct(
    int $isbn,
    string $title,
    string $author,
    int $available = 0
) {
    $this->isbn = $isbn;
    $this->title = $title;
    $this->author = $author;
    $this->available = $available;
}

我们可以通过两种不同的方式使用前面的构造函数:

$book1 = new Book("1984", "George Orwell", 9785267006323, 12);
$book2 = new Book("1984", "George Orwell", 9785267006323);

$book1 会将可用单位数设置为 12,而 $book2 会将其设置为默认值 0。但不要相信我;自己尝试一下吧!

魔术方法

有一类特殊方法的行为与普通方法不同。这些方法被称为魔术方法,它们通常由类或对象的交互触发,而不是由调用触发。我们已经看到了其中的一个方法,即类的构造函数 __construct。这个方法不是直接调用的,而是在使用 new 创建新实例时使用的。你可以很容易地识别魔法方法,因为它们都以 __ 开头。下面是一些最常用的魔术方法:

  • __toString:当我们试图将一个对象转换为字符串时,就会调用该方法。该方法不带参数,预计将返回一个字符串。

  • __call:当试图调用一个不存在的类的方法时,PHP 调用该方法。它通过参数获取字符串形式的方法名和数组形式的调用参数列表。

  • __get:这是属性的 __call 版本。它通过参数获取用户试图访问的属性名称,并且可以返回任何内容。

我们可以使用 __toString 方法来替换当前 Book 类中的 getPrintableTitle 方法。为此,只需更改该方法的名称如下:

public function __toString() {
    $result = '<i>' . $this->title . '</i> - ' . $this->author;
    if (!$this->available) {
        $result .= ' <b>Not available</b>';
    }
    return $result;
}

要尝试前面的代码,您只需添加以下代码片段即可创建对象书,然后调用 __toString 方法将其转换为字符串:

$book = new Book(1234, 'title', 'author');
$string = (string) $book; // title - author Not available

顾名思义,这些都是神奇的方法,因此它们的功能在大多数情况下看起来就像魔法一样。出于显而易见的原因,我们个人鼓励开发人员使用构造函数和 __toString 方法,但在使用其他方法时要小心,因为对于不熟悉代码的人来说,您的代码可能会变得非常难以预测。