0

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)?

Community
  • 1
  • 1
hielsnoppe
  • 2,819
  • 3
  • 31
  • 56
  • There are several solutions to this, it might be useful to know which library you are using, assuming it is open source. – Flosculus May 21 '15 at 14:55
  • Yes, [it is](https://github.com/jjriv/emogrifier). I need to change `getCssFromAllStyleNodes`. – hielsnoppe May 21 '15 at 14:59

4 Answers4

3

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.

Daan
  • 12,099
  • 6
  • 34
  • 51
  • 1
    I know why it doesn't work, but I am looking for a different way to achieve what I stated in the question. – hielsnoppe May 21 '15 at 14:57
  • 2
    I don't think that answers the question. The OP says it's a library so he cannot change the access modifiers. – marekful May 21 '15 at 14:59
2

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.

Tomas Creemers
  • 2,645
  • 14
  • 15
1

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.

Flosculus
  • 6,880
  • 3
  • 18
  • 42
0

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.

daremachine
  • 2,678
  • 2
  • 23
  • 34