Thursday, September 30, 2010

Monkey patching in PHP

Don't do this! It's only curiosity that drove me to try.
A monkey patch is a way to extend or modify the runtime code of dynamic languages (e.g. Smalltalk, JavaScript, Objective-C, Ruby, Perl, Python, Groovy, etc.) without altering the original source code. -- Wikipedia
Till tried to do some monkey patching a while ago, using PHP namespaces. Basically, if you use a PHP function inside a namespace, the original function will be called if an override is not defined. In the example, calling strlen() inside of a namespace would refer to the NamespaceName\strlen() function, and if it not exists, it would fall back to PHP's native one.
The problem was that you actually have to namespace the original code. It would not be monkey patching anymore: if I could modify that code, I would simply change the function calls.
For example, we may want to monkey patch the following code:
<?php
$str = 'mystring';

echo "This should eight, but it's not: " . strlen($str) . "\n"; // 6

So what do we do? Basically, this evil black magic:
<?php
namespace monkeypatching;
function strlen()
{
    return 6;
}

$code = file_get_contents('monkeypatched.php');
$code  = 'namespace monkeypatching; ?> ' . $code;
echo $code;
eval($code);
which prints out:
This should eight, but it's not: 6
Of course this infringes every single SOLID and software engineering principle. Again, don't code like this, we are not in Ruby.
In general, methods and functions definitions are part of the mandatory global state of an application, and so once the loading phase has finished they should be immutable. This is done at the loading time, so it may be acceptable. But also dangerous: it's the poor man's version of polymorphism.

7 comments:

  1. there's a discussion on the grusp mailing list defining when monkey patching is a good practice

    ReplyDelete
  2. Also, for purposes of testing legacy code, it seems like a very nice seam.

    ReplyDelete
  3. You really should give a better explanation of why one shouldn't do this. There are a handful of places where monkey patching is appropriate and if you are going to give advice as to whether people should or shouldn't engage in a practice then you should give a good (read well supported) reason for it.

    ReplyDelete
  4. There are many reasons for avoiding monkey patching:
    http://en.wikipedia.org/wiki/Monkey_patch#Pitfalls
    However, the greatest issue for me is the global state modifications implied by monkey patching and the Action at a distance anti-pattern that results. Once you start modifying the library functions, code in other modules and classes will began to work differently. This article describes a way to use the seam at loading/compile time, but it seems pretty dangerous nonetheless: think about one programmer unaware of the patching going on who sees this output: he would never question the correctness of a PHP function, leading to an awful debugging session...

    ReplyDelete
  5. Why the 'echo' before the 'eval'?

    ReplyDelete
  6. To show the code actually executed (with the addition of the namespace). Not necessary, but explanatory. :)

    ReplyDelete
  7. There is runkit extension for true monkey patching: http://www.php.net/manual/en/intro.runkit.php

    ReplyDelete