Язык php часто ругают, обычно необоснованно. Особенно удивляет, что javascript ругают меньше. Зачастую это делают люди, которые писали на нем 10+ лет назад, когда язык был действительно чертовски плох, да и разработчики в те времена не задумывались над качеством кода. Посмотрите хотя бы на код wordpress, который до сих пор вызывает шок.

Ругают необоснованно, но проблемы у языка, конечно же, есть, и они серьёзные. Разуметеся, если сравнить последние релизы php7 (с нормальным ООП и строгим тайпхинтингом) и php4, то разница будет колоссальная. Однако и в последних версиях языка не всё гладко, и до java/c# пока что очень далеко. Более того, берусь утверждать, что будущее php тоже довольно сомнительно (с точки зрения типов).

Другими словами, давайте рассмотрим предметно, что хорошо и что плохо в php с точки зрения типизации.

Тайп хинтинги

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

Немного отвлечемся и посмотрим кусок кода на javascript:

    function filterUsersByAge(users, age) {
        alert("Hello World!");
    }

Что мы можем сказать об этой функции? Она берет каких-то пользователей и фильтрует их по возрасту. Но этого мало, потому что сразу возникают вопросы:

  • Что такое users? Массив? Или какой-то хитрый объект-коллекция?
  • Возраст задан как целое число или может быть дробным?
  • Может ли возраст быть null?
  • Возвращает ли эта фунция значение или же меняет переданный массив users?

Чтобы всё это понять, надо прочесть код функции, а также вызовы этой функции. Ошибки будет отловить сложно, потому что язык не будет ругаться ни на какие аргументы, а будет пытаться их как-то привести к нужному типу.

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

В PHP 7 добавлена возможность объявлять тип возвращаемого значения. Аналогично объявлению типов аргументов можно задать тип значения, которое будет возвращаться функцией. Типы, которые можно объявить для возвращаемых значений те же, что и для аргументов функций.

Допустимые значения:

  • Имя класса/интерфейса (Агрумент должен быть instanceof, что и имя класса или интерфейса. PHP 5.0.0)
  • self (Этот параметр должен быть instanceof того же класса, в методе которого он указан. Это можно использовать только в методах класса или экземпляра. PHP 5.0.0)
  • array (Аргумент должен быть типа array. PHP 5.1.0)
  • callable (Аргумент должен быть корректным callable-типом. PHP 5.4.0)
  • bool (Аргумент должен быть типа boolean. PHP 7.0.0)
  • float (Аргумент должен быть типа float. PHP 7.0.0)
  • int (Аргумент должен быть типа integer. PHP 7.0.0)
  • string (Аргумент должен иметь тип string. PHP 7.0.0)
  • iterable (Параметр должен быть либо массивом, либо экземпляром класса, реализующего Traversable. PHP 7.1.0)

Подробнее на php.net

Для сравнения код на последних версиях php начиная с версии PHP 7:

    function filterUsersByAge(array $users, ?int $age) : array {
        // ...
    } 

Тут мы видим, что на входе массив пользователей, возраст может быть null, возвращается также массив. Гораздо яснее, не так ли? Если же в нужных местах указать declare(strict_types=1), то при попытке пихнуть дробное число в качестве возраста, мы получим ошибку.

Вроде всё супер, но есть нюансы.

Нет дженериков

Мы смотрим на эту php-функцию filterUsersByAge и сходу не понимаем, массив чего нам пришел. Что именно за array? В java можно было бы написать List, и мы бы понимали, что к нам пришел список объектов User. Или Set, и мы бы сразу видели, что это список без повторов, т.е. только разные объекты. (Вообще, array в php — это странноватая смесь массива и HashMap, но это тема для отдельной статьи)

Нет уточнений для типа callable.

Пример функции:

    function reduce ( array $array, callable $callback )

Что за функция идет вторым аргументом? Что в ней должно быть?

Только в комментариях к коду мы можем понять, что там должно быть, к примеру, два аргумента. Кстати, есть четыре вида лжи: ложь, наглая ложь, статистика и комментарии к коду.

В некоторых языках, например в TypeScript, можно прописать прямо в объявлении функции:

    function fn(action: (a: string, b: number) => void)

Т.е. здесь в качестве аргумента action должна быть функция с двумя аргументами (строка и число), которая ничего не возрващает. Всё максимально явно, IDE и компилятор сразу скажут, если аргумент был какой-то не такой

Странности тайпхинтинга и типа возврата в связке с наследованием

    

Здесь получаем ошибку, что findById не совместим с findById из абстрактного класса.

Такой же пример в Java нормально компилируется:

    interface Entity {}

    class User implements Entity {};
    
    abstract class Repository {
        abstract public Entity findById();
    }
    
    class UserRepository extends Repository {
        public User findById(){
            return new User();
        }
    }

в TypeScript тоже можно:

    interface Entity {}

    class User implements Entity {}
    
    abstract class Repository {
        public abstract findById(): Entity;
    }
    
    class UserRepository extends Repository {
        public findById(): User{
            return new User();
        }
    }

На это дело время от времени появляются баг репорты, возможно будет исправлено когда-нибудь:

Фатальная проблема

Проблема в том, что PHP проверяет типы во время выполнения, а не во время компиляции. Потому что, не смотря на strict_types и type hintings, это ВНЕЗАПНО не строго типизированный язык

Отсюда следует два вывода:

  1. Чем больше проверок в рантайме, тем больше тормозов. Поэтому слишком сложные проверки навряд ли вообще когда-нибудь появятся. Многослойные дженерики и callable с callable аргументами просто положат рантайм. Также будут тормозить рантайм введение типов для членов класса и в других местах.
  2. Ошибки выявляются только во время запуска. Т.е. всегда будут ситуации, когда в какой-то хитрой ситуации пользователь сделает что-то не предусмотренное тестами, и всё повалится

Вместо выводов

Хотя (с точки зрения типов и ООП) на мой взгляд php на голову выше, чем javascript, и подходит для написания сложных программ, но при этом, конечно, не дотягивает до java / c# / typescript, и навряд ли когда-нибудь дотянется (см "Фатальная проблема"). Повторюсь, не дотянется именно с точки зрения системы типов, в остальных вещах возможны предпочтения в ту или иную сторону.

Поэтому в по-настоящему сложных приложениях надо обязательно всё обкладывать тестами. Также, возможно, что phpdoc добавит поддержку сложных callable с параметрами, и IDE научатся их понимать.