Home » Php » monkeypatching – monkey patching in php

monkeypatching – monkey patching in php

Posted by: admin July 12, 2020 Leave a comment

Questions:

I’m trying to figure out how monkey patching works and how I can make it work on my own objects/methods.

I’ve been looking at this lib, it does exactly what I want to do myself:
https://github.com/antecedent/patchwork

With it you can redefine a method from an object. It uses the ‘monkey patch’ technique for that. But I couldn’t really figure out what exactly is going on by looking at the source.

So suppose I have the following object:

//file: MyClass.php
namespace MyClass;

class MyClass {

    public function say()
    {
        echo 'Hi';
    }
}

I’d like to do something like this:

Monkeypatch\replace('MyClass', 'say', function() {
    echo 'Hello';
});

$obj = new MyClass();
$obj->say();  // Prints: 'Hello'

But i’m not sure how to code the actual patching part. I know namespaces in this context are important. But how does that exactly let me patch a certain method? And do I need to use eval() somewhere (if so, how)?

I couldn’t really find any good examples about this matter, except:
http://till.klampaeckel.de/blog/archives/105-Monkey-patching-in-PHP.html

But I really don’t see how I can apply that to my own objects/methods. I’m hoping for a good explanation or example.

How to&Answers:

In the case of http://till.klampaeckel.de/blog/archives/105-Monkey-patching-in-PHP.html what actually makes the difference is the \ character used in front of the second strlen.

When you are using namespaces you can either use a namespace and directly invoke the methods/classes declared in the namespace:

use TheNamespace;
$var = new TheClass();

Or invoke the class explicitly by using something like:

$var = new \TheNamespace\TheClass();

So by invoking \strlen() instead of strlen() you are explicitly requesting PHP to use the default strlen and not the strlen defined for this namespace.

As for monkey patching you could use runkit (http://ca.php.net/runkit). Also with regards to patchwork there are a fair amount of examples in their website (http://antecedent.github.com/patchwork/docs/examples.html). You could check the magic method example that is replacing a function in a class.

Answer:

You can do runtime class modification using runkit. More specifically, you can use runkit_method_redefine.

Answer:

As of PHP 5.6, there’s still no support for monkey patching; however PHP 5.3 introduced anonymous functions. This answer is not exactly what you’re looking for and could probably be improved upon, but the general idea is to use arrays, anonymous functions, and references to create a self-contained, self-referential array (an “object”, if you will):

test.php

$inner = require('test2.php');
$inner['say'](); // Hi!

$inner['data']['say'] = 'Bye!';
$inner['say'](); // still says Hi!

$inner['set_say']('Bye!');
$inner['say'](); // Bye!

$inner = require('test2.php');
$inner['say'](); // Hi!

test2.php

$class = array(
    'data' => array(
        'say' => 'Hi!'
    ),

    'say' => function() use (&$class){
        echo $class['data']['say'].'<br />';
    },

    'set_say' => function($msg) use (&$class){
        $class['data']['say'] =& $msg; 
    }
);

return $class;

Also, here’s a disclaimer saying that the above code (along with monkey patching in PHP) is almost always a terrible idea, but there are times where this is absolutely necessary.

Answer:

I have monkey-patched a class using eval() and namespaces.
This may not be the answer for you, though, since this does not work if the class you are monkey-patching is already in a namespace.
I haven’t figured out how to get around that, other than trimming the namespace declaration from the eval string. However, doing so would likely break any namespace-dependent code within the class methods.

In my case, I am monkey-patching the core PDO class for unit-testing a class that depends on database interaction. But, maybe seeing my technique will help you figure out how to make it work for your situation.

I have the code snippets up in a blog post here:
http://chrisgriffing.com/coding/php/2012/04/12/how-to-mock-pdo-and-other-objects/

Answer:

you probably already figured this out, but, just for reference, they are using stream wrappers,

http://php.net/manual/es/function.stream-wrapper-register.php

basically, they register an stream wrapper on file and phar, so when the code is loaded, thay can manipulate it, it doesnt work on code loaded from an opcache