Фокус внимания давно переместился с PHP на JavaScript и Python. Тем не менее у него выходят новые версии, а тесты производительности говорят о неплохом прогрессе. Насколько актуален PHP сегодня? Под катом — размышления разработчика, который продолжает отдавать ему предпочтение.

Краткая история PHP

PHP разработал Расмус Лердорф в 1994 году. Лердорф создал набор скриптов, которые отслеживали посещения его онлайн-резюме, и назвал их Personal Home Page Tools («инструменты личной домашней страницы»). Со временем название превратилось в PHP Tools. Он пополнял этот набор новыми инструментами, а потом решил переписать их: добавил взаимодействие с базами данных и многое другое. Так набор превратился во фреймворк.

Дальше эти инструменты продолжали эволюционировать в еще более сложные примитивы, а после публикации кода в open source в 1995 году число пользователей стало расти заметно быстрее. Если вас интересуют подробности этой истории, вы можете найти их на официальном сайте PHP.

Последняя версия языка сейчас — PHP 8.0.

А что не так с PHP?

Уже долгие годы этот язык — мишень для критики. Люди справедливо упрекают его за проблемы в работе, особенно старые версии. Лердорф разрабатывал PHP как язык шаблонизации, а не как полнофункциональный язык программирования. Поэтому у него есть ряд недостатков, которые сильно усложняют поддержку и разработку крупных приложений. 

Слабая типизация

Лично мне в этом языке не нравится слабая типизация, позволяющая сочетать разные типы и выполнять их неявные преобразования. Рассмотрим такой пример:

echo "1" + 3;
echo 1 + "3";
echo "1" + "3";

Результатом этих операций будет число 4, то есть в контексте оператора сложения язык преобразует числа в строке в целочисленные значения. В некоторых случаях это может оказаться желательным или позволит сэкономить пару строк кода, но чем больше становится проект, тем сложнее будет поддержка.

В поздних версиях языка начали появляться предупреждения о подобных странных и недопустимых операциях, то есть они признаны устаревшими или скоро ими станут.

Отсутствие пространств имен

Поддержка пространств имен была добавлена в PHP в версии 5.3. В более старых проектах приходилось создавать собственные типы пространств имен, в основном при помощи добавления пространств имен к названиям классов и методов. Из-за этого приходилось использовать повсюду абсурдно длинные имена. 

В проектах, разработанных в предыдущих версиях, часто можно встретить классы наподобие Payments_Provider_Processor_Provider_SomeExternalServiceProvider, хотя его можно было бы просто назвать SomeExternalServiceProvider. В большинстве случаев это приводит к созданию раздутого кода и усложняет его чтение и разбор.

В более поздних версиях языка таких проблем нет.

Противоречивые функции стандартной библиотеки

Не буду говорить, что стандартная библиотека языка плоха, но она могла бы быть и получше. Язык довольно сильно совершенствуется, однако первым версиям стандартной библиотеки, на которые до сих пор ссылаются и поддерживают для обратной совместимости, не хватает целостности. Хоть это и небольшая проблема, она означает, что многие функции стандартной библиотеки имеют разные правила наименования, имена и порядок аргументов. Все это усложняет выявление их значений по умолчанию и поведение.

Вот некоторые примеры противоречивости наименований в строковых методах из документации:

  • strpos(string $haystack, string $needle, int $offset = 0): int|false: находит позицию первого вхождения подстроки в строке;

  • strsplit(string $string, int $length = 1): array : преобразует строку в массив;

  • explode(string $separator, string $string, int $limit = PHP_INT_MAX): array: разделяет строку по граничной строке.

Три разные функции: одна с префиксом str, вторая с префиксом str_, а третья без префикса. Аргумент $string является первым для str_split, но вторым для explode. Вы можете изучить все строковые методы в документации — этому паттерну следует множество функций, то есть среди них нет особого единообразия.

Суперглобальные переменные

Это больше относится к личным предпочтениям — я ненавижу использовать глобальные переменные, а следовательно, и суперглобальные. Когда находишь какие-то старые самодельные проекты, особенно высока вероятность встретиться с печально известными переменными типа $SERVER или $REQUEST. Не поймите меня неправильно: иногда они очень полезны и время от времени их нужно использовать. Однако для безопасного использования этих значений первым делом нужно инкапсулировать их в многократно используемые классы. Если этого не сделать, то взаимодействие со значениями или внесение изменений в крупный проект будет очень сложной задачей, ведь с этими значениями связано множество скрытых зависимостей.

И что хорошего в PHP?

Язык на многих произвел плохое впечатление, но за последние годы он сильно улучшился. Благодаря релизу PHP 7 язык модернизировался, в его основе появилось множество приятных особенностей, повысились скорость работы и удобство пользования.

Type hints

Это один из моих любимых способов модернизации старого кода на PHP: использование необязательных type hints, выполняющих преобразование типов, а также обеспечивающих документацию кода. Рассмотрим простую функцию:

function isValueSomething($value) {}

Если добавить type hints, код станет таким:

function isValueSomething(string $value): bool {}

Просто увидев сигнатуру функции, мы понимаем, что она ожидает строковое значение и вернет булев-результат. Можно добавить, что здесь полезно было бы применить правильное наименование, однако эти type hints гарантируют, что значения будут именно указанных типов и предоставляют IDE возможность автодополнения и статического анализа с предупреждениями и другими полезными вещами.

С версии 7.4 PHP позволяет задавать и типизированные свойства для классов:

class Person {
    public string $firstName;

    public string $lastName;

    public int $age;

    public ?string $job;
}

Это означает, что у объектов Person будут строковые имя и фамилия, возраст в integer и допускающая пустое значение строка для должности. Чем больше классов — тем полезнее эта возможность.

Улучшения синтаксиса

В поздних версиях PHP появилось много синтаксических улучшений:

  • стрелочные функции: fn ($x, $y) => $x + $y;

  • оператор объединения с неопределенным значением: $value = $array['key'] ?? 'default value';

  • присваивание с неопределенным значением: return $cache['key'] ??= computeSomeValue('key');

  • расширение массивов: $first = ['a', 'b']; $second = ['c', 'd']; $final= […$first, …$second];

  • именованные аргументы: array_fill(start_index: 0, num: 100, value: 50);

  • разделитель числовых литералов: 299_792_458

Помимо синтаксических он содержит возможности для комплексных улучшений.

Constructor promotion

Взгляните на класс Person:

class Person {
    private string $firstName;

    private string $lastName; 

    protected int $age;

    public ?string $job;

    public function __construct(
        string $firstName,
        string $lastName,
        int $age,
        ?string $job
    ){
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        $this->age = $age;
        $this->job = $job;
    }
}

Вместо этого избыточно многословного кода PHP 8 поддерживает возможность написания такого кода:

class Person {
    public function __construct(
        private string $firstName,
        private string $lastName,
        protected int $age,
        public ?string $job
    ){}
}

Удобно, не так ли?

Nullsafe-оператор

Этот оператор существовал в некоторых других языках наподобие JavaScript, но PHP его не поддерживал. Взгляните на код, который я взял из документации PHP:

<?
if (is_null($repository)) {
    $result = null;
} else {
    $user = $repository->getUser(5);
    if (is_null($user)) {
        $result = null;
    } else {
        $result = $user->name;
    }
}

Именно так писали логику в старых версиях PHP для учета проверок на null. Новый nullsafe-оператор позволяет свести это к такому коду:

$result = $repository?->getUser(5)?->name;

Разве не великолепно?

Объединенные типы

Хоть для меня это и не самая любимая штука, она все равно ценна в случаях, когда уже есть несколько возможных типов без type hints. Объединения позволяют определять в качестве вариантов несколько типов значений. Благодаря объединениям становится работающим вот такой код:

function doSomething(int|string $value): bool|array {}

Обычно наличие нескольких возвращаемых типов сигнализирует о возможности улучшения кода, однако предыдущие версии PHP вообще не позволяли задавать в таких случаях типы, поэтому это все равно усовершенствование.

Производительность

У меня нет четких данных для сопоставления производительности с другими языками, но по сравнению с предыдущими версиями скорость работы кода PHP заметно выросла. В дополнение к рывку, сделанному PHP 7 по сравнению с PHP 5.6, все последующие релизы вносили улучшения, и эта тенденция продолжается. Проведенные Phoronix бенчмарки демонстрируют, что последняя версия PHP 8 в три с лишним раза быстрее PHP 5.6. В посте по ссылке выше есть подробные тесты, так что их стоит изучить.

В дополнение к этим бенчмаркам Kinsta также провела реальные испытания с WordPress. Вот результат для WordPress 5.3.

Точные цифры замеров таковы:

  • Результаты бенчмарка WordPress 5.3 с PHP 5.6: 97,71 запросов/с

  • Результаты бенчмарка WordPress 5.3 с PHP 7.0: 256,81 запросов/с

  • Результаты бенчмарка WordPress 5.3 с PHP 7.1: 256,99 запросов/с

  • Результаты бенчмарка WordPress 5.3 с PHP 7.2: 273,07 запросов/с

  • Результаты бенчмарка WordPress 5.3 с PHP 7.3: 305,59 запросов/с

  • Результаты бенчмарка WordPress 5.3 с PHP 7.4: 313,42 запросов/с

Пока в эти бенчмарки не включен PHP 8, однако видно, что даже версия 7.4 способна обрабатывать в три раза больше запросов по сравнению с 5.6, и это серьезное улучшение.

Вывод

В целом PHP за последние годы значительно улучшили, и с ним стало удобно работать. Я профессионально пишу на Golang, PHP и Python, но больше всего опыта у меня в PHP, поэтому я могу быть пристрастен. Однако, по моему мнению, PHP нашел идеальный баланс между гибкостью и поддерживаемостью. 

В нем есть необходимые инструменты для реализации безумных вещей, гибкая система типов, позволяющая постепенно улучшать легаси-код, он достаточно быстр для большинства применений, его продолжают совершенствовать, а еще у него потрясающее сообщество open source.

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