Implementation

Redefining a function using Patchwork doesn't actually modify its source or the opcodes produced from it. Doing the former would only allow to redefine a function once per runtime, and the latter is entirely impossible in userland PHP code. The process behind this so-called redefinition, whose name is more metaphorical than technically representative, is considerably more complicated, and the goal of this document is to explain it to those who are interested.

Intercepting File Includes

Patchwork uses a stream wrapper to intercept include statements and its variations.

The following line from Patchwork.php registers the stream wrapper, whose code is located in lib/Preprocessor/Stream.php:

Preprocessor\Stream::wrap();

This wrapper is attached to the file protocol, and thus can override any of PHP's basic filesystem routines, such as opening a filesystem node (e.g. using fopen) or deleting it (using unlink). However, Patchwork's wrapper preserves the original functionality of all routines except for file opening, which is handled by the wrapper's stream_open method.

This method first checks if the request to open a file comes from an include statement. This check is done by checking for a specific bit flag (128 or 0x80) in the $mode argument. Any mention of this flag is nowhere to be found in PHP's documentation, yet it being set always coincides with the request coming from a statement of the include family (this has been tested on multiple PHP installations running on different platforms).

If that turns out to be the case, Patchwork temporarily unwraps the file protocol to read the file being included, preprocesses its contents (more on that soon), writes the result to an in-memory stream and returns this stream's handle instead of a handle corresponding to the file being included.

Otherwise, stream_open just relays control to the default filesystem routine (fopen in this case), like other methods in Patchwork's wrapper.

Preprocessing Code

Patchwork by default attaches four callbacks that are responsible for code preprocessing:

Preprocessor\attach(array(
    Preprocessor\Callbacks\Preprocessor\propagateThroughEval(),
    Preprocessor\Callbacks\Interceptor\markPreprocessedFiles(),
    Preprocessor\Callbacks\Interceptor\injectCallInterceptionCode(),
    Preprocessor\Callbacks\Interceptor\injectScheduledPatchApplicationCode(),
));

The most important of them is injectCallInterceptionCode, which, as the name suggests, injects code to the beginning of each function and method, and this code makes it possible to intercept any call to a function that resides in a preprocessed PHP file.

Behavior of injectCallInterceptionCode

The code injected by injectCallInterceptionCode is contained in a constant named CALL_INTERCEPTION_CODE. Before injection, this code is condensed to a single line to preserve line numbering, so that there wouldn't be any PHP error messages with incorrect line numbers. Also, this code is optimized to achieve minimum performance overhead in cases when the call doesn't have to be intercepted.

Intercepting Calls

Now that we've injected code into every user-defined function, we can actually achieve an effect equivalent to redefining them. We can make it possible to attach handlers to the event of a function being called, which is what Patchwork does. All that's left to do beyond this point is trivial, so we'll conclude this article here.

If there's a detail in the implementation of Patchwork that remained a mystery to you after reading this, please leave a comment!