9

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.

iconoclast
  • 21,213
  • 15
  • 102
  • 138
w00
  • 26,172
  • 30
  • 101
  • 147

5 Answers5

3

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

shanethehat
  • 15,460
  • 11
  • 57
  • 87
tabdulla
  • 505
  • 1
  • 5
  • 14
  • I don't want to use runkit, it's a pecl and it cannot be installed on alot of hosters. Besides, i'm interested in how monkey patching works. If i just wanted a library i would've used runkit on patchwork. I just want to know how monkey patching would work with patching my own objects. – w00 Apr 08 '12 at 18:47
3

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.

mobius
  • 5,104
  • 2
  • 28
  • 41
  • I understand the strlen example from the webstie. But not how to apply this on my own objects. It doesn't say how to redefine methods like it does in patchwork. I'm also not looking for any ohter library like runkit etc. I just want to know how to do monkey patching myself with just plain PHP. – w00 Apr 08 '12 at 18:48
  • @w00 The example mentioned in the website is not monkey patching as you can understand, since you get the whole point with the namespaces. This is why you don't get any examples overriding functions inside classes. If your interest in monkey patching is strictly for unit testing then why don't you use runkit on your development server? Otherwise why don't you give patchwork a try? I don't know however if it scales that good. – mobius Apr 08 '12 at 19:16
1

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.

iconoclast
  • 21,213
  • 15
  • 102
  • 138
0

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/

cmgriffing
  • 117
  • 2
  • 12
-1

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

Javier Neyra
  • 1,239
  • 11
  • 12