Фишечки PHP, о которых вы, скорее всего, не знали

php

Можете нас поздравить - это двухсотая запись в блоге! А теперь перейдем к статье.

Всем айтишникам известно, что PHP - очень простой язык с низким порогом вхождения. Простой скрипт на нем сможет написать даже тот, кто не особо разбирается в программировании и не знает большинства синтаксических особенностей языка и приемов разработки на нем. Я сейчас занимаюсь написанием новой версии своего старого проекта PHP Obfuscator. Я переписываю его с нуля, чтобы он поддерживал все самые современные фичи последних версий PHP. Старый обфускатор (1.5) уже и в подметки не годится новому, хотя тот еще не дописан. Так вот, когда пишешь что-то подобное, волей-неволей оказываешься вынужден изучать синтаксис языка в мельчайших подробностях и узнаешь все его особенности и возможности. Поэтому я хочу рассказать о таких интересных свойствах языка PHP, о которых большинство людей, которые пишут на нем код, даже не слышали. Это малоизвестные или совсем новые возможности. Может быть, кто-то начнет их применять, потому что какие-то из них могут оказаться реально полезными. Многие вещи из тех, о которых я собираюсь рассказать, упомянуты в документации на PHP, но какие-то совсем не документированы. Какие-то же факты вас, вероятно, даже удивят. Кроме того, я хочу отдельно рассказать про метапрограммирование в PHP.

Итак, сначала - список достаточно малоизвестных, на мой взгляд, фич PHP. Для справки: все это будет работать на версиях PHP пятой ветки. Иногда я буду явно указывать, для каких версий PHP применима та или иная особенность. Кстати, не исключено, что какие-то из перечисленных вещей будут работать и в более старых версиях PHP. Однако, затрагивать какие-то новые фичи PHP 5.6 я пока не буду (хотя там уже много интересного), так как на момент написать статьи его релиза еще не было.

1. Теги <?php и ?>
Всем 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-файлы могут возвращать значение?

test2.php:

test1.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 (получить значение константы по имени, известном во время выполнения) и многие другие.

Тут у вас, вероятно, возникнет вопрос: что же значит "имя известно только во время выполнения"? Вот пример:

Здесь мы создаем константу с неизвестным на этапе компиляции скрипта именем, так как это имя берется из параметра, переданного скрипту. Затем значение этой константы распечатывается. Теперь приведу противоположный пример:

Здесь, как видно, делается то же самое, но имя константы уже заранее известно еще до выполнения скрипта (т.е. на этапе компиляции) - MY_CONSTANT.

3. В PHP есть даже специальные магические методы у классов, предоставляющие возможности для метапрограммирования. Это __callStatic, __call, __set, __get, __isset и __sleep.

4. В PHP имеются даже целые объекты, поддерживающие концепцию метапрограммирования. Например, SimpleXMLElement:

Как понятно из примера, объект класса SimpleXMLElement, созданный функцией simplexml_load_string, содержит поля test и value. Но если бы в эту функцию мы передали другую строку (она, вообще говоря, известна только на этапе выполнения скрипта, например, ее можно было бы читать из файла), то и объект класса SimpleXMLElement ($xml) содержал бы совсем другие поля. Получается, они создаются динамически.

5. PHP позволяет динамически создавать переменные с заранее неизвестным именем. Он позволяет обращаться к классам, функциям и переменным по именам, которые становятся известны только на этапе выполнения скрипта. Вот пример:

Как видите, PHP очень располагает к использованию метапрограммирования. Многие другие языки, например, C++ или C#, предоставляют гораздо более скудные возможности для этого.

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

7. __autoload, require, include, require_once, include_once
Эти штуки позволяют на этапе исполнения решать, какой файл с кодом подключить к текущему PHP-файлу.

8. ::class
Эта синтаксическая особенность появилась только в PHP 5.5. Вот пример ее использования:

Она позволяет получить полное имя класса (в виде строки). Ее бы я тоже отнес к метапрограммированию.

7. Reflection
Разработчикам PHP оказалось мало всего вышеперечисленного, и они решили добавить еще и механизм Reflection в этот язык. Это набор классов и функций, которые позволяют во время выполнения получать информацию о PHP-коде и менять его. Конечно, в других языках, вроде того же C#, такой механизм тоже есть, но в нем нет всех остальных фич, которые присущи PHP в плане метапрограммирования.

8. Но некоторым людям даже этого оказывается мало! И они начинают писать собственные расширения, позволяющие во время исполнения менять код PHP.

Как видите, PHP - язык, который действительно богат возможностями, позволяющими менять код скрипта во время его выполнения. Иногда применение таких фич очень полезно и оправдано, позволяет сэкономить много времени, написав меньше кода, чем пришлось бы написать, не прибегая к ним. Однако, как я уже говорил, злоупотребление метапрограммированием может превратить ваш код в нечитаемую кашу, при этом вам самим будет сложно в нем разбираться и искать ошибки. В любом случае, вам решать, насколько оправдано использование метапрограммирования в PHP-скриптах. Я же, как программист, предпочитающий C++ и строгие подходы к разработке, стараюсь не прибегать к этим возможностям при написании кода на PHP.

Напоследок предлагаю вам отписать в комментариях, о скольки из перечисленных в статье 19-ти возможностей языка PHP вы знали.

Фишечки PHP, о которых вы, скорее всего, не знали: 16 комментариев

  1. Наудивление много из этого уже знал, не смотря на то, что давно не слежу за развитием на пхп и не пишу на нем.

    Кстати, напиши статейку про даунлоадер с google.music. Не то что бы интересовало само скачивание музыки, скорее интересна их реализация стриминга.

    1. Делаешь запрос на получение информации о нужном треке, получаешь в json список ссылок на фрагменты трека. Качаешь, клеишь, получаешь трек. В чем цимес то?

  2. >Многие другие языки, например, C++ или C#, предоставляют гораздо более скудные возможности для этого.

    И слава богу. Чем меньше магии, тем лучше. Я отлично знаю шарп, пхп(ну про все вышеописанные штуки кроме точки с запятой в виде ?> я знал) и руби. так вот в силу величайшей динамичности рубей там народ такое воротит, что хрен поймешь чего где берется. Ясность лучше магии. Поддержка редактора у пхп лучше, что делает понятнее работу с ним. А неудобность метапрограммирования приводит к тому, что хитрые штуки только внутри библиотек, а в обычном коде эти штуки не используют.

  3. [quote]Однако, не все знают, что закрывающийся тег ?> можно свободно опускать в конце скрипта![/quote]
    Лол, это в стандарте PSR-0
    [quote]3. Точки, запятые и float.[/quote]
    Это уже зависит от локали пользователя. Чтобы регэкспы не портились, лучше использовать prepared queries

    1. Про закрывающийся тег вообще на сайте php.net написано. Но знают-то не все. А PSR-0, видимо, вообще не стандарт, а только proposal.

  4. Как вы думаете, что можно делать с локалью? Оказывается, ее можно пробросить в интерпретатор. Вона чо, а мужики-то не знают. Интересно, а продавить ее можно? С нетерпением жду ответа, народ на местах волнуется.

    1. Не лень было писать бесполезный комментарий, чтобы просто показать свою проницательность и придраться к словам?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *