Markus Malkusch's weblog 

Monkey patch a PHP class

by Markus Malkusch

Posted on Friday Jan 08, 2016 at 01:01AM in Technology

Monkey patch

PHP-Mock is a testing libray for mocking built-in PHP functions. This is done with a namespace monkey patch of a PHP function. Today I want to show a way to monkey patch a user land class with plain PHP (i.e. without extensions like runkit or UOPZ).

First of all, the motivation: Why the hell would I want such a thing? Honestly, I don't! What I want, is integrating PHP-Mock with Prophecy. To provide complete functionality, that integration must be able to prophesize functions which have call-by-reference parameters (e.g. exec()). Unfortunately upstream has one offending line of code which simply eats references. I wanted to prepare a pull request, but upstream seems to be reluctant:

So I would even consider that not being able to prophesize such API is a Prophecy feature rather than a bug [..]. This goes in line with the PhpSpec way: making it hard to work with badly designed APIs [..].

I do very much agree with PHP having a badly designed API, but still I want to provide the possibility to prophesize such badly designed functions. So let's replace that reference eating class locally. I did this experimental operation in c9cd844. In this commit you see (next to some documentation and an accidentially commited test) my own implementation of Prophecy\Doubler\ClassPatch\ProphecySubjectPatch and some change in composer.json:

     "autoload": {
-        "psr-4": {"phpmock\\prophecy\\": "classes/"}
+        "psr-4": {
+            "phpmock\\prophecy\\": "classes/",
+            "Prophecy\\": "overwrites/Prophecy/"
+        }

The magic is PHP's autoloading. Nowadays autoloading is effectively done by Composer's autoloader. Let's find some implicit functionality to provide a class path which would have a higher priority in discovering a class definition. In ClassLoader::findFileWithExtension() you can read that PSR-4 is prefered over PSR-0. Luckily Prophecy uses PSR-0. By providing a PSR-4 class path for the namespace Prophecy, Composer's autoloader will first see if I have an implementation for any class in Prophecy\*. If not it continues searching on its other sources, which includes the original prophecy PSR-0 class path.

But wait! I'm leveraging implementation details here. This is highly fragile and could break with any patch release of Composer or Prophecy without any further notice. Monkey patching is a very dangerous tool, but there can be use cases where it might be helpful. Having that implicit behaviour as an explicit feature in Composer would remove the fragility of user land monkey patching.

No one has commented yet.

Leave a Comment

HTML Syntax: Ausgeschaltet