Как два байта переслать: контрибьютим в KPHP
Что такое KPHP
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/cf2/d1a/cba/cf2d1acba5bd8c3341d9e1c47ac3f2b3.png)
KPHP - компилятор для PHP. Он конвертирует PHP код в код на C++, компилируя который, ускоряет производительность в десятки раз. Это open-source проект, созданный ВКонтакте. Благодаря ему собирается огромный монолит ВКонтакте на 9 миллионов строк PHP кода в обычный бинарник, запуская который вы локально поднимаете полноценный ВКонтакте.
Цель
Я расскажу про добавление новых функций в runtime KPHP. Точнее про тернистую дорогу на пути.
План
- Подготовка
- runtime
- добавление функций
- типы
- флаги
- изменение подключаемых библиотек
3. Тесты
- cpp тесты
- php тесты
4. pull_request
Подготовка
Устанавливаем kphp из репозитория
runtime
Добавление функций
В качестве примера возьмем ситуацию, когда нам нужно реализовать функцию mb_check_encoding
из php. Первым делом идем в доки:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/1ff/ea0/5df/1ffea05df5db38c67c88ee98c55389f8.png)
Узнаем, что функция проверяет кодировку строки или массива строк. Массив строк обрабатывается рекурсивно, так что сфокусируемся на функции, работающей для строки. Теперь идем в код php смотреть как работает функция в php:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/1c6/764/3cc/1c67643cc10fe32cfa67f0bc0c1c4375.png)
Идем смотреть в сишный файл, чтобы протрейсить реализацию:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/cf9/f80/e75/cf9f80e754a8015d438d3d9d5271a826.png)
Видим, что конвертация идет через входной параметр типа mbfl_encoding
. Ищем:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/eef/1cc/d5d/eef1ccd5d9d051e46373085be89b9183.png)
Сразу видим, что эта структура - часть какой-то библиотеки libmbfl
, которая видимо и занимается конвертацией. Ищем ее в интернете и находим репозиторий:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/d33/12f/9fb/d3312f9fb2e44e4d19f3dff364bdbeea.png)
Отлично, мы поняли, что нам нужно лишь использовать эту библиотеку, которая возьмет всю конвертацию на себя, а мы просто допишем интерфейс. Скачиваем эту библиотеку, устанавливаем, изучаем ее и пишем реализацию в любом сишном файлике (не внутри kphp, сейчас нам надо, чтобы просто работало). Для проверки кодировки нужна функция конвертации mb_convert_encoding
, которую мы тоже реализуем:
#include <libmbfl/mbfl/mbfilter.h> mbfl_string *mb_convert_encoding(const char *str, const char *to, const char *from) { int len = strlen(str); enum mbfl_no_encoding from_encoding, to_encoding; mbfl_buffer_converter *convd = NULL; mbfl_string _string, result, *ret; /* from internal to mbfl */ from_encoding = mbfl_name2no_encoding(from); to_encoding = mbfl_name2no_encoding(to); /* init buffer mbfl strings */ mbfl_string_init(&_string); mbfl_string_init(&result); _string.no_encoding = from_encoding; _string.len = len; _string.val = (unsigned char*)str; /* converting */ convd = mbfl_buffer_converter_new(from_encoding, to_encoding, 0); ret = mbfl_buffer_converter_feed_result(convd, &_string, &result); mbfl_buffer_converter_delete(convd); /* fix converting with multibyte encodings */ if (len % 2 != 0 && ret->len % 2 == 0 && len < ret->len) { ret->len++; ret->val[ret->len-1] = 63; } return ret; } bool mb_check_encoding(const char *value, const char *encoding) { /* init buffer mbfl strins */ mbfl_string _string; mbfl_string_init(&_string); _string.val = (unsigned char*)value; _string.len = strlen((char*)value); /* from internal to mbfl */ const mbfl_encoding *enc = mbfl_name2encoding(encoding); /* get all supported encodings */ const mbfl_encoding **encs = mbfl_get_supported_encodings(); int len = sizeof(**encs); /* identify encoding of input string */ /* Warning! String can be represented in different encodings, so check needed */ const mbfl_encoding *i_enc = mbfl_identify_encoding2(&_string, encs, len, 1); /* perform convering */ const char *i_enc_str = (const char*)mb_convert_encoding(value, i_enc->name, enc->name)->val; const char *enc_str = (const char*)mb_convert_encoding(i_enc_str, enc->name, i_enc->name)->val; /* check equality */ /* Warning! strcmp not working, because of different encodings */ bool res = true; for (int i = 0; i < strlen(enc_str); i++) if (enc_str[i] != value[i]) { res = false; break; } free((void*)i_enc_str); free((void*)enc_str); return res; }
Функции работают, но только для сишных строк, теперь нам нужно перенести эти локально работающие функции в runtime kphp. Для этого есть 4 шага:
- Добавить php-интерфейс (txt)
- Добавить код с интерфейсом (h)
- Добавить код с реализацией (cpp)
- Добавить файлы в сборку (cmake)
Добавить php-интерфейс (txt)
Для того, чтобы правильно перенести php интерфейс, нужно знать про типы в kphp тут
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/843/d34/ddc/843d34ddce463da220aca65033b63272.png)
Итак, открываем файл builtin-functions/_functions.txt
. И в самый конец добавляем переделанный интерфейс из php. Например в php mb_check_encoding имеет следующий интерфейс:
function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool
в kphp это будет:
function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool;
Аналогично для mb_convert_encoding в php:
function mb_convert_encoding(array|string $string, string $to_encoding, array|string|null $from_encoding = null): array|string|false
в kphp:
function mb_convert_encoding(array|string $string, string $to_encoding, array|string|null $from_encoding = null): array|string|false;
Вот и все, теперь нужно добавить код.
Добавить код с интерфейсом (h)
Все модули рантайма находятся в папке runtime
. Наши функции являются частью расширения mbstring
для php. Оказывается уже есть файлик mbstring.h
. Давайте откроем и посмотрим:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/dfa/aa4/640/dfaaa46404faa45cda23d78de926dfed.png)
Оказывается mb_check_encoding уже реализована, странно. Почему имя функций начинается на f$
? Так kphp понимате какие функции искать в builtin-functions/_functions.txt
. Посмотрим cpp файл:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/bf4/4a7/19f/bf44a719f61573871064eb7f3996a1ea.png)
Хм, функция уже реализована, но все работает только для двух кодировок (UTF-8 и Windows-1251). Ничего страшного, теперь мы покажем им все кодировки! (о последствиях расскажу в конце). Видим, что входными параметрами являются переменные типа string
. Ловушка! Это не string из I/O! Это string из kphp! Где про нее почитать? Все типы включаются из runtime/kphp_core.h
. Смотрим внутри:
. Поменяем интерфейс mb_check_encoding
:
bool f$mb_check_encoding(const string &value, const string &encoding);
аналогично для mb_convert_encoding
:
string f$mb_convert_encoding(const string &str, const string &to_encoding, const string &from_encoding);
Отлично, идем дальше.
Добавить код с реализацией (cpp)
В предыдущем шаге мы нашли все типы, так что можно посмотреть в string.inl
и узнать, как доставать из нее const char *
, который нам уже знаком или как создать string из const char *
. Теперь перепишем функцию mb_check_encoding
:
bool f$mb_check_encoding(const string &value, const string &encoding) { const char *c_encoding = encoding.c_str(); const char *c_value = value.c_str(); // ... }
Теперь осталось сделать аналогичное для функции mb_convert_encoding
:
string f$mb_convert_encoding(const string &str, const string &to_encoding, const string &from_encoding) { const char *c_string = s.c_str(); const char *c_to_encoding = to_encoding.c_str(); const char *c_from_encoding = from_encoding.c_str(); // ... return string((const char*)ret->val, ret->len); }
Добавить файлы в сборку (cmake)
Идем в runtime/runtime.cmake
. Все cpp должны оказаться в KPHP_RUNTIME_SOURCES
. Но для удобства можно группировать их. В нашем случае mbstring.cpp
уже включен в сборку. Но, можно сделать по другому:
prepend(KPHP_RUNTIME_MBSTRING_SOURCES mbstring.cpp)
тогда можно в KPHP_RUNTIME_SOURCES
включать не отдельный файл mbstring.cpp
, а KPHP_RUNTIME_MBSTRING_SOURCES
:
prepend(KPHP_RUNTIME_SOURCES ${BASE_DIR}/runtime/ ${KPHP_RUNTIME_MBSTRING_SOURCES} ...
Фух, теперь точно все. Билдим!
mkdir build && cd build && cmake .. -DDOWNNLOAD_MISSING_LIBRARIES=On && make -j$(nproc)
Создаем test.php:
echo mb_check_encoding("Hello World", "UTF-8");
Запускаем!
./objs/bin/kphp2cpp -M test.php && ./kphp_out/cli
Ура! Мы успешно добавили новые функции в рантайм kphp!
!!! Важно !!!
Не все так просто. Мы очень быстро их добавили (лишь бы работало). Теперь когда мы убедились, что все работает нужно переделать некоторые моменты.
- И было бы хорошим тоном разграничивать логичное поведение функции и ее поведение в php. Например в php
mb_convert_encoding
если при конвертации есть символы, которых нет в выходной кодировке, то их байты заменяются на 0x63 ('?' в ASCII). Это поведение уникально, поэтому лучше ее реализовать вf$
функции, а всю логику вынести в обычную функцию. - Нужны другие типы -
mb_check_enсoding
в качестве параметра данных может принимать массив строк илиnull
, нужно это предусмотреть. Причем поведение функции должно быть идентично php (даже если поведение в php нелогично). Аналогично дляmb_convert_encoding
, которая может и принимать и возвращать строку или массив строк. mbstring
это расширение kphp, которое вphp-src
, собирается и подключается только если указан определенный флаг при компиляции. Нужно сделать и это.- Обработка разных версий php.
Хороший тон
- Используйте
const &
для входных параметров (если они не меняются) - Разграничение логики и поведения как в php (используйте
static
):
static bool check_enсoding(const char *value, const char *encoding) { // ... } bool f$mb_check_encoding(const string &value, const string &encoding) { const char *c_encoding = encoding.c_str(); const char *c_value = value.c_str(); bool res = check_enсoding(c_value, c_encoding); // process differences in php return res }
- Используйте
noexcept
если функция не вызывает исключений:
static bool check_enсoding(const char *value, const char *encoding) { // ... } bool f$mb_check_encoding(const string &value, const string &encoding) noexcept { const char *c_encoding = encoding.c_str(); const char *c_value = value.c_str(); bool res = check_enсoding(c_value, c_encoding); // process differences in php return res }
Типы
mb_check_encoding
в качестве переменной данных может принимать string|array|null
(судя по php-интерфейсу). В C++ мы не можем записывать типы так, поэтому заменяем тип на mixed
(смешанный). Про mixed
можно почитать аналогично типу string
. Параметр кодировки mb_check_encoding
(судя по php-интерфейсу) имеет тип ?string
, что нужно заменить на Optional<string>
. Про Optional
можно почитать аналогично типу string
. Главная информация в том, что mixed
мы можем проверять на любой тип через .is_<тип>
и попытаться привести к любому типу через .to_<тип>
. Из Optional
можно достать значение через .val()
.
static bool check_enсoding(const char *value, const char *encoding) { // ... } bool f$mb_check_encoding(const mixed &value, const Optional<string> &encoding) noexcept { if (encoding.is_null() || value.is_null()) return 1; const char *c_encoding = encoding.val().c_str(); if (value.is_string()) { // ... } if (value.is_array()) { // ... } return 1; }
Расширение
mbstring
это расширение php, его нужно включить через флаг при сборке с cmake
. Пусть флагом будет - MBFL
Чтобы это сделать нужно всего 3 шага:
cmake/external-libraries.cmake
- добавить флаг в cmake и пробросить его в компилятор:
option(DOWNLOAD_MISSING_LIBRARIES "download and build missing libraries if needed" OFF) option(MBFL "build mbstring" OFF) # ...
runtime/runtime.cmake
- включить файлы в сборку по флагу
# ... if (MBFL) prepend(KPHP_RUNTIME_MBSTRING_SOURCES mbstring.cpp) endif() # ... prepend(KPHP_RUNTIME_SOURCES ${BASE_DIR}/runtime/ ${KPHP_RUNTIME_MBSTRING_SOURCES} # ...
В моем случае некоторые функции из mbstring.cpp
использовались в самом kphp (не в рантайме). Это значит, что мне нужно всегда собирать mbstring.cpp
, но не собирать большинство функции по флагу. Чтобы это сделать нужно 3 шага:
cmake/external-libraries.cmake
- добавить флаг в cmake и пробросить его в компилятор:
option(DOWNLOAD_MISSING_LIBRARIES "download and build missing libraries if needed" OFF) option(MBFL "build mbstring" OFF) # ...
compiler/compiler-settings.cpp
- пробросить флаг дальше из компилятора в рантайм:
void CompilerSettings::init() { // ... std::stringstream ss; #ifdef MBFL ss << " -DMBFL "; #endif // ...
runtime/mbstring.*
- через#ifdef
отслеживать флаг из cmake:
#ifdef MBFL bool f$mb_check_encoding(const mixed &value, const Optional<string> &encoding) noexcept; #endif
Теперь при сборке можно указать:
cmake .. -DMBFL=On
Обработка разных версий php
В разработке...
Изменение подключаемых библиотек
Все работает. Библиотека линкуется. Но было бы хорошо, если не нужно было бы ее устанавливать самому, чтобы kphp по флагу MBFL
скачивал библиотеку, собирал и линковал. Тогда не придется писать новые доки для установки библиотеки + можно заморозить версию библиотеки на форке. Разберем мой форк libmbfl
. Мне не повезло с библиотекой, она с 1992 года не обновляется, нет документации, нет статей с ее использование, нет комментариев к коду и билдится она по-своему. Чтобы kphp сам скачивал, билдил и линковал библиотеку нужно выполнить шагов:
- Убрать сборку 'по-своему'
- Создать таргеты, которые ищет kphp
- Включить сборку и линковку в
cmake
- Добавить библиотеку в импорт компилятора
Убрать сборку 'по-своему'
Если в сборке библиотеку есть всякие ./configure
, ./buildconf
и прочее - переносим это в cmake
. Нам нужно, чтобы библиотека собиралась только через cmake
+ make
. В моем случае был и ./configure
и ./buildconf
, которые отвечали проверку зависимостей и сборку (каждый файл не собирался в своей папке, его собирали рядом с зависимостями). Я вручную починил все зависимости, чтобы каждый файл могу собираться по своему пути и добавил проверку зависимостей в cmake.
Создать таргеты, который ищет kphp
Когда kphp будет билдить библиотеку он будет ожидать специальные таргеты, чтобы знать какой код скопировать в include
и где находится собранная библиотека.
Название библиотеки libmbfl
, тогда kphp будет ожидать следующие таргеты:
- libmbfl
- libmbfl_ALL_SOURCES
- libmbfl-includecopy
Разберем по отдельности каждый, начнем с libmbfl
.
libmbfl
project(libmbfl VERSION 1.0.0 DESCRIPTION "libmbfl" HOMEPAGE_URL "https://github.com/andreylzmw/libmbfl") # ... set_target_properties(libmbfl PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${OBJS_DIR}) # ... set_target_properties(libmbfl PROPERTIES PUBLIC_HEADER "libmbfl/mbfilter.h") # ... add_dependencies(libmbfl libmbfl-includecopy) # ... install(TARGETS libmbfl COMPONENT KPHP LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/kphp/liblibmbfl") # ... add_library(libmbfl STATIC ${libmbfl_ALL_SOURCES}) add_library(vk::libmbfl ALIAS libmbfl)
libmbfl_ALL_SOURCES
set(libmbfl_ALL_SOURCES filters/mbfilter_iso2022_jp_ms.c filters/mbfilter_iso8859_14.c filters/mbfilter_iso8859_6.c filters/mbfilter_sjis_mobile.c filters/mbfilter_koi8r.c filters/mbfilter_cp850.c filters/mbfilter_euc_jp.c # ... add_library(libmbfl STATIC ${libmbfl_ALL_SOURCES}) # ...
libmbfl-includecopy
add_dependencies(libmbfl libmbfl-includecopy) # ... add_custom_command( COMMAND cp -R ${CMAKE_CURRENT_SOURCE_DIR}/mbfl ${CMAKE_CURRENT_SOURCE_DIR}/include/kphp/libmbfl OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/include/kphp/libmbfl/mbfl) add_custom_command( COMMAND cp -R ${CMAKE_CURRENT_SOURCE_DIR}/filters ${CMAKE_CURRENT_SOURCE_DIR}/include/kphp/libmbfl OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/include/kphp/libmbfl/filters) add_custom_command( COMMAND cp -R ${CMAKE_CURRENT_SOURCE_DIR}/nls ${CMAKE_CURRENT_SOURCE_DIR}/include/kphp/libmbfl OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/include/kphp/libmbfl/nls) # ... add_custom_target(libmbfl-includecopy ALL DEPENDS include/kphp/libmbfl/mbfl/mbfilter.h) # ...
Включить сборку и линковку в cmake. cmake/external-libraries.cmake:
# ... FetchContent_Declare(libmbfl GIT_REPOSITORY https://github.com/andreylzmw/libmbfl) FetchContent_MakeAvailable(libmbfl) include_directories(${libmbfl_SOURCE_DIR}/include) add_definitions(-DLIBMBFL_LIB_DIR="${libmbfl_SOURCE_DIR}/objs") add_link_options(-L${libmbfl_SOURCE_DIR}/objs) # ...
Добавить библиотеку в импорт компилятора
Чтобы kphp мог линковать нужную нам библиотеку libmbfl
нужно добавить ее (без lib
в начале) в external_static_libs
в compiler/compiler-settings.cpp
:
std::vector<vk::string_view> external_static_libs{..., "mbfl"};
Тесты
cpp тесты
cpp тесты это тесты реализации, запускаются через сtest. В них мы заранее должны знать что должна вернуть функция и просто проверять. Добавить их просто. Для каждой функции отдельный тест. cpp тесты находятся в tests/cpp/runtime/
. Чтобы их добавить создаем mbstring-test.cpp
внутри tests/cpp/runtime/
:
#include <gtest/gtest.h> #include "runtime/mbstring/mbstring.h" // Note: all tests written for php8.3 /* TEST ASCII DATA */ const string &ASCII_STRING = string("sdf234"); const array<string> &ASCII_ARRAY = array<string>::create(string("234"), string("wef")); const string &ASCII_ENCODING = string("ASCII"); /* TEST WINDOWS1251 DATA */ const string &WINDOWS1251_STRING = string("sdfw3234ыва"); const array<string> &WINDOWS1251_ARRAY = array<string>::create(string("234"), string("wef"), string("ыва")); const string &WINDOWS1251_ENCODING = string("Windows-1251"); /* TEST UTF8 DATA */ const string &UTF8_STRING = string("Өd5па"); const array<string> &UTF8_ARRAY = array<string>::create(string("sdыа"), string("wef"), string("ыва"), string("Ө"), string("Ä°nanç Esasları")); const string &UTF8_ENCODING = string("UTF-8"); #ifdef MBFL TEST(mbstring_test, test_mb_check_encoding) { /* TEST ASCII ENCODING */ ASSERT_TRUE(f$mb_check_encoding(ASCII_STRING, ASCII_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(ASCII_ARRAY, ASCII_ENCODING)); ASSERT_FALSE(f$mb_check_encoding(WINDOWS1251_STRING, ASCII_ENCODING)); ASSERT_FALSE(f$mb_check_encoding(WINDOWS1251_ARRAY, ASCII_ENCODING)); ASSERT_FALSE(f$mb_check_encoding(UTF8_STRING, ASCII_ENCODING)); ASSERT_FALSE(f$mb_check_encoding(UTF8_ARRAY, ASCII_ENCODING)); /* TEST WINDOWS1251 ENCODING */ ASSERT_TRUE(f$mb_check_encoding(WINDOWS1251_STRING, WINDOWS1251_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(WINDOWS1251_ARRAY, WINDOWS1251_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(ASCII_STRING, WINDOWS1251_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(ASCII_ARRAY, WINDOWS1251_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(UTF8_STRING, WINDOWS1251_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(UTF8_ARRAY, WINDOWS1251_ENCODING)); /* TEST UTF8 ENCODING */ ASSERT_TRUE(f$mb_check_encoding(UTF8_STRING, UTF8_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(UTF8_ARRAY, UTF8_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(ASCII_STRING, UTF8_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(ASCII_ARRAY, UTF8_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(WINDOWS1251_STRING, UTF8_ENCODING)); ASSERT_TRUE(f$mb_check_encoding(WINDOWS1251_ARRAY, UTF8_ENCODING)); } TEST(mbstring_test, test_mb_convert_encoding) { /* input is string, encoding is string */ ASSERT_STREQ(f$mb_convert_encoding(ASCII_STRING, WINDOWS1251_ENCODING, ASCII_ENCODING).to_string().c_str(), ASCII_STRING.c_str()); ASSERT_STREQ(f$mb_convert_encoding(UTF8_STRING, UTF8_ENCODING, UTF8_ENCODING).to_string().c_str(), UTF8_STRING.c_str()); /* input is string, encoding is array of strings */ ASSERT_STREQ(f$mb_convert_encoding(ASCII_STRING, {WINDOWS1251_ENCODING, UTF8_ENCODING}, ASCII_ENCODING).to_string().c_str(), {ASCII_STRING.c_str(), UTF8_STRING.c_str()}); } #endif
Сборка тестов находится в tests/cpp/runtime/runtime-tests.cmake
, нужно добавить cpp файл в RUNTIME_TESTS_SOURCES
:
prepend(RUNTIME_TESTS_SOURCES ${BASE_DIR}/tests/cpp/runtime/ # ... mbstring-test.cpp # ...
Теперь когда мы запустим ctest:
cd build && ctest -j$(nproc)
Мы увидим:
... 242/261 Test #242: mbstring_test.test_mb_check_encoding ....................................... Passed 0.24 sec 243/261 Test #243: mbstring_test.test_mb_convert_encoding ..................................... Passed 0.23 sec ...
php тесты
php тесты это тесты, которые сравнивают результат работы php и kphp, запускаются через tests/kphp_tester.py
. В них мы пишем php код который должен вести себя ожидаемого в kphp. php тесты находятся в tests/phpt/
. Чтобы их добавить создаем папку mbstring
внутри tests/phpt/
. Внутри создаем отдельный файл для каждой функции в формате 001_*****.php
, 002_*****.php
и тд, где *****
- названия функций. В нашем случае:
tests/phpt/mbstring/001_mb_convert_encoding.php
:
@ok <?php function mb_convert_encoding_not_utf_8() { $str = "Somebody was told: F$#$)UT#*HD]"; var_dump(mb_convert_encoding($str, "UTF-7", "EUC-JP")); } # If mbstring.language is "Japanese", "auto" is expanded to "ASCII,JIS,UTF-8,EUC-JP,SJIS" function test_mb_convert_encoding_auto() { $str = "Somebody was told: F$#$)UT#*HD]"; var_dump(mb_convert_encoding($str, "EUC-JP", "auto")); } mb_convert_encoding_not_utf_8(); test_mb_convert_encoding_auto();
tests/phpt/mbstring/002_mb_check_encoding.php
:
@ok <?php function mb_check_encoding_utf_8() { $str = "The leading horse is white, the second horse is red, the third one is black, the last one is green"; var_dump(mb_check_encoding($str, "UTF-8")); } function mb_check_encoding_not_utf_8() { $str = "The leading horse is white, the second horse is red, the third one is black, the last one is green"; var_dump(mb_check_encoding($str, "base64")); } mb_check_encoding_utf_8(); mb_check_encoding_not_utf_8();
На первой строчке мы указываем ожидаемое поведение kphp для этого теста, например:
@ok
, если kphp должен успешно скомпилировать этот код@kphp_should_fail
, если kphp не должен скомпилировать этот код и это ожидаемо@kphp_should_warn
, если kphp не должен скомпилировать этот код и это ожидаемо + должен вывести ворнинг
Запустим тесты:
./kphp_tester.py phpt/mbstring/001_mb_convert_encoding.php
:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/c19/c5a/c4e/c19c5ac4eb6d11760ebcae853927f3af.png)
./kphp_tester.py phpt/mbstring/002_mb_check_encoding.php
:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/ac0/fb7/a8c/ac0fb7a8cf92afbbb8a89351750ed804.png)
pull_request
Мы добавили новые функции, учли все варианты поведения, добавили тесты. Теперь нужно грамотно оформить pull_request. Не поверите, но есть одно-единственное правило, на котором я погорел. Ломать не строить - нужно понимать, что целевым проектом для kphp является vk. vk это монолит из более 9 миллионов строк кода на php. Если вы заменяете какие-то функции своими, которые работают правильно (как я заменил все функции mbstring своими), то разработчикам vk нужно будет переписывать места использования этих функций, что им не нужно, так как vk работает и без ваших обновленных функций, поэтому лучшим вариантом является добавление новых (уже существующих) функций по флагам. Таким образом выигрывают все. Разработки vk не переписывают код и другие пользователи kphp могут получить ваш функционал для своих проектов.
Что касается остального правил особо нет - пишите, что вы добавили. Можете сделать ревью проще, прикрепив ссылки на документацию, откуда вы взяли функцию, которую реализовали. Если есть какие-то нюансы (по типу того, что вы используете свою модифицированную библиотеку), тоже просто пишите об этом.