Understanding the broken instancing metaphore

Why I need to use runkit

In an earlier post I explained the way I’m dressing up an adapter class for mocking trying to solve the problem created by the fact that the class has no real methods but to wrap the global functions but the __get and __call magic ones.
Aside for some WordPress-related considerations the adapter classes implement a build_facade static method that will add methods to the classes using runkit

    class adclasses_Functions{

        ...
    /**
     * Builds the façade for the class filling it with methods
     * @param  string $file_path An optional file path to read the commma separated list of functions to add from.
     * @return bool            True if the facade was successfully built, false otherwise.
     */
    public static function build_facade($file_path = null)
    {
        // to avoid re-adding the methods
        if (self::$built_facade) {

            return false;
        }
        if (null === $file_path) {
            // resort to the default one
            $file_path = dirname(__FILE__) . '/../assets/functions.txt';
        }

        try {
            $contents = file_get_contents($file_path);
            if (false === $contents) {

                return false;
            }
        }
        catch(Exception $e) {

            return false;
        }
        $functions_to_facade = explode(',', $contents);
        $functions_to_facade = array_unique($functions_to_facade);

        foreach ($functions_to_facade as $function_name) {
            runkit_method_add('adclasses_Functions', $function_name, '', 'return null;');
        }
        // set the built_facade to avoid re-adding methods
        self::$built_facade = true;

        return true;
    }

The method does little and loads the functions either from a passed file or from a default one, /assets/functions.txt. The file looks like this (cut to limit space)

wp_unregister_globals,wp_fix_server_vars,wp_check_php_mysql_versions,wp_favicon_request,wp_maintenance,timer_start,timer_stop,wp_debug_mode,wp_set_lang_dir,require_wp_db,wp_set_wpdb_vars,wp_using_ext_object_cache,wp_start_object_cache,wp_not_installed,wp_get_mu_plugins,wp_get_active_and_valid_plugins,wp_set_internal_encoding,wp_magic_quotes,shutdown_action_hook,wp_clone,is_admin,is_blog_admin,is_network_admin,is_user_admin,is_multisite,get_current_blog_id,wp_load_translations_early,wp_initial_constants,wp_plugin_directory_constants,wp_cookie_constants,wp_ssl_constants,wp_functionality_constants,wp_templating_constants,_mb_substr,_hash_hmac,mysql2date,current_time,date_i18n,number_format_i18n,size_format,get_weekstartend,maybe_unserialize,is_serialized,is_serialized_string,maybe_serialize,xmlrpc_getposttitle,xmlrpc_getpostcategory,xmlrpc_removepostdata,wp_extract_urls,do_enclose,wp_get_http,wp_get_http_headers,is_new_day,build_query,_http_build_query,add_query_arg,remove_query_arg,add_magic_quotes,wp_remote_fopen,wp,get_status_header_desc,status_header,wp_get_nocache_headers,nocache_headers,cache_javascript_headers,get_num_queries,bool_from_yn,do_feed,do_feed_rdf,do_feed_rss,do_feed_rss2,do_feed_atom,do_robots,is_blog_installed,wp_nonce_url,wp_nonce_field,wp_referer_field,wp_original_referer_field,wp_get_referer,wp_get_original_referer,wp_mkdir_p,path_is_absolute,path_join,get_temp_dir,wp_is_writable,win_is_writable,wp_upload_dir,wp_unique_filename,wp_upload_bits,wp_ext2type,wp_check_filetype,wp_check_filetype_and_ext,wp_get_mime_types,get_allowed_mime_types,wp_nonce_ays,wp_die,_default_wp_die_handler,_xmlrpc_wp_die_handler,_ajax_wp_die_handler,_scalar_wp_die_handler,wp_send_json,wp_send_json_success,wp_send_json_error,_config_wp_home,_config_wp_siteurl,_mce_set_direction,smilies_init,wp_parse_args

where it’s clear that the functions to dump come from a WordPress run.

The persistence of the façade

From my current understanding runkit will modify the class for the whole execution of the PHP program or, in more practical terms, until the global PHP namespace persists.
Trying to add the same method to the same class twice will throw an error

$exit1 = runkit_add_method('SomeClass','someMethod','','return null;');
$exit1 = runkit_add_method('SomeClass','someMethod','','return null;');

and, in the case above, the way I have solved the problem is to use a bool, $built_facade, as a static class property to know if the façade of methods was built already and avoid attempting its building again.

Object get pulled rather than cast

It may come as natural but I’ve read (web) some confusion about the argument: PHPUnit mocks and stubs are instances of a class. Sure modified to suit the test needs but nonetheless instances.
Being instances of a class they will define properties and methods the class defines as instance properties and metods at the moment they are instanced, right?
Following along this line the following test should pass

<?php
dl('runkit.so');
/*
* A class with no methods in it
*/
class Bar
{
}
/*
 * The test class
 */
class runkit_Test extends PHPUnit_Framework_TestCase
{
    public function testInstancesDoNotUpdateMethods()
    {
        // the Bar class has no explicitly defined instance methods
        $this->assertCount(0, array_keys(get_class_methods('Bar')));
        // I instance a class that has no explicitly defined instance methods
        $bar = new Bar();
        // I expect the method someMethod not to be defined
        $has_someMethod = method_exists($bar, 'someMethod');
        $this->assertFalse($has_someMethod);
        // add someMethod to the class defined instance methods
        runkit_method_add('Bar', 'someMethod', '', 'return null;');
        // the class should now have 1 instance method
        $this->assertCount(1, array_keys(get_class_methods('Bar')));
        // the object I instanced before adding the method to the class should not have the method...
        $has_someMethod = method_exists($bar, 'someMethod');
        $this->assertFalse($has_someMethod, 'bar should not have someMethod!');
    }
}

But the test will fail like this

There was 1 failure:

1) runkit_Test::testMethods
bar should not have someMethod!
Failed asserting that true is false.

/.../runkit_tests.php:60

FAILURES!
Tests: 1, Assertions: 4, Failures: 1.

Objects will add newly defined methods

So, to my surprise, the instance bar, created before someMethod was added to the class, will be upgraded to the new and latest defined instance methods. My idea, and the often used metaphore, of a mould-like class to cast instances is shattered and dynamism comes into play. I’m not that knowledgeable into PHP and runkit inner workings to understand exactly why but it’s nice to know.

Objects will upgrade re-defined methods

Not unexpectedly bar will not only sport the latest method added but also the latest version of it: the assertions below will all pass (imagine them appended to the previous test code)

// returns null as it's implementation in runkit_method_add specifies
$this->assertNull($bar->someMethod());
// modify the method
runkit_method_redefine('Bar', 'someMethod', '', 'return true;');
// the method should upgrade
$this->assertTrue($bar->someMethod());

Objects will lose the removed methods

Again without surprise the object will not have methods removed by runkit defined and the assertions below, appended to the previous tests, will all pass.

// remove the method
runkit_method_remove('Bar', 'someMethod');
// object should not have someMethod
$has_someMethod = method_exists($bar, 'someMethod');
$this->assertFalse($has_someMethod);