PHP

В твіттері Сохацькому не дають працювати і знову відтягують на графоманство. За допомогою капіталістичного шантажу мене змусили написати статтю для нашої Енциклопедії мов програмування. А саме стаття про мову програмування PHP, на якій написане все зло людства --- Фейсбук, і навіть пара популярних сайтів типу Вікіпедії та продуктів типу Вордпрес і Ларавель.

Я рідко даю розширені ретроспективи мовам, тому що вважаю це журналістикою, вона може бути хорошою чи поганою, але вона не замінить самого письма. Тому мені абсолютно байдуже, яку спадщину має ця мова, на скількох пристроях вона працює, скільки грошей, реклами, котів чи порно ці мови обробляють за наносекунди. Для мене це другорядні речі, тому що модулі написані на найкрасивіших мовах програмування, і вони взагалі ніде не використовуються. Так, що все це починаючи з якогось момента дуже відносно.

З іншого боку, смішно писати про PHP коротко (із зазначенням лише нотації BNF, семантики, історичної довідки та тактико-технічних характеристик) — ця мова заслуговує на історію раннього вебу та швидко закріплені унікальні шляхи розвитку. що визначило web 2020. Сучасна мережа 2020 — це як Коронавірус — інфекція, яка випадково вбиває навіть найсміливіших і запечатує їх у забуття. Потрібно розуміти, що PHP створювався в той час, коли Netscape домінував у мережі, про що ви можете знайти фільми в Нешинал Джеографік. У той час JavaScript був невеликим механізмом скриптів, створеним для анімації падаючих новорічних сніговиків на веб-сторінках (тоді це було модно), і ця технологія називалася DHTML.

Тут я хотів би відпустити думки і пояснити, чому ми ненавидимо капіталізм, гроші і технології, які під їхнім тиском застигають і набувають інерції граничної швидкості руху. Трьома вершниками цього апокаліпсису є Netscape, JavaScript і PHP. Хоча ці речі певною мірою повністю доповнюють одна одну і нагадують мені зараз щось на кшталт повного стосу архаїчних систем, а не сучасних засобів розробки. Якщо ми скажемо зараз, завдання полягатиме в тому, щоб написати новий стандарт для всього, починаючи з віртуальної машини, синтаксису та мови — я думаю, кожен погодиться на щось більш освічене та культурне. Тепер ці речі виглядають для освічених людей так само, як ALGOL і PL/1 виглядають для нас зараз, мови навіть більш архаїчні та не менш легендарні.

Концепція веб-сайту проста, як чиста лямбда: у контекстно-незалежному середовищі шаблон видає нову сторінку за кожним запитом і вмирає. Крім того, спеціалізований клієнт (браузер) виконує JavaScript для запиту нових сторінок, перемальовуючи частини сторінки без перезавантаження (так, DHTML або те, що зараз називається SPA, було доступне навіть за часів Netscape). У зв'язку з цим все ідеально: контекстно-незалежний шаблон PHP (який можна зробити контекстно-залежним, підключивши базу), спеціалізований клієнт, віртуальна машина для написання сторінок. Будь-який архітектор, який збирався рефакторити сучасну мережу, залишив би цю трійку. Та й усі спроби переписати веб так чи інакше зводяться до цих компонентів.

Однак розвиток мов і середовища виконання зайшов далеко, і багатьох проблем існуючої мережі можна було б уникнути, маючи хоча б найменшу можливість щось змінити, але змінити нічого неможливо, можна лише спробувати почекати. На жаль, сучасна мережа інфікована цією трійкою.

Кожен із компонентів (як віруси) природно намагався замінити сам себе. Так JavaScript хотів замінити Dart найзліснішою корпорацією на планеті, хоча поруч завжди була Lua, яка також чудово підходила на роль JIT-інтерпретатора. Тут OpenResty для NGNIX вже виглядає кращим стеком, ніж LAMP! У ньому хоча б по одному компоненту все покращено, латентність зменшена за рахунок JIT-запуску прямо в веб-сервері, загалом справжнє (тільки слабкі стеки).

Іншим прикладом хорошої заміни є заміна PHP перетворенням XSLT. Також послідовний і абсолютно інший підхід до відтворення клієнтського вмісту. У цьому підході дані між клієнтом запускаються виключно на типі XSD, самі запити виконуються через SOAP, XSLT кешується в браузері (так, браузер може це робити), а сама сторінка відображається на клієнті. Результатом є чистий бекенд XML-RPC, усе стандартизоване та кероване. Я бачив деякі частини ПриватБанку, побудовані на цій технології в 2011 році, шкода, що тема з XSLT в браузері не стала загальнопопулярною, багато в чому через синхронність перетворень (потрібен повний набір даних), але я думаю, що можна було б розширити деякі стандартні потоки або навіть Pi-числення, якщо це потрібно. Не було сильної руки ринку, яка харчувалася необхідним продуктом.

Але були й погані приклади наслідування. Інші мови негайно скопіювали успіх PHP. Так JSP з’явився у світі Java, ASP у світі Microsoft, DTL у світі Python тощо. тощо сотні з них, і всі беруть свій початок від Netscape/JavaScript/PHP. JavaScript був і залишається основним веб-браузером без видимої можливості бути заміненим у найближчому майбутньому. Хіба що йому вдалося заразити серверні бекенди: на придуманій для сніжинок мові компанія Joyent запропонувала писати серверні мережеві додатки.

Тож давайте почнемо класифікувати екстремуми підходу до відтворення вмісту: 1) на клієнті (XSLT), чисті дані по дроту? 2) на сервері (PHP), html-over-http. Знову ж таки, існують тисячі фреймворків, які реалізують ці два підходи, і в мовах, які існували до PHP, таких як Perl або Python. Насправді в ті часи це називалося CGI. Будь-яка програма, розміщена в контексті веб-сервера, ставала шаблоном, наприклад, написана на Pascal, тоді веб не був прив’язаний до мови. Одночасно з PHP з'явилася не менш легендарна і більш послідовна мова програмування Ruby з розвиненою системою макросів, так необхідних у веб-програмуванні, синтаксис яких був перенесений на інші віртуальні машини та платформи. До речі про них.

У світі кодової бази Facebook, яка швидко розвивається, будь-яка корпорація була змушена захистити свої інвестиції та написати власну віртуальну машину та власну мову з визначенням типу (і тип одиниці, який негайно знижується до нетипової лямбда). Facebook успішно працює на HipHop VM, яка створена більш технологічно, ніж оригінальний PHP. Мова також була вдосконалена та усереднена до спільного знаменника всіх ООП-мов із дженериками. Нажаль це не втримало HVVM від End of Life.

Особливістю побудови PHP-систем є перенесення кеш-пам'яті в спеціальне сховище (наприклад, Redis або Erlang) для зберігання даних сесії (а також баз даних) для мінімізації контексту рендерингу контейнерів і оптимізації розподілу пам'яті в інтерпретаторі. Інший підхід полягає в об'єднанні обчислювальної пам’яті з контекстом веб-сервера та створенні довготривалих контейнерів додатків в однорідному мовному середовищі з високою локальністю даних (зниження трафіку, вартість) аж до інтеграції з шиною та системою зберігання даних. Це друга (більш монолітна версія), яка замінює старий підхід швидкого припинення всього процесу після відтворення сторінки. Інші проблеми в пайплані PHP полягають в неможливості побудови принципово ідіоматичного довгограючого WebSocket сервера (без чого сучасний веб немислимий). Були спроби типу phpDaemon і FastCGI, але це вже не справжній PHP і працює гірше конкурентів.

Єдиний вихід для PHP-бізнесу — це просто запускати проекти, поки є ринок, підтримуючи людей, які працюють виключно в Інтернеті: веб-магазини, саморобні ERP-системи, системи керування контентом. Це як COBOL, стільки написано, що працюватиме ще 50 років. Чи варто новому програмісту починати вивчати PHP? Хіба що з археологічної точки зору, оскільки мова не складна, і вам потрібно зрозуміти її нішу, щоб вважатися професіоналом у веб-програмуванні. Якщо вже є якийсь проект на PHP, наприклад, написана власна система управління підприємством, то є сенс оновити його до HHVM, а там напевно до вас прийдуть і запропонують переписати його в щось. Прислухайтеся до них, це окупиться, але не викидайте і версію PHP.

БНФ-нотаці

Наводжу БНФ-нотацію приблизно тої версії мови, на якій я писав на першій своїй роботі.

PHP_SOURCE_TEXT = { inner_statement | halt_compiler_statement }; halt_compiler_statement = "__halt_compiler" "(" ")" ";" ; inner_statement = statement | function_declaration_statement | class_declaration_statement ; inner_statement_list = { inner_statement } ; statement = "{" inner_statement_list "}" | "if" "(" expr ")" statement {elseif_branch} [else_single] | "if" "(" expr ")" ":" inner_statement_list {new_elseif_branch} [new_else_single] "endif" ";" | "while" "(" expr ")" while_statement | "do" statement "while" "(" expr ")" ";" | "for" "(" for_expr ";" for_expr ";" for_expr ")" for_statement | "switch" "(" expr ")" switch_case_list | "break" [expr] ";" | "continue" [expr] ";" | "return" [expr_without_variable | variable] ";" | "global" global_var {"," global_var} ";" | "static" static_var { "," static_var } ";" | "echo" echo_expr_list ";" | T_INLINE_HTML | expr ";" | "use" use_filename ";" # FIXME: not implemented | "unset" "(" variable {"," variable} ")" ";" | "foreach" "(" (variable|expr_without_variable) "as" foreach_variable ["=>" foreach_variable] ")" foreach_statement | "declare" "(" declare_list ")" declare_statement | ";" # empty statement | "try" "{" inner_statement_list "}" catch_branch {catch_branch} | "throw" expr ";" ; catch_branch = "catch" "(" fully_qualified_class_name T_VARIABLE ")" "{" inner_statement_list "}" ; use_filename = T_CONSTANT_ENCAPSED_STRING | "(" T_CONSTANT_ENCAPSED_STRING ")" ; function_declaration_statement = "function" ["&"] T_STRING "(" parameter_list ")" "{" inner_statement_list "}" ; class_declaration_statement = class_entry_type T_STRING [extends_from] [implements_list] "{" {class_statement} "}" | "interface" T_STRING [interface_extends_list] "{" {class_statement} "}" ; class_entry_type = [ "abstract" | "final" ] "class" ; extends_from = "extends" fully_qualified_class_name ; interface_extends_list = "extends" interface_list ; implements_list = "implements" interface_list ; interface_list = fully_qualified_class_name { "," fully_qualified_class_name } ; foreach_variable = ["&"] variable ; for_statement = statement | ":" inner_statement_list "endfor" ";" ; foreach_statement = statement | ":" inner_statement_list "endforeach" ";" ; declare_statement = statement | ":" inner_statement_list "enddeclare" ";" ; declare_list = T_STRING "=" static_scalar { "," T_STRING "=" static_scalar } ; switch_case_list = "{" [";"] {case_list} "}" | ":" [";"] {case_list} "endswitch" ";" ; case_list = "case" expr [":"|";"] inner_statement_list | "default" [":"|";"] inner_statement_list ; while_statement = statement | ":" inner_statement_list "endwhile" ";" ; elseif_branch = "elseif" "(" expr ")" statement ; new_elseif_branch = "elseif" "(" expr ")" ":" inner_statement_list ; else_single = "else" statement ; new_else_single = "else" ":" inner_statement_list ; parameter_list = [ parameter {"," parameter} ] ; parameter = [T_STRING | "array"] ["&"] T_VARIABLE ["=" static_scalar] ; function_call_parameter_list = [ function_call_parameter { "," function_call_parameter } ] ; function_call_parameter = expr_without_variable | variable | "&" w_variable ; global_var = T_VARIABLE | "$" r_variable | "$" "{" expr "}" ; static_var = T_VARIABLE [ "=" static_scalar ] ; class_statement = variable_modifiers class_variable_declaration {"," class_variable_declaration} ";" | "const" class_constant_declaration {"," class_constant_declaration} ";" | {modifier} "function" ["&"] T_STRING "(" parameter_list ")" method_body ; method_body = ";" | "{" inner_statement_list "}" ; variable_modifiers = "var" | modifier {modifier} ; modifier = "public" | "protected" | "private" | "static" | "abstract" | "final" ; class_variable_declaration = ("var" | modifier {modifier}) T_VARIABLE ["=" static_scalar] ; class_constant_declaration = T_STRING "=" static_scalar ; echo_expr_list = expr {"," expr} ; for_expr = [ expr {"," expr} ] ; expr_without_variable = "list" "(" assignment_list ")" "=" expr | variable "=" expr | variable "=" "&" variable | variable "=" "&" "new" class_name_reference [ctor_arguments] | "new" class_name_reference [ctor_arguments] | "clone" expr | variable ("+=" | "-=" | "*=" | "/=" | ".=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" ) expr | rw_variable "++" | "++" rw_variable | rw_variable "--" | "--" rw_variable | expr ("||" | "&&" | "or" | "and" | "xor" | "|" | "&" | "^" | "." | "+" | "-" | "*" | "/" | "%" | "<<" | ">>" | "===" | "!==" | "<" | "<=" | ">" | ">=" ) expr | ("+" | "-" | "!" | "~") expr | expr "instanceof" class_name_reference | "(" expr ")" | expr "?" expr ":" expr | internal_functions | "(int)" expr | "(double)" expr | "(float)" expr | "(real)" expr | "(string)" expr | "(array)" expr | "(object)" expr | "(bool)" expr | "(boolean)" expr | "(unset)" expr # FIXME: not implemented | "exit" [exit_expr] | "die" [exit_expr] | "@" expr | scalar | "array" "(" [array_pair_list] ")" | "`" encaps_list "`" | "print" expr ; function_call = T_STRING "(" function_call_parameter_list ")" | fully_qualified_class_name "::" T_STRING "(" function_call_parameter_list ")" | fully_qualified_class_name "::" variable_without_objects "(" function_call_parameter_list ")" | variable_without_objects "(" function_call_parameter_list ")" ; fully_qualified_class_name = T_STRING ; class_name_reference = T_STRING | dynamic_class_name_reference ; dynamic_class_name_reference = base_variable "->" object_property { "->" object_property } | base_variable ; exit_expr = "(" [expr] ")" ; ctor_arguments = "(" function_call_parameter_list ")" ; common_scalar = T_LNUMBER | T_DNUMBER | T_CONSTANT_ENCAPSED_STRING | "__LINE__" | "__FILE__" | "__CLASS__" | "__METHOD__" | "__FUNCTION__" ; # FIXME: very bad syntax, $x = + + + 4; is valid! static_scalar = common_scalar | T_STRING | "+" static_scalar | "-" static_scalar | "array" "(" [static_array_pair_list] ")" | static_class_constant ; static_class_constant = T_STRING "::" T_STRING ; scalar = T_STRING | T_STRING_VARNAME | class_constant | common_scalar | "\"" encaps_list "\"" | "'" encaps_list "'" | T_START_HEREDOC encaps_list T_END_HEREDOC ; static_array_pair_list = static_array_pair { "," static_array_pair } [","] ; static_array_pair = static_scalar ["=>" static_scalar] ; expr = r_variable | expr_without_variable ; r_variable = variable ; w_variable = variable ; rw_variable = variable ; variable = base_variable_with_function_calls [ "->" object_property method_parameters { "->" object_property method_parameters } ] ; method_parameters = "(" function_call_parameter_list ")" ; variable_without_objects = reference_variable | simple_indirect_reference reference_variable ; static_member = fully_qualified_class_name "::" variable_without_objects ; base_variable_with_function_calls = base_variable | function_call ; base_variable = reference_variable | simple_indirect_reference reference_variable | static_member ; reference_variable = compound_variable { selector } ; compound_variable = T_VARIABLE | "$" "{" expr "}" ; selector = "[" [expr] "]" | "{" expr "}" ; object_property = variable_name { selector } | variable_without_objects ; variable_name = T_STRING | "{" expr "}" ; simple_indirect_reference = "$" {"$"} ; assignment_list = [assignment_list_element] {"," [assignment_list_element]} ; assignment_list_element = variable | "list" "(" assignment_list ")" ; array_pair_list = array_pair {"," array_pair} [","] ; array_pair = "&" w_variable | expr "=>" "&" w_variable | expr "=>" expr ; encaps_list = { encaps_var | T_STRING | T_NUM_STRING | T_ENCAPSED_AND_WHITESPACE | T_CHARACTER | T_BAD_CHARACTER | "[" | "]" | "{" | "}" | "->" } ; encaps_var = T_VARIABLE [ "[" encaps_var_offset "]" ] | T_VARIABLE "->" T_STRING | "${" expr "}" | "${" T_STRING_VARNAME "[" expr "]" "}" | T_CURLY_OPEN variable "}" ; encaps_var_offset = T_STRING | T_NUM_STRING | T_VARIABLE ; internal_functions = "isset" "(" variable {"," variable} ")" | "empty" "(" variable ")" | "include" expr | "include_once" expr | "eval" "(" expr ")" | "require" expr | "require_once" expr ; class_constant = fully_qualified_class_name "::" T_STRING ; LABEL = (letter | "_") {letter | digit | "_"} ; T_STRING = LABEL; T_BAD_CHARACTER = "\x00".."\x08" | "\x0b" | "\x0c" | "\x0e".."\x1f" ; T_VARIABLE = "$" T_STRING ; T_LNUMBER = octal | decimal | hexadecinal ; octal = "0" {"0".."7"} ; decimal = "1".."9" {digit} ; hexadecinal = "0x" hexdigit {hexdigit} ; digit = "0".."9" ; hexdigit = digit | "a".."f" | "A".."F" ; letter = "a".."z" | "A".."Z" | "\x7f".."\xff" ; T_DNUMBER = DNUM | EXPONENT_DNUM; DNUM = digit ["."] digit {digit} | digit {digit} ["."] {digit}; EXPONENT_DNUM = (LNUM | DNUM) ("e"|"E") ["+"|"-"] LNUM; LNUM = digit {digit}; T_CURLY_OPEN = "${"; T_CONSTANT_ENCAPSED_STRING = single_quoted_constant_string | double_quoted_constant_string; # FIXME single_quoted_constant_string = "'" { "any char except ' and \\" | "\\" "any char" } "'"; # FIXME double_quoted_constant_string = "\"" { "any char except $ \" and \\" | "\\" "any char" } "\""; T_STRING_VARNAME = LABEL; T_NUM_STRING = LNUM | hexadecinal; T_START_HEREDOC = "<<<<" {" "|"\t"} LABEL NEWLINE; NEWLINE = "\r"|"\n"|"\r\n"; T_END_HEREDOC = "FIXME: here at the beginning of the line" LABEL [";"] NEWLINE;