hielsnoppe
hielsnoppe

Reputation: 2869

How to override private method from a library class

Given are the following classes with class A coming from an external library so that I can not change it:

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();

This results in A being printed out by A::privateMethod instead of B from B::privateMethod, because the latter is not visible to A::test as explained here.

How else can I modify the behavior of this private library method in the cleanest possible way (e.g. without code duplication from copying the whole class and changing it)?

Upvotes: 1

Views: 7940

Answers (4)

Daan
Daan

Reputation: 12236

That is because private is only in the scope of the class itself. I you had used protected you would've overridden the function, because a protected method means it's available for child classes.

Upvotes: 3

Flosculus
Flosculus

Reputation: 6946

You can manipulate the behaviour indirectly. This is the snippet you are interested in.

$allCss = $this->css;

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

Looking at the class setters, you can call disableStyleBlocksParsing to prevent the function being called.

The $allCss variable is taken straight from $this->css, which is only modified by the setCss method.

So you have two choices:

  • Extend the class, make isStyleBlocksParsingEnabled false and immutable, then override the setCss method to do what you wanted getCssFromAllStyleNodes to do.
  • Call disableStyleBlocksParsing and and call setCss with preprocessed text.

Here is an example of the first option:

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);
    }
}

So there is no shotgun surgery, or reflection needed.

To be honest though. I would feel much less inclined to even use a library as concrete as this one. I use protected for nearly all of my would be private methods.

Upvotes: 1

daremachine
daremachine

Reputation: 2788

You can change hardcode visibility of property via ReflectionClass::setAccessible this is a part of ReflectionClass.

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

It is dangerous but in some cases you can use it.

Upvotes: 0

Tomas Creemers
Tomas Creemers

Reputation: 2715

You can change accessibility of a class method using ReflectionMethod::setAccessible():

$myEmogrifier = new \Pelago\Emogrifier;
$reflectedMethod = new ReflectionMethod($myEmogrifier, 'getCssFromAllStyleNodes');
$reflectedMethod->setAccessible(true);
$argument = new \DOMXpath(new \DOMDocument);
$returnValue = $reflectedMethod->invoke($myEmogrifier, $argument);

Take into account that this code will be 'fragile', since the author of the library will not take into account that a user of the library is relying on the result of a private function. It may be better to simply duplicate the function's code yourself than messing with the library itself.

Upvotes: 3

Related Questions