Фишечки PHP, о которых вы, скорее всего, не знали
Можете нас поздравить — это двухсотая запись в блоге! А теперь перейдем к статье.
Всем айтишникам известно, что PHP — очень простой язык с низким порогом вхождения. Простой скрипт на нем сможет написать даже тот, кто не особо разбирается в программировании и не знает большинства синтаксических особенностей языка и приемов разработки на нем. Я сейчас занимаюсь написанием новой версии своего старого проекта PHP Obfuscator. Я переписываю его с нуля, чтобы он поддерживал все самые современные фичи последних версий PHP. Старый обфускатор (1.5) уже и в подметки не годится новому, хотя тот еще не дописан. Так вот, когда пишешь что-то подобное, волей-неволей оказываешься вынужден изучать синтаксис языка в мельчайших подробностях и узнаешь все его особенности и возможности. Поэтому я хочу рассказать о таких интересных свойствах языка PHP, о которых большинство людей, которые пишут на нем код, даже не слышали. Это малоизвестные или совсем новые возможности. Может быть, кто-то начнет их применять, потому что какие-то из них могут оказаться реально полезными. Многие вещи из тех, о которых я собираюсь рассказать, упомянуты в документации на PHP, но какие-то совсем не документированы. Какие-то же факты вас, вероятно, даже удивят. Кроме того, я хочу отдельно рассказать про метапрограммирование в PHP.
Итак, сначала — список достаточно малоизвестных, на мой взгляд, фич PHP. Для справки: все это будет работать на версиях PHP пятой ветки. Иногда я буду явно указывать, для каких версий PHP применима та или иная особенность. Кстати, не исключено, что какие-то из перечисленных вещей будут работать и в более старых версиях PHP. Однако, затрагивать какие-то новые фичи PHP 5.6 я пока не буду (хотя там уже много интересного), так как на момент написать статьи его релиза еще не было.
1. Теги
Всем PHP-разработчикам известно, что код на PHP начинается с открывающегося тега. Однако, не все знают, что закрывающийся тег ?> можно свободно опускать в конце скрипта! Более того, это является рекомендованной методикой разработки и описано на сайте PHP. Мотивация опускать закрывающийся тег следующая:
Это помогает избежать добавлени случайных символов пробела или перевода строки после закрывающего тега PHP, которые могут послужить причиной нежелательных эффектов, так как PHP начинает выводить данные в буфер при отсутствии намерения у программиста выводить какие-либо данные в этой точке скрипта.
2. Закрывающийся тег.
Идем дальше. Знали ли вы, что закрывающийся тег ?> является эквивалентом точки с запятой? Можете убедиться в этом сами, выполнив такой скрипт:
3. Точки, запятые и float.
Закончили с тегами, идем дальше. Для начала вопрос к аудитории: что выведет следующий фрагмент кода?
Вероятно, вы ответите, что «PI = 3.14», и будете неправы. Нельзя точно сказать, что выведет этот скрипт! Есть два варианта: «PI = 3.14″ и «PI = 3,14″. Обратите внимание, в первом случае в значении PI точка, а во втором — запятая. Все зависит от того, какая локаль (LC_NUMERIC) проброшена в интерпретатор PHP. Если английская, то выведется точка. Если, например, русская, то запятая. И вы можете очень сильно задуматься, почему не выполняется ваш SQL-запрос вроде » select value from table where value > $pi «. А всё потому, что SQL ожидает в числе с плавающей точкой именно точку, а не запятую!
«И как же бороться с этой проблемой?» — спросите вы. Я обнаружил два пути. Первый — явно в начале скрипта выставить для чисел локаль C:
Это может быть для кого-то непримемлемо, если его скрипт использует локаль в разных местах при выполнении. Второй способ — банально заменять запятую на точку после конвертации float в строку:
4. Имена функций, классов, трейтов и интерфейсов.
Имена перечисленных сущностей в PHP являются регистронезависимыми. То есть, следующий код корректен и выполнится:
Однако, имена констант классов и всех переменных являются регистрозависимыми. Кстати, в каких-то затертых версиях PHP имена были регистронезависимыми с учетом локали, что было очень странно и непереносимо между разными компьютерами. В последних версиях это поправили, и теперь регистр в именах не учитывается только для английских букв.
5. Имена констант.
Как я уже упоминал, имена констант классов в PHP регистрозависимые. Имена глобальных констант также по умолчанию регистрозависимы. Но можно в глобальной области видимости создать константу с регистронезависимым именем. Для этого служит третий параметр функции define:
(Хотя я этим пользоваться бы не рекомендовал, как и вообще менять регистр для одного и того же имени в скрипте.)
6. Switch-case и точки с запятыми.
Все знают, что допустим такой синтаксис оператора switch-case:
Но не всем известно, что двоеточия в нем можно заменить на точки с запятыми:
7. Если имя метода совпадает с именем класса, то это конструктор. Эта фича тянется еще со времен PHP 4, в котором имеется совсем незамысловатое ООП. Однако, с версии PHP 5.3.3 функция с именем как имя класса не будет считаться конструктором, если этот класс расположен в пространстве имен. Из-за всех этих сложностей в PHP 5 всегда рекомендуется использовать название __construct для создания конструктора.
8. stdClass.
В PHP есть такой полезный встроенный и совершенно пустой класс — stdClass . Иногда очень удобно его использовать для чего-то такого:
Как ни странно, этот класс не является аналогом System.Object в .NET или java.lang.Object в Java. Другие классы не унаследованы от stdClass в PHP, кроме того, в нем нет никаких методов и членов.
9. Type Hinting и null.
Вы, вероятно, слышали о таком механизме в PHP, как Type Hinting. Он позволяет контролировать во время исполнения тип данных, передаваемых в функцию:
Есть одна интересная (и даже документированная) особенность: если для аргумента с type hint’ом задать значение по умолчанию null , то в функцию помимо объектов указанного типа можно будет передавать еще и null :
10. Короткий синтаксис массивов.
В PHP начиная с версии 5.4 для объявления массивов поддерживается такой синтаксис:
11. Операторы для массивов.
Встроенные в PHP массивы поддерживают целую кучу операторов, которые обычно никто не использует. Особенно полезен, на мой взгляд, оператор «+».
12. Альтернативный синтаксис управляющих конструкций.
Для конструкций if-elseif-else , for , while , switch , foreach и declare в PHP имеется необычный синтаксис:
13. Тернарный оператор.
Уверен, что многие знакомы с этим оператором:
Но известно ли вам о том, что с версии PHP 5.3 у этого оператора появилась еще и такая запись:
Выражение expr1 ?: expr3 возвращает expr1 если expr1 имеет значение TRUE, и expr3 в другом случае.
14. foreach и list.
Вероятно, вы даже не представляете, насколько полезна эта конструкция языка PHP — list. (Советую вам полностью прочитать эту страничку документации.) И вот с версии 5.5 ей добавилось еще одно применение:
15. Пробелы.
В некоторых неожиданных местах кода вы добавляете пробелы только для того, чтобы код можно было удобно читать. PHP-парсеру они не нужны. Следующие два примера одинаковы, и оба выполнятся успешно:
16. HEREDOC, NOWDOC.
Вы точно слышали о HEREDOC и, скорее всего, даже сами его использовали:
В PHP 5.3 появился NOWDOC, который представляет собой тот же HEREDOC, но не парсится (примерно как inline HTML):
А HEREDOC теперь стало можно записывать еще и так:
17. Преобразование массива в объект.
Хотите знать, как удобно заполнить объект? Вот так:
Примечательно, но в результате преобразования массива в объект вы получите экземпляр класса stdClass, о котором я уже говорил выше. (А к этому примеру я вернусь во второй части статьи.)
18. require, include.
Знали ли вы, что подключаемые PHP-файлы могут возвращать значение?
19. Имена сущностей.
Имена различных сущностей (переменных, классов, функций и т.д.) в скрипте на PHP могут состоять не только из цифр, английских букв и подчеркиваний. Они могут содержать следующие символы: a-z, A-Z, 0-9, _, 0x7f-0xff. То есть, функция с именем «ПРИВЕТ» вполне легальна, как и с любым другим именем в кодировке UTF-8. Но есть еще более веселая штука: эти ограничения на имена накладывает исключительно парсер PHP-кода. Сами по себе они могут содержать практически вообще что угодно! Но не для всех сущностей такие кривые имена можно задать. Вот парочка примеров:
Ну что ж, пожалуй, хватит о малоизвестных особенностях PHP. Теперь поговорим еще об одной занимательной вещи — о метапрограммировании в этом языке. Метапрограммирование позволяет на ходу во время исполнения программы модифицировать ее код. В PHP таких возможностей предостаточно (я бы сказал, что даже больше, чем требуется, и очень часто люди этим злоупотребляют в угоду тому, чтобы сэкономить пару строк кода). Часто программист может даже не задумываться, что он применяет такие приемы. Почему я решил об этом написать? Потому что использование метапрограммирования в PHP:
[+] может уменьшить объем написанного кода;
[+] может сильно усложнить восприятие кода;
[+] часто делает код непригодным для статического анализа.
Особенно хочу выделить последний пункт. Часто код после применения приемов метапрограммирования становится непригодным для какого-либо статического анализа и, в том числе, для обфускации. Поэтому я встроил в свой новый обфускатор детектор подобных вещей, чтобы заранее предупредить пользователя о том, что его скрипт, вероятно, корректно обфусцировать не получится. Разумеется, он сможет в конечном итоге все равно провести успешную обфускацию, но для этого придется написать правильный файл конфигурации для обфускатора.
Итак, здесь я приведу список всех (или почти всех) возможностей PHP, которые относятся к метапрограммированию. Это будет полезно, чтобы понять, какие фичи стоит по возможности ограничивать в применении, дабы не усложнять собственный код. Кто-то же, опять-таки, для себя откроет какие-то особенностя языка, о которых раньше не знал.
1. Во-первых, это, конечно же, eval. Разработчики безмерно любят применять эту инструкцию, хотя часто можно обойтись и без нее.
2. В PHP есть целый набор встроенных функций, которые позволяют модифицировать код на этапе выполнения. Это такие функции, как, например, call_user_method и call_user_method_array (вызовут неизвестный на этапе компиляции метод), create_function (создаст функцию с неизвестным на этапе компиляции телом), extract (создаст переменные на этапе выполнения с именами, которые не известны на этапе компиляции), compact (упакует значения переменных в массив, имена которых определятся только на этапе выполнения) и даже define . Есть еще целая гора функций, которые позволяют работать не напрямую с переменными, объектами или типами классов и функциями, а с их строковыми именами, и это тоже можно отнести к метапрограммированию. Это, например, get_class (получить название класса по переданному объекту), class_alias (создать псевдоним для класса, имена оригинала и псевдонима станут известны только на этапе выполнения), class_exists (проверить, существует ли класс с именем, известным только во время выполнения), get_class_methods (получить имена методов класса по его имени, которое, как вы уже догадались, известно только на этапе выполнения), interface_exists (проверить, существует ли интерфейс с известным только на этапе выполнения именем), is_a (проверить, унаследован ли тот или иной класс от другого класса по его имени, которое известно лишь во время выполнения), get_defined_vars (получить имена определенных переменных), spl_autoload_functions (попробовать загрузить класс через механизм autoload , имя класса известно на этапе выполнения), constant (получить значение константы по имени, известном во время выполнения) и многие другие.
Тут у вас, вероятно, возникнет вопрос: что же значит «имя известно только во время выполнения»? Вот пример: