После статьи про скрытые возможности DevelNext, многим стал интересен специальный класс Environment, который позволяет создавать изолированные или частично изолированные окружения для выполнения кода. Раз эта тема интересна многим, тогда мы расскажем, как они работают изнутри и рассмотрим работу с окружениями более детально.
А если быть более конкретным, то имя класса
1 | php\lang\Environment |
Что же такое окружение? В DevelNext и JPHP любой код обязательно выполняется в каком-то окружении, даже если это и не заметно. Окружение содержит множество информации, это в первую очередь – глобальные переменные, загруженные модули, классы, константы и функции, даже статические переменные классов, у каждого окружения свой буфер вывода (см. функции ob_*). К тому же, окружения имеют свои опции.
Когда создавался JPHP, мы естественно брали в расчет оригинальный движок Zend PHP. Оригинальный движок умеет работать в изолированных разных окружениях, только это обеспечивает обычно веб-сервер, например Apache Server. В самом же языке нет возможностей создавать свои окружения, только через дополнительные расширения.
И если кто-то захочет написать веб-сервер на JPHP, он легко сможет повторить полную изолированность скриптов на каждый запрос, как это есть в Zend PHP и Apache Server, применяя заветный класс Environment. Если еще подумать, то этот класс можно применять, если вы ходите добавить в свою программу систему скриптов, чтобы пользователи вашей программы могли с помощью скриптов php расширять ее функционал. Изолированность окружений поможет оградить доступ к основному функционалу вашей программы из этих скриптов.
Создание окружения это простой процесс, нам всего-то нужно создать объект:
use php\lang\Environment; // создаем окружение. $env = new Environment();
После чего, мы легко можем выполнять код от “имени” этого окружения, т.е. в его рамках:
$env->execute(function() { echo "Код нового окружения."; });
Чтобы передать в окружение переменную извне, используются стандартные возможности языка php, через use:
$name = 'Мир'; $env->execute(function() use ($name) { echo "Привет, $name!"; });
Если вы просто создадите окружение, как описано выше, то никакие ваши объявленные классы и функции в этом окружении работать не будут. При попытке их использовать будут возникать ошибки по типу Class ‘…’ not found и т.п. Также не будут работать автозагрузчики классов (autoloaders), т.е. автоматически классы подключаться не будут в ваше окружение. JPHP по-умолчанию настраивает автозагрузку классов для первого окружения, в котором выполняется ваш код. Но это всё не беда и легко настраивается.
Для начала, попробуем включить автозагрузку классов как в окружении извне, в котором было создано наше окружение:
// импортирует все автозагрузчики для классов. $env->importAutoLoaders();
А теперь, попробуем импортировать наш написанный класс в окружение:
// Объявляем тестовый класс для примера. class Test { function __construct($text) { $this->text = $text; } function call() { echo $this->text, "\n"; } } // импортируем класс. $env->importClass(Test::class); // ::class специальный синтаксис php // пытаемся использовать класс в окружении. $env->execute(function () { $test = new Test('Второе окружение'); $test->call(); });
Импорт функций происходит по тому же принципу, только не забывайте, что указывать надо полное имя функции или класса, вместе с их namespace:
function test($text) { echo $text, "\n"; } // импортируем функцию. $env->importFunction('test'); // если у вас вверху есть namespace, то импортируем полное название: $env->importFunction('namespace\test'); // выполняем: $env->execute(function () { test('Привет'); });
Для того, чтобы в окружении объявить константу, используем специальный метод
1 | defineConstant() |
// объявляем константу: $env->defineConstant('MY_CONST', 12345); // используем эту константу. $env->execute(function () { echo MY_CONST, "\n"; // выведет 12345 }); // выведет MY_CONST, т.к. константы нет в родном окружении. echo MY_CONST, "\n";
У вас также есть возможность повесить хук на вывод текста через функции echo, print, print_r, var_dump и т.п. Иногда это удобно и сделать это также просто:
// ставим хук на вывод: $env->onOutput(function ($text) { // в переменной $text будет то, что мы выводим через echo в окружении. });
У класса Environment также есть два метода для реализации функционала обмена сообщениями, по типу RPC.
// определяем функцию, которая принимает сообщения: $env->onMessage(function ($msg) { var_dump($msg); return 'результат'; }); // отправляем тестовое сообщение: $result = $env->sendMessage(['type' => 'test', 'arg' => 32]); // результат в переменной $result echo $result, "\n";
Если внутри окружения объявлена функция или класс, которых нет в родительском окружении, то их можно в него экспортировать с помощью методов exportClass() и exportFunction(). Методы работают так же как и importClass() + importFunction(), только в обратную сторону.
Если вы хотите создать окружение точно такое же как и родительское, со всеми глобальными переменными, функциями, классами и т.п., то и для этого есть возможность:
use php\lang\Environment; // создаем окружение на основе текущего. $env = new Environment(Environment::current()); // ... все функции и классы будут доступны и в нашем окружении.
Однако, не забывайте, что создается копия окружения, а не ссылка одного на другое, поэтому, если в одном вы объявите новый класс уже после создания окружения, то в другом он автоматически не появится. Вы по прежнему можете использовать методы для импорта и экспорта классов и функций.
По-умолчанию, окружения не пригодны для работы с потоками, так код в них быстрее работает. Окружения по-умолчанию будут выдавать всякие непонятные ошибки при работе с потоками, это нормально, т.к. они не предназначены для этого. Однако, есть возможность включить поддержку потоков для создаваемого окружения через опцию:
use php\lang\Environment; // создаем конкурентное окружения для многопоточности. $env = new Environment(null, Environment::CONCURRENT); // используем поток в окружении. $env->execute(function () { $thread = new Thread(function () { while (true) { echo "Привет\n"; sleep(1); } }); });
Такие окружения необходимы, чтобы код в них один раз выполнился, а далее их использовать – нет надобности. Все это сделано наподобие того, что описано в начале статьи, по тому же принципу, как работает оригинальный Zend PHP. Чтобы создать такое окружение, используйте специальную опцию HOT_RELOAD:
use php\lang\Environment; // создаем конкурентное окружение с опцией hot reload (горячей перезагрузкой). $env = new Environment(null, Environment::CONCURRENT | Environment::HOT_RELOAD);
Эта опция позволит грузить все модули, классы и т.п. с чистого листа, реализуя таким образом горячую перезагрузку кода. Окружения с данной опцией пока являются экспериментальной возможностью, они могут накапливать память, не освобождая её, что может приводить к ошибкам в программе. Это может происходить не часто, используйте данную возможность только во время разработки программы или с осторожностью.
Есть еще некоторые специфичные возможности окружений, например source map-ы и пакеты, о которых мы возможно когда-нибудь расскажем в следующих статьях. Эти темы слишком обширные и специфичные для данной статьи.
Таймеры штука интересная и очень полезная в разработке. Если вы работали только с серверным php, то скорее всего, таймеры вам…
Возможно, писать о себе в третьем лице не очень хорошо, но лучше заголовка для этой статьи я не придумал. Сегодня…
Итак, в этой статье мы расскажем о 7 скрытых возможностях DevelNext. 1. Копирование компонентов в txt Вы когда-нибудь пробовали скопировать…
Представляем вам осеннюю версию DevelNext 16.7.0, с поддержкой возможностей из PHP 7, менеджером скинов и новыми компонентами в стиле Material…
Сегодня у нас отличные новости, встречайте новый летний DevelNext 16.6.0! Мы постарались сделать его еще более дружелюбным для новичков, постарались…
Сегодня у нас отличные новости, встречайте новый DevelNext 16.5.2! DevelNext это теперь по праву целая студия, а не просто конструктор,…
View Comments
Спасибо, ото думал.