处理私有方法

开发人员创建超类的原因之一是为了对子类的方法签名进行一定程度的控制。在解析阶段,PHP 通常会确认方法签名是否匹配。这样,其他开发人员就可以正确使用您的代码。

同样,如果一个方法被标记为私有方法,让 PHP 执行同样严格的方法签名检查是没有意义的。私有方法的目的是让扩展类看不到它。如果你在扩展类中定义了一个同名的方法,你就可以随意定义它。

为了说明这个问题,让我们定义一个名为 Cipher 的类,并定义一个名为 encrypt() 的私有方法。OpenCipher 子类重新定义了这个方法,导致在 PHP 7 下运行时出现致命错误:

  1. 首先,让我们定义一个 Cipher 类,它的构造函数会生成 $key$salt 的随机值。它还定义了一个名为 encode() 的公共方法,用于调用私有的 encrypt() 方法:

    // /repo/src/Php7/Encrypt/Cipher.php
    namespace Php7\Encrypt;
    class Cipher {
        public $key = '';
        public $salt = 0;
        public function __construct() {
            $this->salt = rand(1,255);
            $this->key = bin2hex(random_bytes(8));
        }
        public function encode(string $plain) {
            return $this->encrypt($plain);
        }
  2. 接下来,让我们定义一个名为 encrypt() 的私有方法,使用 str_rot13() 生成加密文本。请注意,该方法被标记为最终方法。虽然这没有任何意义,但为了本示例的目的,请假设这是有意为之:

        final private function encrypt(string $plain) {
            return base64_encode(str_rot13($plain));
        }
    }
  3. 最后,让我们定义一个简短的调用程序,创建类实例并调用已定义的方法:

    // /repo/ch09/php7_oop_diffs_private_method.php
    include __DIR__ . '/../src/Server/Autoload/Loader.php';
    $loader = new \Server\Autoload\Loader();
    use Php7\Encrypt\{Cipher,OpenCipher};
    $text = 'Super secret message';
    $cipher1 = new Cipher();
    echo $cipher1->encode($text) . "\n";
    $cipher2 = new OpenCipher();
    var_dump($cipher2->encode($text));

如果我们在 PHP 7 中运行调用程序,我们将得到以下输出:

oot@php8_tips_php7 [ /repo/ch09 ]#
php php7_oop_diffs_private_method.php
RmhjcmUgZnJwZXJnIHpyZmZudHI=
PHP Fatal error: Cannot override final method Php7\Encrypt\
Cipher::encrypt() in /repo/src/Php7/Encrypt/OpenCipher.php on
line 21

在这里,你可以看到 Cipher 的输出结果是正确的。然而,我们却抛出了一个致命的错误,同时还有一条信息指出我们无法覆盖一个最终方法。理论上,子类应该完全看不到私有方法。然而,从输出结果中可以清楚地看到,情况并非如此。Cipher 超类中私有方法的方法签名会影响我们在子类中重新定义相同方法的能力。

不过,在 PHP 8 中,这一矛盾已经得到了解决。下面是相同代码在 PHP 8 中运行的输出结果:

root@php8_tips_php8 [ /repo/ch09 ]#
php php7_oop_diffs_private_method.php
PHP Warning: Private methods cannot be final as they are never
overridden by other classes in /repo/src/Php7/Encrypt/Cipher.
php on line 17
RmhjcmUgZnJwZXJnIHpyZmZudHI=
array(2) {
    ["tag"]=> string(24) "woD6Vi73/IXLaKHFGUC3aA=="
    ["cipher"]=> string(28) "+vd+jWKqo8WFPd7SakSvszkoIX0="

从前面的输出中可以看到,应用程序已成功运行,父类和子类的输出都已显示。我们还可以看到一个警告,通知我们不能将私有方法标记为最终方法。

有关私有方法签名背景讨论的更多信息,请参阅此文档参考: https://wiki.php.net/rfc/inheritance_private_methods

在了解了 PHP 8 如何防止子类看到超类中的私有方法之后,让我们来看看 PHP 8 中匿名类的不同之处。