Изолированные окружения в JPHP

После статьи про скрытые возможности DevelNext, многим стал интересен специальный класс Environment, который позволяет создавать изолированные или частично изолированные окружения для выполнения кода. Раз эта тема интересна многим, тогда мы расскажем, как они работают изнутри и рассмотрим работу с окружениями более детально.

Класс 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

У вас также есть возможность повесить хук на вывод текста через функции 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-ы и пакеты, о которых мы возможно когда-нибудь расскажем в следующих статьях. Эти темы слишком обширные и специфичные для данной статьи.

Читайте также:

1 комментарий

Добавить комментарий