Всем доброго дня, уважаемые читатели. В данной статье вы узнаете как добавить новые функции в runtime KPHP.
Совсем вкратце расскажу о том, что такое KPHP и на примере какой задачи вы узнаете о расширении возможностей runtime KPHP.
ДИСКЛЕЙМЕР: статья является гайдом. Бездумно лезть в runtime не стоит.
О KPHP
KPHP - компилируемый PHP. Де-факто PHP, транслированный в C++. В свою очередь это увеличивает производительность исходного кода как минимум потому что скомпилировано в бинарный файл.
О нашей задаче
Задача, которую мы решим, заключается в следующем - реализовать две функции для парсинга строк и файлов в формате ENV. Исключительно для демонстрации всех этапов добавления новых функций в runtime.
Приступаем
Итак, перейдём к нашему плану:
Подготовим всё необходимое
Добавим новые функции
Напишем тесты
Проверим работоспособность
Подготовим всё необходимое
Я работаю под 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 и убедиться, что всё добавлено без ошибок и собирается.
Теперь на нашем конкретном примере:
В файле _functions.txt добавим интерфейсы функций parse_env_file и parse_env_string. Обратите внимание, на то как указываются типы. В целом всё ясно. Принимают строки, возвращают массивы строк.
function parse_env_file($filename ::: string) ::: string[];
function parse_env_string($env_string ::: string) ::: string[];
Добавляем 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);
Добавляем в 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.
И последнее, добавляем в наш 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());
}
Теперь их можно запустить и убедиться, что они успешно выполняются.
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.
Проверяем работоспособность
Итого, вот какие изменения мы внесли, чтобы реализовать функции 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
А вот что сгенерировано на С++:
//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, то добро пожаловать в чат.
По изложенной теме:
Дополнительные ссылки: