Pull to refresh

Новое в Runkit 1.0.4: PHP 5.6+, closures везде и еще 12 новых фич

Reading time 10 min
Views 14K

Runkit 1.0.4 для PHP выпущен!


Поздравляю всех пользователей Runkit с новым долгожданным мега-релизом! Если вы постоянно используете Runkit и хорошо знакомы с его возможностями, историей и развитием, то можете сразу переходить к описанию изменений релиза 1.0.4. В любом случае предлагаю прочесть статью целиком.



Что такое Runkit?


Runkit – это расширение языка PHP, позволяющее делать вещи, невозможные с точки зрения этого языка. Функционал расширения состоит из трех частей.

Runtime Manipulations


Первая и самая крупная часть функционала Runkit позволяет динамически (в процессе выполнения PHP-программы) копировать, изменять и удалять такие сущности, динамическое изменение которых самим языком PHP не предусмотрено.

Runkit позволяет копировать, переопределять и удалять существующие функции (в том числе встроенные в язык), динамически делать класс потомком другого класса, наследуя всё содержимое (runkit_class_adopt), или откреплять класс от родителя, удаляя унаследованное содержимое (runkit_class_emancipate). Также можно добавлять, копировать, переопределять и удалять методы существующих классов, добавлять и удалять их свойства. Кроме того, Runkit позволяет переопределять и удалять определенные ранее константы.

Runkit_Sandbox


Вторая большая часть функционала – «песочницы» Runkit_Sandbox. Они позволяют выполнять часть программы на PHP в изолированном окружении. У каждой «песочницы» могут быть по-своему настроены параметры безопасности PHP такие как safe_mode, safe_mode_gid, safe_mode_include_dir, open_basedir, allow_url_fopen, disable_functions, disable_classes. Кроме того, каждая «песочница» может по-своему настраивать внутри себя функционал Runkit: проставлять свои суперглобальные переменные (о них речь пойдет ниже) и запрещать изменение встроенных функций.

«Песочницы» могут подключать PHP-файлы (через include(), include_once(), require() и require_once()), вызывать внутри себя функции, выполнять произвольный код на PHP, печатать значения своих внутренних переменных, завершать свою работу. Кроме того, можно указать функцию для перехвата и обработки вывода «песочницы».

Внутри «песочницы» также можно создать объект «анти-песочницы» Runkit_Sandbox_Parent для связи «песочницы» с родительским окружением. Функционал «анти-песочниц» очень похож на функционал «песочниц», но из соображений безопасности, каждая связующая с внешним окружением функция должна быть явно включена при создании «песочницы».

Superglobals


Runkit также позволяет добавлять в PHP новые суперглобальные переменные. Чтобы добавить такие переменные, достаточно перечислить их имена через запятую в свойстве runkit.superglobal внутри файла конфигурации PHP.

Прочее


Помимо трех основных частей функционала в Runkit также есть средства для проверки синтаксиса кода на PHP (runkit_lint и runkit_lint_file) и функция runkit_import, позволяющая импортировать PHP-файл подобно include, но игнорирующая весь глобальный код в этом файле. В зависимости от флагов runkit_import может импортировать функции или классы (полностью или частично), переопределяя или сохраняя уже существующие.

Зачем нужен Runkit?


Runkit помогает PHP-программистам решать множество различных задач. Расскажу о нескольких основных.

Патчинг чужих программ


Представьте, что вы используете стороннюю библиотеку (или фреймворк) и в какой-то момент вам понадобилось изменить ее поведение. Однако код, который нужно изменить находится в private-методе одного из классов библиотеки. Очевидное решение — отредактировать файл, содержащий этот метод. Это рабочее решение, однако код библиотеки теперь изменен и ее обновление становится хлопотной задачей, потому что нужно будет применять патч при каждом обновлении библиотеки. Другое решение — с помощью Runkit переопределить интересующий нас метод, это делается с помощью одного вызова функции runkit_method_redefine. Аналогичное решение есть для переопределения уже существующих в программе функций (runkit_function_redefine) и констант (runkit_constant_redefine). Подобное изменение кода программы во время выполнения называется «monkey patching». На специализированных интернет-форумах можно найти различные рецепты патчинга с помощью Runkit таких библиотек как WordPress, 1С-Битрикс, CodeIngniter, Laravel и т.п. Для решения некоторых проблем бывает полезно заменять функции, встроенные в сам язык PHP, и Runkit это тоже умеет.

Изолированное окружение для выполнения пользовательских скриптов


С помощью «песочниц» Runkit_Sandbox часто делают окружения для выполнения пользовательского кода. При правильной настройке это дает возможность изолировать пользовательский код от основной системы. В простейшем виде это выглядит так:

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

Другие варианты использования


С помощью runkit можно также организовать обновление кода программы на лету, как это, например, делается в phpdaemon (см. habrahabr.ru/post/104811).

Юнит-тесты


Возможности Runkit по переопределению функций и методов делают его крайне полезным при написании unit-тестов на PHP. С помощью Runkit изготовление тестовых двойников (заглушек или «шпионов») во время выполнения тестов становится простым делом, даже если архитектура тестируемого кода не поддерживает dependency injection. Существуют готовые библиотеки, реализующих подмену методов и функций PHP на заглушки в контексте unit-тестов (например, ytest, phpspy и другие). При правильном выборе библиотеки можно получить изумительно простые тесты (см. например, здесь).

История развития Runkit


Начало


Runkit был создан в 2005-м году Сарой Големон (Sara Golemon). Последний авторский релиз (версия 0.9) был выпущен 06.06.06. В октябре 2006-го года Сара перестала поддерживать расширение, так и не выпустив версию 1.0. На тот момент Runkit содержал в себе функции для манипулирования константами, функциями, методами, runkit_import, функцию добавления свойств в классы, функции проверки синтаксиса, песочницы и суперглобальные переменные. Документация на сайте php.net (http://php.net/runkit) застыла в районе версии 0.7, так что в ней до сих пор не описана даже часть функций, сделанных самой Сарой. Кроме того, в этой документации весь функционал Runkit называется экспериментальным, что было актуально в 2006-м, но абсолютно не соответствует действительности сейчас.

Упадок


С октября 2006-го по октябрь 2009-го расширение никем не поддерживалось, а язык PHP шел вперед, из-за чего, несмотря на правки от участников PHP-сообщества, уже в версии PHP 5.2 Runkit работал нестабильно и вызывал ошибки сегментации.

Возрождение


В октябре 2009-го я стал чинить Runkit, а потом и развивать его на https://github.com/zenovich/runkit. Расскажу, какие релизы выпущены за это время и какие изменения в них включены.

Релиз 1.0.0 (1 апреля 2010 года)


На самом деле этого релиза никогда не было, он фиктивный :). К нему относятся все правки сообщества после выпуска версии 0.9 и до релиза 1.0.1.

Релиз 1.0.1 (3 октября 2010 года)


Первый настоящий релиз Runkit после 2006-го года. Теперь Runkit поддерживает все версии PHP до 5.3 включительно. Исправлено более десяти серьезных ошибок, в том числе приводивших к падениям PHP. Основные из них:
  • устранены падения при импорте через runkit_import() свойств и констант со значениями-массивами,
  • устранены падения при импорте функций и методов со статическими переменными,
  • устранено падение при манипуляциях с функциями,
  • устранено падение runkit_method_copy при работе с protected методами,
  • устранено падение при завершении работы PHP после изменения встроенных функций,
  • устранено падение при вызове исходного метода после применения к нему функции runkit_method_copy, если в методе были статические переменные,
  • имена создаваемых методов больше не переводятся в нижний регистр.

В релизе 1.0.1 добавлена возможность определять и модифицировать статические методы с помощью новой константы RUNKIT_ACC_STATIC:

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

Также добавлена возможность импортировать статические свойства класса. При импорте класса статические свойства будут копироваться по умолчанию, однако их импорт можно отключить с помощью новой константы 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); // импортировать только статические свойства классов

Кроме того, в релизе была добавлена возможность применять замыкание к песочнице с помощью Runkit_Sandbox::call_user_func().

Релиз 1.0.2 (5 октября 2010 года)


Баг-фикс предыдущего релиза. Улучшена совместимость с PHP 5.3.

Релиз 1.0.3 (2 января 2012 года)


Исправлено наследование при переименовании методов с помощью runkit_method_rename. Починена сборка расширения под Windows.

Релиз 1.0.4 (25 сентября 2015 года)


Долгожданный Мега-Релиз! Полная поддержка PHP5 (вплоть до PHP 5.6 включительно).

В этом релизе было сделано очень много для стабилизации работы Runkit: тесты прогонялись для каждой версии PHP в четырех вариантах: c ZTS и без, под valgrind и без. Практически по каждому изменению добавлялись новые тесты. Благодаря этому удалось выявить и исправить огромное количество ошибок.

Среди важных исправлений можно выделить следующие:
  • устранены падения при изменении, удалении или переименовании функций, методов и свойств классов, для которых ранее были созданы объекты Reflection,
  • устранено падение при создании Runkit_Sandbox при включенной настройке register_globals,
  • устранено падение при ошибке синтаксиса в файле, загружаемом через runkit_import(),
  • устранено падение при работе с константами, имеющими имена из одного символа,
  • устранено падение при вызове переименованного private или protected метода.

Всего в релизе было сделано больше сорока (!!!) важных исправлений, их полный список можно посмотреть в файле package.xml.

Теперь расскажу о главных изменениях функционала.

Функции и методы

Closures

Для PHP 5.3+ функции runkit_function_add, runkit_function_redefine, runkit_method_add и runkit_method_redefine теперь поддерживают замыкания (closure) в качестве параметров. Например, если раньше для переопределения функции нужно было писать выражение вида

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

которое для превращения строки в байт-код использовало eval, что очень медленно, то теперь можно писать

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

Никаких eval’ов при этом не выполняется, к тому же поддерживать такой код намного проще – больше нет частей программы внутри строковых литералов! То же самое касается функций runkit_function_add, runkit_method_add и runkit_method_redefine.

Магические методы

Также в Runkit теперь полностью поддержаны манипуляции с магическими методами __get, __set, __isset, __unset, __clone, __call, __callStatic, serialize, unserialize, __debugInfo и __toString. То же самое касается конструкторов и деструкторов как при современном способе наименования, так и при наименовании в стиле PHP4.

Doc-comments

Теперь при добавлении или переопределении методов и функций с помощью старого синтаксиса (когда аргументы новой функции и ее тело передаются строками) можно указывать doc-comment’ы. С этой целью у функций runkit_function_add, runkit_function_redefine, runkit_method_add и runkit_method_redefine появился новый опциональный (последний по порядку) аргумент – 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’ом

При определении функций и методов в новом стиле (через замыкания) doc-comment’ы можно задавать так же, как это делается при определении обычных функций, – через комментарии над телом функции. Оба способа можно комбинировать – приоритет у doc-comment’а, переданного через аргумент. Кроме того, было починено проставление doc-comment’ов при наследовании, копировании и переименовании методов и функций.

Возврат значений по ссылке

Добавлена возможность добавлять и переопределять функции и методы так, чтобы новая функция (или метод) возвращала значение по ссылке. Для того чтобы новая функция, заданная с использованием старого синтаксиса (когда аргументы новой функции и ее тело передаются строками), возвращала значение по ссылке, нужно передать в функцию runkit_function_add (или runkit_function_redefine) новый аргумент – return_ref – со значением TRUE. Например,

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

При аналогичном добавлении (или переопределении) метода используется аргумент flags с установленным битом RUNKIT_ACC_RETURN_REFERENCE. Например,

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

Если же вы определяете функцию или метод с помощью нового синтаксиса (через замыкания), то все эти флаги вам не нужны – достаточно добавить амперсанд перед списком аргументов функции:

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

Свойства классов

Внутренняя реализация манипуляций со свойствами классов была полностью переработана. Добавление, удаление и импорт свойств класса теперь правильно отражаются на классах-потомках. Более того, теперь эти действия могут влиять и на объекты класса и его потомков. Чтобы включить такое влияние, нужно установить бит RUNKIT_OVERRIDE_OBJECTS в аргументе flags при вызове функций runkit_default_property_add и runkit_default_property_redefine. Например,

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

То же самое касается и импорта свойств классов:

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); // импортирует свойства классов, переопределяя существующие свойства и изменяя свойства в объектах

Кроме того, была добавлена новая функция runkit_default_property_remove() для удаления свойств из классов. Чтобы удалять свойство не только из класса, но и из его объектов у функции runkit_default_property_remove есть третий необязательный параметр:

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

Также теперь при добавлении и переопределении свойств классов теперь можно использовать не только скалярные значения, но и массивы.

Классы

Раньше функции runkit_class_adopt и runkit_class_emancipate хотя и меняли содержимое классов, но не влияли на их иерархию (т.е. после применения runkit_class_adopt у класса формально по-прежнему не было родителя, а после runkit_class_emancipate родитель по-прежнему оставался). Теперь это исправлено.

Регистр в именах сущностей и namespaces

Работа с константами, функциями, методами и свойствами теперь полностью поддерживает namespace’ы. Также Runkit перестал переводить в нижний регистр названия свойств, классов, методов и функций, которые он создает (как это было раньше).

Дополнительная безопасность песочниц

Для песочниц Runkit_Sandbox теперь можно отключать INI-настройку allow_url_include. Также теперь, независимо от платформы, настройка open_basedir поддерживает списки путей (раньше можно было ввести только один путь).

Обновления

Обновлять Runkit стало намного проще. Теперь это можно делать привычным для всех пользователей PECL способом через официальный канал pecl.php.net. Чтобы установить последний релиз Runkit’а, достаточно набрать

pecl install runkit

Кроме того, все архивы с релизами теперь доступны по адресу http://pecl.php.net/runkit.

Заключение


Сейчас Runkit используется во многих известных компаниях и проектах по всему миру как для unit-тестирования, так и для многих других задач. Уверен, что впереди его ждет большое будущее. Это станет возможным благодаря пожертвованиям, которые теперь можно делать одним кликом со страницы проекта github.com/zenovich/runkit или прямо из phpinfo().

Спасибо!
Tags:
Hubs:
+20
Comments 26
Comments Comments 26

Articles