Можете нас поздравить - это двухсотая запись в блоге! А теперь перейдем к статье.
Всем айтишникам известно, что PHP - очень простой язык с низким порогом вхождения. Простой скрипт на нем сможет написать даже тот, кто не особо разбирается в программировании и не знает большинства синтаксических особенностей языка и приемов разработки на нем. Я сейчас занимаюсь написанием новой версии своего старого проекта PHP Obfuscator. Я переписываю его с нуля, чтобы он поддерживал все самые современные фичи последних версий PHP. Старый обфускатор (1.5) уже и в подметки не годится новому, хотя тот еще не дописан. Так вот, когда пишешь что-то подобное, волей-неволей оказываешься вынужден изучать синтаксис языка в мельчайших подробностях и узнаешь все его особенности и возможности. Поэтому я хочу рассказать о таких интересных свойствах языка PHP, о которых большинство людей, которые пишут на нем код, даже не слышали. Это малоизвестные или совсем новые возможности. Может быть, кто-то начнет их применять, потому что какие-то из них могут оказаться реально полезными. Многие вещи из тех, о которых я собираюсь рассказать, упомянуты в документации на PHP, но какие-то совсем не документированы. Какие-то же факты вас, вероятно, даже удивят. Кроме того, я хочу отдельно рассказать про метапрограммирование в PHP.
Итак, сначала - список достаточно малоизвестных, на мой взгляд, фич PHP. Для справки: все это будет работать на версиях PHP пятой ветки. Иногда я буду явно указывать, для каких версий PHP применима та или иная особенность. Кстати, не исключено, что какие-то из перечисленных вещей будут работать и в более старых версиях PHP. Однако, затрагивать какие-то новые фичи PHP 5.6 я пока не буду (хотя там уже много интересного), так как на момент написать статьи его релиза еще не было.
1. Теги <?php и ?>
Всем PHP-разработчикам известно, что код на PHP начинается с открывающегося тега. Однако, не все знают, что закрывающийся тег ?> можно свободно опускать в конце скрипта! Более того, это является рекомендованной методикой разработки и описано на сайте PHP. Мотивация опускать закрывающийся тег следующая:
Это помогает избежать добавлени случайных символов пробела или перевода строки после закрывающего тега PHP, которые могут послужить причиной нежелательных эффектов, так как PHP начинает выводить данные в буфер при отсутствии намерения у программиста выводить какие-либо данные в этой точке скрипта.
2. Закрывающийся тег.
Идем дальше. Знали ли вы, что закрывающийся тег ?> является эквивалентом точки с запятой? Можете убедиться в этом сами, выполнив такой скрипт:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php function test() { echo 'test! '; } test() //не ставим точку с запятой ?> <?php echo 'No semicolon again! ' //и тут тоже ?> |
3. Точки, запятые и float.
Закончили с тегами, идем дальше. Для начала вопрос к аудитории: что выведет следующий фрагмент кода?
1 2 3 |
<?php $pi = 3.14; echo "PI = $pi"; |
Вероятно, вы ответите, что "PI = 3.14", и будете неправы. Нельзя точно сказать, что выведет этот скрипт! Есть два варианта: "PI = 3.14" и "PI = 3,14". Обратите внимание, в первом случае в значении PI точка, а во втором - запятая. Все зависит от того, какая локаль (LC_NUMERIC) проброшена в интерпретатор PHP. Если английская, то выведется точка. Если, например, русская, то запятая. И вы можете очень сильно задуматься, почему не выполняется ваш SQL-запрос вроде "select value from table where value > $pi
". А всё потому, что SQL ожидает в числе с плавающей точкой именно точку, а не запятую!
"И как же бороться с этой проблемой?" - спросите вы. Я обнаружил два пути. Первый - явно в начале скрипта выставить для чисел локаль C:
1 2 3 4 |
<?php setlocale(LC_NUMERIC, 'C'); $pi = 3.14; echo "PI = $pi"; |
Это может быть для кого-то непримемлемо, если его скрипт использует локаль в разных местах при выполнении. Второй способ - банально заменять запятую на точку после конвертации float в строку:
1 2 3 4 |
<?php $pi = 3.14; $pi = str_replace(',', '.', (string)$pi); echo "PI = $pi"; |
4. Имена функций, классов, трейтов и интерфейсов.
Имена перечисленных сущностей в PHP являются регистронезависимыми. То есть, следующий код корректен и выполнится:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php echo strlen('test!'); echo STRLEN('test!'); echo StRlen('test!'); class MyClass { static public function Test() { echo 'test'; } } MyClass::Test(); MYCLAsS::TeST(); myCLASS::tesT(); |
Однако, имена констант классов и всех переменных являются регистрозависимыми. Кстати, в каких-то затертых версиях PHP имена были регистронезависимыми с учетом локали, что было очень странно и непереносимо между разными компьютерами. В последних версиях это поправили, и теперь регистр в именах не учитывается только для английских букв.
5. Имена констант.
Как я уже упоминал, имена констант классов в PHP регистрозависимые. Имена глобальных констант также по умолчанию регистрозависимы. Но можно в глобальной области видимости создать константу с регистронезависимым именем. Для этого служит третий параметр функции define:
1 2 3 4 5 6 7 8 9 10 |
<?php define('TEST_CONST', 100); echo TEST_CONST; echo test_const; //предупреждение компилятора: такой константы не существует define('TEST_CONST_2', 500, true); echo TEST_CONST_2; echo TEST_const_2; //всё хорошо |
(Хотя я этим пользоваться бы не рекомендовал, как и вообще менять регистр для одного и того же имени в скрипте.)
6. Switch-case и точки с запятыми.
Все знают, что допустим такой синтаксис оператора switch-case:
1 2 3 4 5 6 7 8 9 10 11 |
<?php switch(1) { case 1: echo 'one'; break; case 2: echo 'two'; break; } |
Но не всем известно, что двоеточия в нем можно заменить на точки с запятыми:
1 2 3 4 5 6 7 8 9 10 11 |
<?php switch(1) { case 1; echo 'one'; break; case 2; echo 'two'; break; } |
Это, кстати, документировано.
7. Если имя метода совпадает с именем класса, то это конструктор. Эта фича тянется еще со времен PHP 4, в котором имеется совсем незамысловатое ООП. Однако, с версии PHP 5.3.3 функция с именем как имя класса не будет считаться конструктором, если этот класс расположен в пространстве имен. Из-за всех этих сложностей в PHP 5 всегда рекомендуется использовать название __construct для создания конструктора.
8. stdClass.
В PHP есть такой полезный встроенный и совершенно пустой класс - stdClass
. Иногда очень удобно его использовать для чего-то такого:
1 2 3 4 5 |
<?php $obj = new stdClass; $obj->test = 123; $obj->test_2 = $obj->test + 2; echo $obj->test_2; //125 |
Как ни странно, этот класс не является аналогом System.Object
в .NET или java.lang.Object
в Java. Другие классы не унаследованы от stdClass
в PHP, кроме того, в нем нет никаких методов и членов.
9. Type Hinting и null.
Вы, вероятно, слышали о таком механизме в PHP, как Type Hinting. Он позволяет контролировать во время исполнения тип данных, передаваемых в функцию:
1 2 3 4 5 6 7 8 9 |
<?php function test(stdClass $obj) { print_r($obj); } test(new stdClass); //OK test(new Exception); //Catchable fatal error test(5); //Catchable fatal error |
Есть одна интересная (и даже документированная) особенность: если для аргумента с type hint'ом задать значение по умолчанию null
, то в функцию помимо объектов указанного типа можно будет передавать еще и null
:
1 2 3 4 5 6 7 8 9 |
<?php function test(stdClass $obj = null) { print_r($obj); } test(new stdClass); //OK test(null); //OK! test(5); //Catchable fatal error |
10. Короткий синтаксис массивов.
В PHP начиная с версии 5.4 для объявления массивов поддерживается такой синтаксис:
1 2 3 |
<?php $arr = [1, 2, 'x' => 3, [4, 5, 6]]; print_r($arr); |
11. Операторы для массивов.
Встроенные в PHP массивы поддерживают целую кучу операторов, которые обычно никто не использует. Особенно полезен, на мой взгляд, оператор "+".
12. Альтернативный синтаксис управляющих конструкций.
Для конструкций if-elseif-else
, for
, while
, switch
, foreach
и declare
в PHP имеется необычный синтаксис:
1 2 3 4 5 6 |
<?php if(true): echo 'true!'; else: echo 'false!'; endif; |
13. Тернарный оператор.
Уверен, что многие знакомы с этим оператором:
1 2 |
<?php echo true ? 'true!' : 'false'; |
Но известно ли вам о том, что с версии PHP 5.3 у этого оператора появилась еще и такая запись:
Выражение expr1 ?: expr3 возвращает expr1 если expr1 имеет значение TRUE, и expr3 в другом случае.
14. foreach и list.
Вероятно, вы даже не представляете, насколько полезна эта конструкция языка PHP - list. (Советую вам полностью прочитать эту страничку документации.) И вот с версии 5.5 ей добавилось еще одно применение:
1 2 3 4 5 6 7 8 |
<?php $arr = [ [1, 2], [3, 4] ]; foreach($arr as list($a, $b)) echo '(' . $a . ', ' . $b . '); '; |
15. Пробелы.
В некоторых неожиданных местах кода вы добавляете пробелы только для того, чтобы код можно было удобно читать. PHP-парсеру они не нужны. Следующие два примера одинаковы, и оба выполнятся успешно:
1 2 3 4 |
<?php $arr = [1, 2, 3]; foreach($arr as $val) echo $val; |
1 2 3 4 |
<?php $arr = [1, 2, 3]; foreach($arr as$val) //нет пробела echo$val; //нет пробела |
16. HEREDOC, NOWDOC.
Вы точно слышали о HEREDOC и, скорее всего, даже сами его использовали:
1 2 3 4 5 |
<?php $value = '123'; echo <<<HERE Value = "$value" HERE; |
В PHP 5.3 появился NOWDOC, который представляет собой тот же HEREDOC, но не парсится (примерно как inline HTML):
1 2 3 4 5 |
<?php $value = '123'; echo <<<'HERE' Value = "$value" HERE; |
А HEREDOC теперь стало можно записывать еще и так:
1 2 3 4 5 |
<?php $value = '123'; echo <<<"HERE" Value = "$value" HERE; |
17. Преобразование массива в объект.
Хотите знать, как удобно заполнить объект? Вот так:
1 2 3 4 |
<?php $obj = (object)['value1' => 123, 'my_val' => 567]; echo $obj->my_val; //567 HERE; |
Примечательно, но в результате преобразования массива в объект вы получите экземпляр класса stdClass, о котором я уже говорил выше. (А к этому примеру я вернусь во второй части статьи.)
18. require, include...
Знали ли вы, что подключаемые PHP-файлы могут возвращать значение?
test2.php
:
1 2 |
<?php return 5; |
test1.php
:
1 2 |
<?php echo require 'test2.php'; //Выведет 5 |
19. Имена сущностей.
Имена различных сущностей (переменных, классов, функций и т.д.) в скрипте на PHP могут состоять не только из цифр, английских букв и подчеркиваний. Они могут содержать следующие символы: a-z, A-Z, 0-9, _, 0x7f-0xff. То есть, функция с именем "ПРИВЕТ" вполне легальна, как и с любым другим именем в кодировке UTF-8. Но есть еще более веселая штука: эти ограничения на имена накладывает исключительно парсер PHP-кода. Сами по себе они могут содержать практически вообще что угодно! Но не для всех сущностей такие кривые имена можно задать. Вот парочка примеров:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php ${'???'} = 555; echo ${'???'} . PHP_EOL; print_r(get_defined_vars()); //Выведет, что значение переменной "???" равно 555 class MyClass { public function __call($method, $args) { echo 'Called "' . $method . '"'; } } $obj = new MyClass; $obj->{'!!!'}(); //Вызываем функцию с именем "!!!" |
Ну что ж, пожалуй, хватит о малоизвестных особенностях 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
(получить значение константы по имени, известном во время выполнения) и многие другие.
Тут у вас, вероятно, возникнет вопрос: что же значит "имя известно только во время выполнения"? Вот пример:
1 2 3 4 |
<?php $const_name = isset($_GET['const_name']) && !is_array($_GET['const_name']) ? $_GET['const_name'] : 'unknown'; define($const_name, 'value'); echo constant($const_name); |
Здесь мы создаем константу с неизвестным на этапе компиляции скрипта именем, так как это имя берется из параметра, переданного скрипту. Затем значение этой константы распечатывается. Теперь приведу противоположный пример:
1 2 3 |
<?php const MY_CONSTANT = 'value'; echo MY_CONSTANT; |
Здесь, как видно, делается то же самое, но имя константы уже заранее известно еще до выполнения скрипта (т.е. на этапе компиляции) - MY_CONSTANT
.
3. В PHP есть даже специальные магические методы у классов, предоставляющие возможности для метапрограммирования. Это __callStatic
, __call
, __set
, __get
, __isset
и __sleep
.
4. В PHP имеются даже целые объекты, поддерживающие концепцию метапрограммирования. Например, SimpleXMLElement:
1 2 3 |
<?php $xml = simplexml_load_string('<xml><test>123</test><value>456</value></xml>'); echo $xml->test . ' ' . $xml->value; |
Как понятно из примера, объект класса SimpleXMLElement
, созданный функцией simplexml_load_string
, содержит поля test
и value
. Но если бы в эту функцию мы передали другую строку (она, вообще говоря, известна только на этапе выполнения скрипта, например, ее можно было бы читать из файла), то и объект класса SimpleXMLElement
($xml
) содержал бы совсем другие поля. Получается, они создаются динамически.
5. PHP позволяет динамически создавать переменные с заранее неизвестным именем. Он позволяет обращаться к классам, функциям и переменным по именам, которые становятся известны только на этапе выполнения скрипта. Вот пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php $var_name = 'my_var'; //Динамическое имя переменной $$var_name = 5; //Создали переменную my_var со значением 5 echo $$var_name; //Выводим ее значение echo ${'my_var'}; //Другой путь вывести значение echo $GLOBALS['my_var']; //Еще один путь class TestClass { const TEST_CONST = 5; public function test() { echo 5; } static public function test_2() { echo 5; } } $class_name = 'TestClass'; //Динамическое имя класса $obj = new $class_name; //Создали объект класса с именем, содержащимся в $class_name (TestClass) $method_name = 'test'; //Динамическое имя метода $obj->$method_name(); //Вызвали метод с именем, содержащимся в переменной $method_name echo $class_name::TEST_CONST; //Вывели имя константы класса с именем, содержащимся в $class_name echo $obj instanceof $class_name ? 5 : 0; //Выведет 5, если объект $obj является экземпляром класса с именем из $class_name function test() { echo 5; } $function_name = 'test'; //Динамическое имя функции $function_name(); //Вызвали функцию с именем, содержащимся в переменной $function_name $method_name2 = 'test_2'; //Динамическое имя метода TestClass::$method_name2(); //Вызвали метод с именем, содержащимся в $method_name2 |
Как видите, PHP очень располагает к использованию метапрограммирования. Многие другие языки, например, C++ или C#, предоставляют гораздо более скудные возможности для этого.
6. Преобразование массива в объект
Да, если вы внимательно читали статью, вы уже видели пример кода на эту тему в первой ее части. Это тоже относится к метапрограммированию.
7. __autoload
, require
, include
, require_once
, include_once
Эти штуки позволяют на этапе исполнения решать, какой файл с кодом подключить к текущему PHP-файлу.
8. ::class
Эта синтаксическая особенность появилась только в PHP 5.5. Вот пример ее использования:
1 2 3 4 5 6 7 |
<?php namespace My\Names; class Test { } echo Test::class; //Выведет "My\Names\Test" |
Она позволяет получить полное имя класса (в виде строки). Ее бы я тоже отнес к метапрограммированию.
7. Reflection
Разработчикам PHP оказалось мало всего вышеперечисленного, и они решили добавить еще и механизм Reflection в этот язык. Это набор классов и функций, которые позволяют во время выполнения получать информацию о PHP-коде и менять его. Конечно, в других языках, вроде того же C#, такой механизм тоже есть, но в нем нет всех остальных фич, которые присущи PHP в плане метапрограммирования.
8. Но некоторым людям даже этого оказывается мало! И они начинают писать собственные расширения, позволяющие во время исполнения менять код PHP.
Как видите, PHP - язык, который действительно богат возможностями, позволяющими менять код скрипта во время его выполнения. Иногда применение таких фич очень полезно и оправдано, позволяет сэкономить много времени, написав меньше кода, чем пришлось бы написать, не прибегая к ним. Однако, как я уже говорил, злоупотребление метапрограммированием может превратить ваш код в нечитаемую кашу, при этом вам самим будет сложно в нем разбираться и искать ошибки. В любом случае, вам решать, насколько оправдано использование метапрограммирования в PHP-скриптах. Я же, как программист, предпочитающий C++ и строгие подходы к разработке, стараюсь не прибегать к этим возможностям при написании кода на PHP.
Напоследок предлагаю вам отписать в комментариях, о скольки из перечисленных в статье 19-ти возможностей языка PHP вы знали.
крайне познавательно, спасибо
Наудивление много из этого уже знал, не смотря на то, что давно не слежу за развитием на пхп и не пишу на нем.
Кстати, напиши статейку про даунлоадер с google.music. Не то что бы интересовало само скачивание музыки, скорее интересна их реализация стриминга.
Делаешь запрос на получение информации о нужном треке, получаешь в json список ссылок на фрагменты трека. Качаешь, клеишь, получаешь трек. В чем цимес то?
>Многие другие языки, например, C++ или C#, предоставляют гораздо более скудные возможности для этого.
И слава богу. Чем меньше магии, тем лучше. Я отлично знаю шарп, пхп(ну про все вышеописанные штуки кроме точки с запятой в виде ?> я знал) и руби. так вот в силу величайшей динамичности рубей там народ такое воротит, что хрен поймешь чего где берется. Ясность лучше магии. Поддержка редактора у пхп лучше, что делает понятнее работу с ним. А неудобность метапрограммирования приводит к тому, что хитрые штуки только внутри библиотек, а в обычном коде эти штуки не используют.
Весьма интересно, спасибо.
Kaimi, Dx, ничего я у себя сделаю квест подобный вашему? Не обидетесь что украл идею?
А какая нам разница-то, делай.
Напишите стотью о драйверах под вин и бхц.
Что про драйвера писать то?
[quote]Однако, не все знают, что закрывающийся тег ?> можно свободно опускать в конце скрипта![/quote]
Лол, это в стандарте PSR-0
[quote]3. Точки, запятые и float.[/quote]
Это уже зависит от локали пользователя. Чтобы регэкспы не портились, лучше использовать prepared queries
Про закрывающийся тег вообще на сайте php.net написано. Но знают-то не все. А PSR-0, видимо, вообще не стандарт, а только proposal.
Лол и как я на этом заработаю? к чему это ваще тут? Сидел ждал ждал..ни халявы, ни лавехи.. в чем дело?
Так на заводе заработаешь. А халява кончилась, это короче в рамках санкций США против России.
Ребята, поздравляю с интереснейшим 200 постом!
Вы лучшие!
Как вы думаете, что можно делать с локалью? Оказывается, ее можно пробросить в интерпретатор. Вона чо, а мужики-то не знают. Интересно, а продавить ее можно? С нетерпением жду ответа, народ на местах волнуется.
Не лень было писать бесполезный комментарий, чтобы просто показать свою проницательность и придраться к словам?