New in Runkit 1.0.4: PHP 5.6+, closures everywhere and 12 more new features

    Runkit 1.0.4 for PHP released!


    Congratulations to all Runkit users on the new long-awaited mega-release! If you constantly use Runkit and are well acquainted with its capabilities, history and development, then you can immediately proceed to describe the changes in release 1.0.4 . In any case, I propose to read the entire article.



    What is Runkit?


    Runkit is an extension of the PHP language that allows you to do things that are impossible from the point of view of this language. The extension functionality consists of three parts.

    Runtime manipulations


    The first and largest part of the Runkit functionality allows you to dynamically (during the execution of a PHP program) copy, modify and delete entities whose dynamic change is not provided by the PHP language itself.

    Runkit allows you to copy, override, and delete existing functions (including those built into the language), dynamically make a class a descendant of another class, inheriting all the contents (runkit_class_adopt), or detach the class from its parent by deleting the inherited contents (runkit_class_emancipate). You can also add, copy, redefine and delete methods of existing classes, add and remove their properties. In addition, Runkit allows you to override and delete previously defined constants.

    Runkit_sandbox


    The second most part of the functionality is the Runkit_Sandbox sandboxes. They allow you to execute part of the program in PHP in an isolated environment. Each sandbox can have its own PHP security settings such as safe_mode, safe_mode_gid, safe_mode_include_dir, open_basedir, allow_url_fopen, disable_functions, disable_classes. In addition, each sandbox can configure its own Runkit functionality in its own way: put down its own superglobal variables (we will discuss them below) and prohibit changing built-in functions.

    Sandboxes can include PHP files (via include (), include_once (), require () and require_once ()), call functions within themselves, execute arbitrary PHP code, print the values ​​of their internal variables, and complete their work. In addition, you can specify a function to intercept and process the output of the sandbox.

    Inside the sandbox, you can also create the Runkit_Sandbox_Parent anti-sandbox object to associate the sandbox with the parent environment. The functionality of the “anti-sandboxes” is very similar to the functionality of the “sandboxes”, but for security reasons, each function connecting with the external environment should be explicitly enabled when creating the “sandbox”.

    Superglobals


    Runkit also allows you to add new superglobal variables to PHP. To add such variables, it is enough to list their names with a comma in the runkit.superglobal property inside the PHP configuration file.

    Other


    In addition to the three main parts of the functionality, Runkit also has tools for checking the syntax of PHP code (runkit_lint and runkit_lint_file) and a runkit_import function that allows you to import a PHP file like include, but ignoring all the global code in this file. Depending on the flags, runkit_import can import functions or classes (in whole or in part), overriding or preserving existing ones.

    Why do we need Runkit?


    Runkit helps PHP programmers solve many different problems. I’ll talk about a few basic ones.

    Patching other people's programs


    Imagine that you are using a third-party library (or framework) and at some point you needed to change its behavior. However, the code that needs to be changed is in the private method of one of the library classes. The obvious solution is to edit the file containing this method. This is a working solution, however, the library code has now been changed and updating it becomes a troublesome task, because you will need to apply the patch every time you update the library. Another solution is to use Runkit to override the method of interest to us, this is done with a single call to the runkit_method_redefine function. There is a similar solution for overriding the functions (runkit_function_redefine) and constants (runkit_constant_redefine) that already exist in the program. A similar change to the program code at runtime is called “monkey patching”. On specialized online forums you can find various recipes for patching with the help of Runkit libraries such as WordPress, 1C-Bitrix, CodeIngniter, Laravel, etc. To solve some problems, it can be useful to replace functions built into the PHP language itself, and Runkit can do this too.

    Isolated environment for executing custom scripts


    Using sandboxes, Runkit_Sandbox often make environments for executing custom code. When properly configured, this makes it possible to isolate user code from the main system. In its simplest form, it looks like this:

    $options = […];
    $sandbox = new Runkit_Sandbox($options);
    $sandbox->ini_set(…);
    $sandbox->eval($code);
    

    Other use cases


    Using runkit, you can also organize program code updates on the fly, as is done, for example, in phpdaemon (see habrahabr.ru/post/104811 ).

    Unit tests


    Runkit’s ability to override functions and methods makes it extremely useful when writing unit tests in PHP. Using Runkit, making test doubles (stubs or "spies") during test execution becomes simple, even if the architecture of the tested code does not support dependency injection . There are ready-made libraries that implement the substitution of PHP methods and functions for stubs in the context of unit tests (for example, ytest , phpspy and others). With the right library choice you can get amazingly simple tests (see, for example, here ).

    History of Runkit


    Start


    Runkit was established in 2005 by Sara Golemon ( Sara Golemon ). The latest author's release (version 0.9) was released on 06.06.06. In October 2006, Sarah stopped supporting the extension without releasing version 1.0. At that time, Runkit contained functions for manipulating constants, functions, methods, runkit_import, a function for adding properties to classes, syntax checking functions, sandboxes and superglobal variables. The documentation on the php.net website ( http://php.net/runkit ) froze around version 0.7, so that even part of the functions made by Sarah herself are still not described. In addition, in this documentation, all the functionality of Runkit is called experimental, which was relevant in 2006, but is absolutely untrue now.

    Decline


    From October 2006 to October 2009, no one supported the extension, and the PHP language moved forward, which is why, despite the changes from members of the PHP community, Runkit was unstable in PHP 5.2 and caused segmentation errors.

    Rebirth


    In October 2009, I began to repair Runkit, and then develop it at https://github.com/zenovich/runkit . I’ll tell you which releases were released during this time and what changes are included in them.

    Release 1.0.0 (April 1, 2010)


    In fact, this release never happened, it is fictitious :). It includes all community edits after the release of version 0.9 and before the release of 1.0.1.

    Release 1.0.1 (October 3, 2010)


    The first real release of Runkit after 2006. Now Runkit supports all versions of PHP up to and including 5.3. More than ten serious bugs were fixed, including those leading to PHP crashes. The main ones are:
    • crashes during import through runkit_import () of properties and constants with array values ​​are fixed,
    • crashes when importing functions and methods with static variables are fixed,
    • fixed crash when manipulating functions,
    • fixed crash of runkit_method_copy when working with protected methods,
    • fixed crash when shutting down PHP after changing built-in functions,
    • fixed crash when calling the original method after applying the runkit_method_copy function to it, if the method had static variables,
    • the names of the generated methods are no longer lowercase.

    Release 1.0.1 adds the ability to define and modify static methods using the new constant RUNKIT_ACC_STATIC:

    runkit_method_add('MyClass', 'newMethod', '$arg1, $arg2', '/* some code here*/', RUNKIT_ACC_STATIC);
    

    Also added the ability to import static class properties. When importing a class, static properties will be copied by default, but their import can be disabled using the new constant RUNKIT_IMPORT_CLASS_STATIC_PROPS:

    runkit_import('myfile.inc', RUNKIT_IMPORT_CLASSES); // импортировать классы целиком
    runkit_import('myfile.inc', RUNKIT_IMPORT_CLASSES & ~ RUNKIT_IMPORT_CLASS_STATIC_PROPS); // импортировать классы, но не импортировать их статические свойства
    runkit_import('myfile.inc', RUNKIT_IMPORT_CLASS_STATIC_PROPS); // импортировать только статические свойства классов
    

    In addition, the release added the ability to apply a closure to the sandbox using Runkit_Sandbox :: call_user_func ().

    Release 1.0.2 (October 5, 2010)


    Bug fix of the previous release. Improved compatibility with PHP 5.3.

    Release 1.0.3 (January 2, 2012)


    Fixed inheritance when renaming methods using runkit_method_rename. The assembly of the extension for Windows has been fixed.

    Release 1.0.4 (September 25, 2015)


    The long-awaited Mega Release! Full support for PHP5 (up to and including PHP 5.6).

    A lot has been done in this release to stabilize the work of Runkit: tests were run for each version of PHP in four versions: with ZTS and without, under valgrind and without. For almost every change, new tests were added. Thanks to this, it was possible to identify and correct a huge number of errors.

    Among the important corrections are the following:
    • Fixed crashes when changing, deleting or renaming functions, methods and properties of classes for which Reflection objects were previously created,
    • fixed crash when creating Runkit_Sandbox with register_globals setting enabled
    • fixed crash due to a syntax error in the file loaded via runkit_import (),
    • fixed crash when working with constants that have names of one character,
    • fixed crash when calling a renamed private or protected method.

    In total, more than forty (!!!) important corrections were made in the release; their complete list can be found in the package.xml file .

    Now I’ll tell you about the main changes in the functionality.

    Functions and Methods

    Closures

    For PHP 5.3+, the functions runkit_function_add, runkit_function_redefine, runkit_method_add and runkit_method_redefine now support closure as parameters. For example, if earlier to redefine a function it was necessary to write an expression of the form

    runkit_function_redefine('sprintf', '$s', 'return $s;');
    

    which used eval to turn a string into bytecode, which is very slow, now you can write

    runkit_function_redefine('sprintf', function($s) {return $s;});
    

    No eval is performed at the same time, and maintaining such code is much easier - there are no more parts of the program inside string literals! The same goes for the runkit_function_add, runkit_method_add and runkit_method_redefine functions.

    Magic methods

    Also in Runkit, manipulations with the magic methods __get, __set, __isset, __unset, __clone, __call, __callStatic, serialize, unserialize, __debugInfo and __toString are now fully supported. The same goes for constructors and destructors both with the modern naming method and with naming in the style of PHP4.

    Doc-comments

    Now, when adding or overriding methods and functions using the old syntax (when the arguments of the new function and its body are passed in strings), you can specify doc-comments. For this purpose, the functions runkit_function_add, runkit_function_redefine, runkit_method_add and runkit_method_redefine have a new optional (last order) argument - doc_comment:

    runkit_method_redefine('MyClass','myMethod', '$arg', 'return $arg',  RUNKIT_ACC_PRIVATE, 'my doc_comment'); // переопределяет приватный метод с doc-comment’ом
    runkit_method_add('MyClass','myMethod2', '$arg', 'return $arg',  NULL, 'my doc_comment2'); // добавляет приватный метод с doc-comment’ом
    

    When defining functions and methods in the new style (through closures), doc-commentes can be specified in the same way as when defining ordinary functions — through comments on the body of the function. Both methods can be combined - priority is given to doc-comment passed through the argument. In addition, the affixing of doc-comments was fixed during inheritance, copying and renaming of methods and functions.

    Return values ​​by reference

    Added the ability to add and redefine functions and methods so that a new function (or method) returns a value by reference. In order for the new function specified using the old syntax (when the arguments of the new function and its body are passed in strings) to return a value by reference, you need to pass a new argument to the function runkit_function_add (or runkit_function_redefine) - return_ref - with the value TRUE. For instance,

    runkit_function_redefine('my_function', '$a', 'return $a;', TRUE); // возвращает значение по ссылке
    

    For a similar addition (or overriding) of the method, the flags argument is used with the RUNKIT_ACC_RETURN_REFERENCE bit set. For instance,

    runkit_method_redefine('MyClass', 'myMethod', '$a', 'return $a;', RUNKIT_ACC_PROTECTED | RUNKIT_ACC_RETURN_REFERENCE); // protected-метод возвращает значение по ссылке
    

    If you define a function or method using the new syntax (via closures), then you do not need all these flags - just add an ampersand in front of the function argument list:

    runkit_function_redefine('my_function', function &($a) {return $a;}); // возвращает значение по ссылке
    

    Class Properties

    The internal implementation of manipulating class properties has been completely redesigned. Adding, deleting, and importing class properties now correctly affects child classes. Moreover, now these actions can affect the objects of the class and its descendants. To enable this effect, you need to set the RUNKIT_OVERRIDE_OBJECTS bit in the flags argument when calling the runkit_default_property_add and runkit_default_property_redefine functions. For instance,

    runkit_default_property_add('MyClass', 'newProperty', 'value'); // не влияет на объекты класса и его потомков
    runkit_default_property_add('MyClass', 'newProperty', 'value', RUNKIT_OVERRIDE_OBJECTS); // добавит свойство не только в классы и классы-потомки, но и в их объекты
    

    The same goes for importing class properties:

    runkit_import('myfile.inc', RUNKIT_IMPORT_CLASS_PROPS); // импортирует свойства классов, не переопределяя существующие свойства и не затрагивая объекты
    runkit_import('myfile.inc', RUNKIT_IMPORT_CLASS_PROPS | RUNKIT_IMPORT_OVERRIDE); // импортирует свойства классов, переопределяя существующие свойства, но не затрагивая объекты
    runkit_import('myfile.inc', RUNKIT_IMPORT_CLASS_PROPS | RUNKIT_IMPORT_OVERRIDE | RUNKIT_OVERRIDE_OBJECTS); // импортирует свойства классов, переопределяя существующие свойства и изменяя свойства в объектах
    

    In addition, a new runkit_default_property_remove () function has been added to remove properties from classes. To remove a property not only from a class, but also from its objects, the runkit_default_property_remove function has a third optional parameter:

    runkit_default_property_remove('MyClass', 'myProperty'); // удаляет свойство из класса, но оставляет его в объектах
    runkit_default_property_remove('MyClass', 'myProperty', TRUE); // удаляет свойство из класса и из его объектов
    

    Also now, when adding and overriding class properties, now you can use not only scalar values, but also arrays.

    Classes

    Previously, the runkit_class_adopt and runkit_class_emancipate functions, although they changed the contents of classes, did not affect their hierarchy (i.e., after applying runkit_class_adopt, the class formally still did not have a parent, and after runkit_class_emancipate the parent still remained). This is now fixed.

    Register in entity names and namespaces

    Working with constants, functions, methods and properties now fully supports namespaces. Runkit also stopped lowering the names of properties, classes, methods, and functions that it creates (as it did before).

    Extra sandbox security

    For Runkit_Sandbox sandboxes, you can now disable the INI setting allow_url_include. Also now, regardless of the platform, the open_basedir configuration supports path lists (previously, only one path could be entered).

    Updates

    Updating Runkit is now much easier. Now this can be done in a way familiar to all PECL users through the official pecl.php.net channel . To install the latest release of Runkit, just dial

    pecl install runkit
    

    In addition, all release archives are now available at http://pecl.php.net/runkit .

    Conclusion


    Now Runkit is used in many well-known companies and projects around the world for unit testing, as well as for many other tasks. I am sure that a great future awaits him. This will be possible thanks to donations, which can now be made with one click from the project page github.com/zenovich/runkit or directly from phpinfo ().

    Thanks!

    Also popular now: