Всем доброго дня, уважаемые читатели. В данной статье вы узнаете как добавить новые функции в runtime KPHP.

Совсем вкратце расскажу о том, что такое KPHP и на примере какой задачи вы узнаете о расширении возможностей runtime KPHP.

ДИСКЛЕЙМЕР: статья является гайдом. Бездумно лезть в runtime не стоит.

О KPHP

KPHP - компилируемый PHP. Де-факто PHP, транслированный в C++. В свою очередь это увеличивает производительность исходного кода как минимум потому что скомпилировано в бинарный файл.

О нашей задаче

Задача, которую мы решим, заключается в следующем - реализовать две функции для парсинга строк и файлов в формате ENV. Исключительно для демонстрации всех этапов добавления новых функций в runtime.

Приступаем

Итак, перейдём к нашему плану:

  1. Подготовим всё необходимое

  2. Добавим новые функции

  3. Напишем тесты

  4. Проверим работоспособность

Подготовим всё необходимое

Я работаю под Ubuntu 20.04. И для начала нам нужно установить следующее (в том случае, если у вас их нет):

  • git

  • make, cmake

  • g++,

  • python3,

  • pip3

  • php7.4

После установки вышеупомянутых пакетов, необходимо установить vk`шные. А перед тем, надо добавить репозитории:

sudo apt-get update

sudo apt-get install -y --no-install-recommends apt-utils ca-certificates gnupg wget

sudo wget -qO - https://repo.vkpartner.ru/GPG-KEY.pub | sudo apt-key add -

echo "deb https://repo.vkpartner.ru/kphp-focal/ focal main" >> /etc/apt/sources.list

И уже затем установить пакеты:

sudo apt-get update

sudo apt install git cmake make g++ gperf python3-minimal python3-jsonschema \
            curl-kphp-vk libuber-h3-dev kphp-timelib libfmt-dev libgtest-dev libgmock-dev libre2-dev libpcre3-dev \
            libzstd-dev libyaml-cpp-dev libnghttp2-dev zlib1g-dev php7.4-dev libmysqlclient-dev libnuma-dev

Проделав это, переходим к сборке KPHP из исходников:

# Клонируем репозиторий
git clone https://github.com/VKCOM/kphp.git

# Заходим в папку репозитория
cd kphp

# Переключаемся на ветку
git checkout -b 'pmswga/env_parsing'

# Создаём папку build
mkdir build

# Заходим в папку build
cd build

# Просим cmake по CMakeLists.txt сотоврить нам чудо
cmake ..

# Также просим make сотворить чудо 
make -j6 all

Сборка должна пройти успешно. Что мы получили в итоге? В корне репозитория мы получим новую папку objs и содержимое при ней:

kphp/
├─ build/                      <-- Если вы ещё не вышли из этой папки, то вы тут :)
├─ objs/
│  ├─ bin/
│  │  ├─ kphp2cpp              <-- Наш kphp компилятор. Остальное нас не интересует :(
│  │  ├─ tl2php
│  │  ├─ tl-compiler
│  ├─ flex/
│  │  ├─ libvk-flex-data.o
│  │  ├─ libvk-flex-data.so
│  ├─ generated/*
│  ├─ vkext/
│  │  ├─ modules/
│  │  │  ├─ vkext.so
│  │  ├─ modules7.4/
│  │  │  ├─ vkext.so

Вот мы и собрали последнюю версию KPHP из исходников. Приготовления завершены, теперь можем переходить к добавлению функций.

Добавим новые функции

Кратко обрисую алгоритм добавления новых функций, в такой схеме:

kphp/
├─ builin-functions/_functions.txt  1) Добавить интерфейс функции сюда
├─ runtime/
│  ├─ *.h                           2) Добавить h-файлы с объявлением функций 
│  ├─ *.cpp                         3) Добавить cpp-файлы с реализацией функций
│  ├─ runtime.cmake                 4) Добавить имена cpp-файлов в переменную KPHP_RUNTIME_SOURCES

После чего можно смело запускать make и убедиться, что всё добавлено без ошибок и собирается.

Теперь на нашем конкретном примере:

  1. В файле _functions.txt добавим интерфейсы функций parse_env_file и parse_env_string. Обратите внимание, на то как указываются типы. В целом всё ясно. Принимают строки, возвращают массивы строк.

function parse_env_file($filename ::: string) ::: string[];
function parse_env_string($env_string ::: string) ::: string[];
  1. Добавляем parsing_functions.h со следующим содержимым:

#pragma once

#include "runtime/kphp_core.h"
#include <regex>
#include <fstream>
#include <sstream>

/*
 * Cool functions. А именно функции для очистки строк от ненужного
 */

string clearSpecSymbols(const string &str);

string clearSpaces(const string &str);

string clearEOL(const string &str);

string clearQuotes(const string &str);

string clearString(const string &str);

string trim(const string &str);

/*
 * The best funtions. 
 * А именно функции, которые проверяют строки по регулярным выражениям 
 * и функции, которые возвращают части одной ENV-записи
 */

bool isEnvComment(const string &env_comment);

bool isEnvVar(const string &env_var);

bool isEnvVal(const string &env_val);

string get_env_var(const string &env_entry);

string get_env_val(const string &env_entry);

/*
 * Env file|string parsing functions. 
 * А именно функции будут подставляться в сгенерированном коде
 */

array<string> f$parse_env_file(const string &filename);

array<string> f$parse_env_string(const string &env_string);
  1. Добавляем в parsing_functions.cpp следующий код:

#include "parsing_functions.h"

string clearSpecSymbols(const string &str)
{
  return string(
    std::regex_replace(str.c_str(), std::regex(R"([\t\r\b\v])"), "").c_str()
  );
}

string clearSpaces(const string &str)
{
  return string(
    std::regex_replace(str.c_str(), std::regex(" += +"), "=").c_str()
  );
}

string clearEOL(const string &str)
{
  return string(
    std::regex_replace(str.c_str(), std::regex("\\n"), " ").c_str()
  );
}

string clearQuotes(const string &str)
{
  return string(
    std::regex_replace(str.c_str(), std::regex("[\"\']"), "").c_str()
  );
}

string clearString(const string &str)
{
  string clear_string = clearSpecSymbols(str);
  clear_string = clearSpaces(clear_string);
  clear_string = clearQuotes(clear_string);
  clear_string = trim(clear_string);

  return clear_string;
}

string trim(const string &str)
{
  if (str.empty()) {
    return {};
  }

  size_t s = 0;
  size_t e = str.size()-1;

  while (s != e && std::isspace(str[s])) {
    s++;
  }

  while (e != s && std::isspace(str[e])) {
    e--;
  }

  return str.substr(s, (e-s)+1);
}

/* Example: #APP_NAME=Laravel */
bool isEnvComment(const string &env_comment)
{
  return std::regex_match(
    env_comment.c_str(), 
    std::regex("^#.*", std::regex::ECMAScript)
  );
}

/* Example: APP_NAME */
bool isEnvVar(const string &env_var)
{
  return std::regex_match(
    env_var.c_str(), 
    std::regex("^[A-Z]+[A-Z\\W\\d_]*$", std::regex::ECMAScript)
  );
}

/* Example: Laravel */
bool isEnvVal(const string &env_val)
{
  return std::regex_match(
    env_val.c_str(), 
    std::regex("(.*\n(?=[A-Z])|.*$)", std::regex::ECMAScript)
  );
}

/* Example: APP_NAME=Laravel -> APP_NAME */
string get_env_var(const string &env_entry)
{
  string::size_type pos = env_entry.find_first_of(string("="), 0);

  if (pos == string::npos) {
    return {};
  }

  return env_entry.substr(0, pos);
}

/* Example: APP_NAME=Laravel -> Laravel */
string get_env_val(const string &env_entry)
{
  string::size_type pos = env_entry.find_first_of(string("="), 0);

  if (pos == string::npos) {
    return {};
  }

  pos++;
  
  return env_entry.substr(pos, env_entry.size() - pos);
}

/*
 * Вот собственно реализация parse_env_file
 */
array<string> f$parse_env_file(const string &filename)
{
  if (filename.empty()) {
    return {};
  }

  std::ifstream ifs(filename.c_str());

  if (!ifs.is_open()) {
    php_warning("File not open");
    return {};
  }

  array<string> res(array_size(1, 0, true));

  std::string env_entry;
  while (getline(ifs, env_entry)) {
    string env_entry_copy = clearString(string(env_entry.c_str()));

    if (!env_entry_copy.empty() && !isEnvComment(env_entry_copy)) {
      string env_var = get_env_var(env_entry_copy);

      if (env_var.empty()) {
        php_warning("Invalid env string format %s", env_entry_copy.c_str());
        return {};
      }

      string env_val = get_env_val(env_entry_copy);

      if (isEnvVar(env_var) && isEnvVal(env_val)) {
        res.set_value(env_var, env_val);
      } else {
        php_warning("Invalid env string format %s", env_entry_copy.c_str());
        return {};
      }
    }
  }

  ifs.close();

  return res;
}

/*
 * Вот собственно реализация parse_env_string
 */
array<string> f$parse_env_string(const string &env_string)
{
  if (env_string.empty()) {
    return {};
  }

  array<string> res(array_size(0, 0, true));

  string env_string_copy = clearString(env_string);
  env_string_copy = clearEOL(env_string_copy);

  std::stringstream ss(env_string_copy.c_str());
  std::string str;

  while (getline(ss, str, ' ')) {
    string env_entry = string(str.c_str());

    if (!isEnvComment(env_entry)) {
      string env_var = get_env_var(env_entry);

      if (env_var.empty()) {
        php_warning("Invalid env string format %s", env_entry.c_str());
        return {};
      }

      string env_val = get_env_val(env_entry);

      if (isEnvVar(env_var) && isEnvVal(env_val)) {
        res.set_value(env_var, env_val);
      } else {
        php_warning("Invalid env string format %s", env_entry.c_str());
        return {};
      }
    }
  }

  return res;
}

Как видите, для того чтобы функции реально работали в runtime их нужно называть с префиксом f$ в начале. Ибо именно они будут подставляться в сгенерированном коде (позже сами увидите). В остальном, плодите кода столько, сколько хотите :)

Поговорим о двух важных вещах - это array<string> и string. Это реализация массивов и строк в самом runtime KPHP, а не std`шная (Сам бы Александр Степанов дал бы по рукам за такие методы как set_value и другие).

array<string> позволяет нам делать ассоциативные и обычные массивы.

string позволяет привести себя в int, float, bool, string.

  1. И последнее, добавляем в наш parsing_functions.cpp в cmake файл:

# тут ещё немного cmake 

prepend(KPHP_RUNTIME_SOURCES ${BASE_DIR}/runtime/
        ${KPHP_RUNTIME_DATETIME_SOURCES}
        ${KPHP_RUNTIME_MEMORY_RESOURCE_SOURCES}
        ${KPHP_RUNTIME_MSGPACK_SOURCES}
        ${KPHP_RUNTIME_JOB_WORKERS_SOURCES}
        ${KPHP_RUNTIME_SPL_SOURCES}
        ${KPHP_RUNTIME_PDO_SOURCES}
        ${KPHP_RUNTIME_PDO_MYSQL_SOURCES}
        allocator.cpp
        array_functions.cpp
        bcmath.cpp
        common_template_instantiations.cpp
        confdata-functions.cpp
        confdata-global-manager.cpp
        confdata-keys.cpp
        critical_section.cpp
        curl.cpp
        exception.cpp
        files.cpp
        from-json-processor.cpp
        instance-cache.cpp
        instance-copy-processor.cpp
        inter-process-mutex.cpp
        interface.cpp
        json-functions.cpp
        json-writer.cpp
        kphp-backtrace.cpp
        mail.cpp
        math_functions.cpp
        mbstring.cpp
        memcache.cpp
        memory_usage.cpp
        migration_php8.cpp
        misc.cpp
        mixed.cpp
        mysql.cpp
        net_events.cpp
        on_kphp_warning_callback.cpp
        openssl.cpp
        parsing_functions.cpp                            <-- Наш файл
        php_assert.cpp
        profiler.cpp
        regexp.cpp
        resumable.cpp
        rpc.cpp
        serialize-functions.cpp
        storage.cpp
        streams.cpp
        string.cpp
        string_buffer.cpp
        string_cache.cpp
        string_functions.cpp
        tl/rpc_tl_query.cpp
        tl/rpc_response.cpp
        tl/rpc_server.cpp
        typed_rpc.cpp
        uber-h3.cpp
        udp.cpp
        url.cpp
        vkext.cpp
        vkext_stats.cpp
        ffi.cpp
        zlib.cpp
        zstd.cpp)

# и тут ещё немного cmake 

Ура! Можно компилировать и проверять работоспособность.

Напишем тесты

Однако, никто же не поверит, что у вас всё работает, если вы не напишите для этого тесты... И никто всерьёз вас не воспримет, когда вы без тестов отправите pull-request. Поэтому приступим.

Что надо знать о тестах?

kphp/
├─ tests/
│  ├─ cpp/                        <---- Здесь cpp тесты 
│  │  ├─ compiler                 <---- Тесты компилятора
│  │  ├─ runtime                  <---- Тесты runtime
│  │  │   ├─ *.cpp                1) Добавляем cpp файлы тестов
│  │  │   ├─ runtime-tests.cmake  2) Добавлем имена cpp файлов в переменную RUNTIME_TESTS_SOURCES
│  │  ├─ server                   <---- Тесты сервера
│  ├─ phpt/                       <---- Здесь php тесты
│  │  ├─ my_folder_with_tests     3) Создаём свою папку для тестов
|  |  |   ├─ 001_*.php            4) Создаём свои *.php тесты с нумерацией
|  ├─ kphp_tester.py              5) Запустить ранее написанные тесты с помощью этого скрипта

CPP тесты написаны с помощью gtest и являются обычными unit-тестами.

Однако, php тесты работают следующим образом. Пишется код на php, в том числе с функциями, которые есть только kphp. Затем они запускаются с помощью kphp_tester.py и выполняются как обычный php код, так и kphp. Затем их результаты сравниваются и делается вывод, тест пройден или нет.

Вопрос вот в чём, откуда обычный php узнает о той же функции parse_env_string и parse_env_file, если их в принципе нет? Для этого нужны php-polyfills (в своём роде заглушки). Далее всё увидите сами.

Для запуска cpp тестов:

# Перейдём в папку build
cd build

# Соберём всё ещё раз
make -j6 all

# Запустим тесты
ctest -j6

В результате все тесты должны выполниться успешно.

Для запуска php тестов нужно проделать следующее:

# Сначала скачать php-polyfiils
git clone https://github.com/VKCOM/kphp-polyfills.git

# Зайдём в папку kphp-polyfiils
cd kphp-polyfills

# Установим пакеты и сгенерируем autoload 
composer install

# Зададим переменную окружения KPHP_TESTS_POLYFIILS_REPO 
export KPHP_TESTS_POLYFILLS_REPO=$(pwd)

Такая многоходовочка даёт нам следующее, что при запуске php тестов, они будут обращаться по этому пути подтягивать "заглушки".

И вот теперь, действительно, для запуска php тестов:

# Запуск всех тестов
tests/kphp_tester.py

# Запуск конкретного теста
tests/kphp_tester.py 001_*.php

Вуаля!

CPP тесты

Теперь к нашим барашкам (к f$parse_env_string и f$parse_env_file). Добавим parsing-functions-tests.cpp со следующим кодом:

#include <gtest/gtest.h>
#include "runtime/parsing_functions.h"

TEST(parsing_functions_test, test_isEnvComment) {
  ASSERT_FALSE(isEnvComment(string("")));
  ASSERT_FALSE(isEnvComment(string("APP_NAME=Laravel")));
  ASSERT_TRUE(isEnvComment(string("#APP_NAME=Laravel")));
}

TEST(parsing_functions_test, test_isEnvVar) {
  ASSERT_FALSE(isEnvVar(string("")));
  ASSERT_FALSE(isEnvVar(string("!APP_NAME")));
  ASSERT_TRUE(isEnvVar(string("APP_NAME")));
}

TEST(parsing_functions_test, test_isEnvVal) {
  ASSERT_TRUE(isEnvVal(string("")));
  ASSERT_TRUE(isEnvVal(string("true")));
  ASSERT_TRUE(isEnvVal(string("local")));
  ASSERT_TRUE(isEnvVal(string("80")));
  ASSERT_TRUE(isEnvVal(string("127.0.0.1")));
  ASSERT_TRUE(isEnvVal(string("https://localhost")));
  ASSERT_TRUE(isEnvVal(string("\'This is my env val\'")));
  ASSERT_TRUE(isEnvVal(string("\"This is my env val\"")));
}

TEST(parsing_functions_test, test_get_env_var) {
  string str("APP_NAME=Laravel");
  string env_var = get_env_var(str);

  ASSERT_STREQ(string("").c_str(), get_env_var(string("")).c_str());
  ASSERT_STREQ("APP_NAME", env_var.c_str());
  ASSERT_EQ(strlen("APP_NAME"), env_var.size());
}

TEST(parsing_functions_test, test_get_env_val) {
  string str("APP_NAME=Laravel");
  string env_val = get_env_val(str);

  ASSERT_STREQ("Laravel", env_val.c_str());
  ASSERT_EQ(string("Laravel").size(), env_val.size());
}

/*
 * Тестируем функцию parse_env_string
 */
TEST(parsing_functions_test, test_parse_env_string) {
  string env_string = string(R"(APP_NAME=Laravel APP_ENV=local #APP_KEY=base64:mtlb8hldh5hZ0GlLzbhInsV531MSylspRI4JsmwVal8= APP_DEBUG=true T1="my" T2='my')");

  array<string> res(array_size(0, 0, true));

  res = f$parse_env_string(env_string);
  ASSERT_EQ(res.size().string_size, 5);

  ASSERT_TRUE(res.has_key(string("APP_NAME")));
  ASSERT_STREQ(res.get_value(string("APP_NAME")).c_str(), string("Laravel").c_str());
  ASSERT_TRUE(res.has_key(string("APP_ENV")));
  ASSERT_STREQ(res.get_value(string("APP_ENV")).c_str(), string("local").c_str());
  ASSERT_TRUE(res.has_key(string("APP_DEBUG")));
  ASSERT_STREQ(res.get_value(string("APP_DEBUG")).c_str(), string("true").c_str());
  ASSERT_TRUE(res.has_key(string("T1")));
  ASSERT_STREQ(res.get_value(string("T1")).c_str(), string("my").c_str());
  ASSERT_TRUE(res.has_key(string("T2")));
  ASSERT_STREQ(res.get_value(string("T2")).c_str(), string("my").c_str());
}

/*
 * Тестируем функцию parse_env_file
 */
TEST(parsing_functions_test, test_parse_env_file) {
  std::ofstream of(".env.example");

  if (of.is_open()) {
    of << "APP_NAME=Laravel "<< std::endl;
    of << "APP_ENV=local" << std::endl;
    of << "APP_DEBUG=true" << std::endl;
    of.close();
  }

  array<string> res(array_size(0, 0, true));

  res = f$parse_env_file(string("file not found"));
  ASSERT_EQ(res.size().string_size, 0);

  res = f$parse_env_file(string(".env.example"));
  ASSERT_TRUE(res.has_key(string("APP_NAME")));
  ASSERT_STREQ(res.get_value(string("APP_NAME")).c_str(), string("Laravel").c_str());
  ASSERT_TRUE(res.has_key(string("APP_ENV")));
  ASSERT_STREQ(res.get_value(string("APP_ENV")).c_str(), string("local").c_str());
  ASSERT_TRUE(res.has_key(string("APP_DEBUG")));
  ASSERT_STREQ(res.get_value(string("APP_DEBUG")).c_str(), string("true").c_str());
}

Теперь их можно запустить и убедиться, что они успешно выполняются.

Результаты cpp тестов
Результаты cpp тестов

PHP тесты

Вспоминаем про php-polyfills, идём в соседний репозиторий, в корне которого находим файл kphp_polyfiils.php добавляем в него следующий код:

#ifndef KPHP

# тут много какого-то кода

#region env parsing

/**
 * parse_env_string return associative array by parsed string
 * 
 */
function parse_env_string(string $env_string) {
  if (empty($env_string)) {
    return [];
  }

  $get_env_entry = function ($env_string) {
    $env_entry = explode('=', $env_string, 2);

    if (count($env_entry) !== 2) {
      die("parse error\n");
    }

    return [
      'env_var' => trim($env_entry[0]),
      'env_val' => trim($env_entry[1])
    ];
  };

  $lines = explode(' ', $env_string);
  $env = [];


  foreach ($lines as $line) {
    $env_entry = $get_env_entry($line);

    $env[trim($env_entry['env_var'])] = trim($env_entry['env_val']); 
  }

  return $env;
}

/**
 * parse_env_string return associative array by parsed file
 * 
 */
function parse_env_file(string $filename) {

  if (empty($filename)) {
    return [];
  }

  if (!is_file($filename)) {
    return [];
  }

  if (!file_exists($filename)) {
    return [];
  }

  $env_string = file_get_contents($filename);

  return parse_env_string($env_string);
}

#endregion

#endif

По существу мы реализовали парсинга env строк и файлов в формате ENV. Что собственно и можно было сделать изначально, создав даже целую либу (kenv).

Теперь же создадим по пути tests/phpt/parsing/001_parsing_env.php и добавим в него следующий код.

@ok                                              # <-- Тег обозначает должен ли этот код компилироваться на KPHP
<?php

require_once 'kphp_tester_include.php';          # <-- Подключаем php-polyfills

function test_parse_env_string_empty() {         # <-- Сами "тесты"
    var_dump(parse_env_string(''));
}

function test_parse_env_string_one() {
    var_dump(parse_env_string('APP_NAME=Laravel'));
}

function test_parse_env_string_many() {
    var_dump(parse_env_string('APP_NAME=Laravel APP_DEBUG=true APP_ENV=local'));
}

function test_parse_env_file_empty() {
    var_dump(parse_env_file(''));
}

function test_parse_env_file_not_found_empty() {
    var_dump(parse_env_file('file not found'));
}

function test_parse_env_file_one() {
    $filename = tempnam("", "wt");
    $fp = fopen($filename, "a");
    fwrite($fp, "APP_NAME=Laravel");
    fclose($fp);

    var_dump(parse_env_file($filename));
}

function test_parse_env_file_many() {
    $filename = tempnam("", "wt");
    $fp = fopen($filename, "a");
    fwrite($fp, "APP_NAME=Laravel");
    fwrite($fp, "APP_DEBUG=true");
    fwrite($fp, "APP_ENV=local");
    fclose($fp);

    var_dump(parse_env_file($filename));
}

test_parse_env_string_empty();                   # <-- Вызов функций
test_parse_env_string_one();
test_parse_env_string_many();

test_parse_env_file_empty();
test_parse_env_file_not_found_empty();
test_parse_env_file_one();
test_parse_env_file_many();

Запустим написанный тест:

tests/kphp_tester.py 001_parse_env

И вот, заветное слово passed.

Результаты php тестов
Результаты php тестов

Проверяем работоспособность

Итого, вот какие изменения мы внесли, чтобы реализовать функции parse_env_file и parse_env_string.

# Репозиторий kphp
kphp/
├─ builin-functions/_functions.txt       <-- Добавили интерфейсы parse_env_file и parse_env_string
├─ runtime/
│  ├─ parsing_functions.h                <-- Добавили объявление функций 
│  ├─ parsing_functions.cpp              <-- Добавили реализацию функций
│  ├─ runtime.cmake                      <-- Добавили parsing_functions.cpp в переменную KPHP_RUNTIME_SOURCES
├─ tests/
│  ├─ cpp/
│  │  ├─ runtime
│  │  │   ├─ parsing-functions-tests.cpp <-- Добавили cpp тесты
│  │  │   ├─ runtime-tests.cmake         <-- Добавили parsing-functions-tests.cpp в переменную RUNTIME_TESTS_SOURCES
│  ├─ phpt/
│  │  ├─ parsing                         <-- Создали папку parsing
|  |  |   ├─ 001_parse_env.php           <-- Добавили php тесты

# Репозиторий kphp-polyfills
kphp-polyfills/
├─ kphp_polyfills.php                    <-- Добавили php`шные реализации parse_env_file и parse_env_string

Теперь можем посмотреть на наши плоды. Создадим index.php и напишем следующий код:

<?php
    $env_string = "APP_NAME=Laravel APP_DEBUG=true APP_ENV=local";

    $res = parse_env_string($env_string);

    print_r('<pre>');
    print_r($res);
    print_r('</pre>');

    $res = parse_env_file('.env.example');

    print_r('<pre>');
    print_r($res);
    print_r('</pre>');

Скомпилируем его:

./kphp2cpp index.php

Запустим и получим следующее:

./kphp_out/server -H 8000 -f 2
                   ^       ^
                   |       |
                   |     Поднимаем двух рабочих работу работать
                   |
                 Поднимаем localhost:8000
Результат работы скомпилированного index.php
Результат работы скомпилированного index.php

А вот что сгенерировано на С++:

//crc64:912a10e8beed9098
//crc64_with_comments:bc9f187534f26ce2
#include "runtime-headers.h"
#include "o_6/src_indexbccbb8a09559268e.h"
extern string v$env_string;

extern array< string > v$res;

extern bool v$src_indexbccbb8a09559268e$called;

extern string v$const_string$us3e8066aa5eeccc54;

extern string v$const_string$us531c70314bd2d991;

extern string v$const_string$usd04f12c090cf2e22;

extern string v$const_string$use301963cf43e4d3a;

//source = [index.php]
//3:     $env_string = "APP_NAME=Laravel APP_DEBUG=true APP_ENV=local";
Optional < bool > f$src_indexbccbb8a09559268e() noexcept  {
  v$src_indexbccbb8a09559268e$called = true;
  v$env_string = v$const_string$us3e8066aa5eeccc54;
//4: 
//5:     $res = parse_env_string($env_string);
  v$res = f$parse_env_string(v$env_string);                       <-- Вот вызов нашей функции 
//6: 
//7: print_r('<pre>');
  f$print_r(v$const_string$usd04f12c090cf2e22);
//8:     print_r($res);
  f$print_r(v$res);
//9: print_r('</pre>');
  f$print_r(v$const_string$us531c70314bd2d991);
//10: 
//11: 
//12:     $res = parse_env_file('.env.example');
  v$res = f$parse_env_file(v$const_string$use301963cf43e4d3a);    <-- И вот вызов нашей функции  
//13: 
//14: print_r('<pre>');
  f$print_r(v$const_string$usd04f12c090cf2e22);
//15:     print_r($res);
  f$print_r(v$res);
//16: 
  f$print_r(v$const_string$us531c70314bd2d991);
  return Optional<bool>{};
}

Заключение

Спасибо всем кто дочитал до конца. Надеюсь что поставленная цель выполнена.

Если вы хотите присоединиться к сообществу KPHP, то добро пожаловать в чат.

По изложенной теме:

Дополнительные ссылки:

Комментарии (1)


  1. quasilyte
    24.11.2022 20:25
    +4

    Небольшое предостережение для читателей.

    Внутри рантайма KPHP использовать что-то, что использует стандартный аллокатор ("системный"), может быть чревато утечками памяти.

    Например, исполнение кода может быть прервано сигналом при таймауте. В этом случае управление перейдёт обработчику сигнала, который потом через getcontext переключится в сетевой контекст и... исполнение прерванного кода никогда не продолжится. Деструкторы всяких std::regex вызваны не будут.

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