如何覆盖库中的私有方法 class

How to override private method from a library class

给定以下 classes,其中 class A 来自 external library,因此我无法更改它:

class A {
    public function test () {
        $this->privateMethod();
    }
    private function privateMethod () {
        echo('A');
    }
}

class B extends A {
    private function privateMethod () {
        echo('B');
    }
}

$b = new B();
$b->test();

这导致 AA::privateMethod 打印出来,而不是 BB::privateMethod 打印出来,因为后者对 A::test 不可见,如前所述here.

我还能如何以最干净的方式修改此私有库方法的行为(例如,无需通过复制整个 class 和更改它来复制代码)?

那是因为private只在class本身的范围内。如果您使用了 protected,您会覆盖该函数,因为 protected 方法意味着它可用于子 classes.

您可以使用 ReflectionMethod::setAccessible():

更改 class 方法的可访问性
$myEmogrifier = new \Pelago\Emogrifier;
$reflectedMethod = new ReflectionMethod($myEmogrifier, 'getCssFromAllStyleNodes');
$reflectedMethod->setAccessible(true);
$argument = new \DOMXpath(new \DOMDocument);
$returnValue = $reflectedMethod->invoke($myEmogrifier, $argument);

考虑到此代码将是 'fragile',因为库的作者不会考虑库的用户依赖于私有函数的结果。自己简单地复制函数的代码可能比弄乱库本身更好。

您可以间接操纵行为。这是您感兴趣的片段。

$allCss = $this->css;

if ($this->isStyleBlocksParsingEnabled) {
    $allCss .= $this->getCssFromAllStyleNodes($xpath);
}

查看 class 设置器,您可以调用 disableStyleBlocksParsing 以防止调用该函数。

$allCss变量直接取自$this->css,仅通过setCss方法修改

所以你有两个选择:

  • 扩展 class,使 isStyleBlocksParsingEnabled 为假且不可变,然后重写 setCss 方法以执行您希望 getCssFromAllStyleNodes 执行的操作。
  • 调用 disableStyleBlocksParsing 并使用预处理文本调用 setCss

这是第一个选项的示例:

class MyEmogrifier extends Emogrifier
{
    public function __construct($html = '', $css = '')
    {
        parent::__construct($html, $css);

        $this->disableStyleBlocksParsing();
    }

    public function setCss($css)
    {
        // Preprocess CSS here.

        parent::setCss($css);
    }
}

所以没有霰弹枪手术,也不需要反思。

老实说。我什至不太愿意使用像这个这样具体的库。我将 protected 用于几乎所有的私有方法。

您可以通过 ReflectionClass::setAccessible 更改 属性 的硬编码 visibility 这是 ReflectionClass.

的一部分

Sets a property to be accessible. For example, it may allow protected and private properties to be accessed.

这很危险但在某些情况下你可以使用它。