PHP может стать еще лучше

PHP слоник для привлечения внимания

Шутки про PHP — уже отдельный жанр в различных сообществах программистов. Некоторые не любят PHP, потому что {lang_name} намного лучше. А кого-то он вполне обоснованно расстраивает.

Я же PHP люблю. Не смотря на его косяки. Этот язык был создан для конкретной цели и решает он свою задачу хорошо. Схема «принял — обработал — отдал — умер» очень эффективна и решает проблему небольших утечек памяти.

В моей работе PHP используется постоянно. Так сказать, это основной backend язык, используемый в моих проектах. За время работы у меня появились некоторые пожелания и замечания. Решил поделиться с обществом. Кому интересно, добро пожаловать под кат.


Традиционный дисклеймер
Этот пост вряд ли изменит мир. Да и не об этом он. Пост, скорее, о наболевшем. Много субъективных моментов из разряда «я так считаю» или «я так хочу». Предупреждаю заранее, чтобы все были готовы.


1. Неявное временное преобразование примитива в объект

Проще объяснить на примере. В JavaScript мы можем обращаться к строке и массиву как к объекту.

let str = "1,2,3";

let arr = str.split(",");

arr = arr.map(_ => _ * 2);

console.log(arr); // [2, 4, 6]

Благодаря таким конструкциям, код выглядит чище, логичнее, есть возможность создавать цепочки вызовов и п.р.

В JavaScript, насколько мне это известно, массив уже является полноценным объектом, а строка хранится в виде примитива. И при необходимости, строка преобразуется в объект String, происходит вызов необходимого метода.

Почему бы в PHP не сделать что-то похожее?

$str = '1,2,3';

$arr = $str->explode(',');

$arr = $arr->map(function ($i) {
    return $i * 2;
});

var_dump($arr);

// Или даже так:

$str = '1,2,3';

$arr = $str
    ->explode(',');
    ->map(function ($i) {
        return $i * 2;
    });

var_dump($arr);

Десятки функций, связанных со строками и массивами можно было бы убрать в классы String и Array. Написание кода в функциональном стиле не был бы столь мучительным процессом.

$arr->keyExists(...);
$arr->map(...);
$arr->filter(...);
$arr->keys(...);
$arr->push(...);
$arr->pop(...);
$arr->exists(...); // in_array

$str->repeat(...);
$str->join(...);
$str->trim(...);
$str->replace(...);

Другая сторона этого вопроса: объектная реализация примитивов может имплементировать различные интерфейсы. Что нам это дает? Можно свободно избавляться от таких функций, как is_iterable, is_numeric, is_countable, и переходить на иcпользование instanceof \Countable, \Traversable и п.р. Ну разве не прекрасно?


2. Дженерики

Впервые с дженериками я познакомился, когда изучал Java. Мощь дженериков сложно переоценить. Они могут использоваться в DI контейнере, когда указываем тип объекта, который хотим получить:

$service = $container->get();

Они полезны при реализации различных коллекций, а также репозиториев (Yii2 Query или Doctrine).

Еще, какую пользу могли бы принести дженерики, это type hinting массивов объектов (этого уж очень не хватает). Опять же, проще объяснить не примере:

public function getItems(): Array

public function setItems(Array): void

Выглядит уже инетересно, согласитесь? Теперь и мы уверены, что всегда получим массив объектов определенного класса, и IDE точно знает, что подсказывать без всяких PHPDoc блоков. Но мы ведь можем не только массивы передавать, но и свои коллекции:

public function getItems(): Collection

public function setItems(Collection): void

Хотя синтаксис массива объектов как PHPDoc мне нравится больше (SomeObject[]), этот вариант тоже хорош. В любом случае это лучше, чем то, что есть сейчас (либо array, либо ничего).


3. Аннотации или декораторы

В Java аннотации используются с версии 1.6 (придуманы они были еще раньше). Доступны они через рефлексию. (Java знаю плохо, поэтому исправьте, если где-то не прав)
В JavaScript существует похожий механизм — декораторы (не уверен, что они являются частью стандарта, но уже повсеместно используются благодаря Babel).
А что есть в PHP? В PHP есть PHPDoc блоки, которые созданы сугубо для документирования. И разработчики выжимают из этого все, что могут. Примерами служат такие замечательные библиотеки, как Doctrine и PHP-DI.

Я об аннотациях задумался, когда подключил компонент Symfony Event Dispatcher. Когда я создаю класс-подписчик, который реагирует на события в системе, я должен имплементировать метод getSubscribedEvents, который возвращает массив, где в качестве ключа — идентификатор события, а в качестве значения — имя метода, реагирующего на событие в этом классе. Это простой и эффективный способ решения проблемы. Эффективный, но не удобный. Почему? У меня идентификаторы событий хранятся в константах классов. Если я хочу найти подписчиков на какое-то событие, я произвожу поиск по коду, который использует константу-идентификатор, меня перекидывает в метод getSubscribedEvents, где я беру название метода (ов), и ищу этот метод (ы) в классе. Если мне нужно узнать, на какое событие реагирует какой-либо метод, то мне нужно проделать ровно то же самое, только в обратном порядке. Слишком много шагов для столь простого действия. Опять же, если я хочу переименовать метод-подписчик, я не могу просто взять и воспользоваться возможностями IDE (поиск по тексту — функция редактора, а не IDE). Это придется делать вручную.
Но как оно могло бы быть, если бы существовали аннотации:

@EventSubscriber(ItemEvents::CREATE)
public function itemCreated(ItemEvent $event)

Как по мне, это очень удобно. Производя поиск по константе, я сразу увижу, какой метод его использует. Без всяких дополнительных действий. И наоборот: мне не нужно куда-то еще лезть, чтобы увидеть, на какое событие среагирует этот метод.

Из плюсов сразу хочется отметить поддержку IDE, если это будет внедрено в язык. Аннотации можно импортировать также, как и обычные классы, что избавляет от необходимости писать namespace’ы.

Это только то, что пришло в голову мне. А как преобразились бы библиотеки, о которых я говорил ранее, если подобный механизм будет внедрен…


4. Короткий синтаксис функций.

До знакомства с JavaScript при любых действиях с массивом (фильтрация, маппинг и п.р.) я использовал императивный подход:

$input = [...];
$output = [];

foreach ($input as $i => $item) {
    // logic
    $output[$i] = $item;  
}

После изучения ReactJS + Redux мне больше понравился функциональный подход к решению подобных задач:

let output = input.map(item => {
    // logic
    return item;
});

Но мне не нравится, как это выглядит в PHP:

$output = array_map(function($item) {
    // logic
    return $item;
}, $input);

А теперь давайте объединим первую идею с этой:

$output = $input->map(($item) => {
    // logic
    return $item;
});

Вот согласитесь, в PHP не хватает чего-то, что заменило бы function($item) {} на что-то более чистое и эллегантное. И совсем не обязательно, чтобы это что-то отличалось по функционалу от обычной записи (как это есть в JS). Главное, чтобы оно занимало меньше места и не создавало шум для глаз разработчика. Об этом уже говорилось ранее не мной. И даже есть такой RFC. Но это актуально, не так ли?


5. Асинхронность

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

В PHP может быть множество задач, которые хотелось бы сделать асинхронно, не дожидаясь окончания выполнения. На ум сразу же приходят работа с большими БД запросами и HTTP запросами. Чтобы составить большой отчет, приходится либо долго ждать, либо пользоваться сторонними решениями, типа очередей. В моем случае, в проекте в большом количестве используются Slack уведомления и Mailgun оповещения. За один клиентский запрос может быть отправлено около десятка HTTP запросов. Почему бы не запустить это все на фоне, а клиенту уже отдать страничку? Это возможно. Но простого решения из коробки нет.

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


6. Использование выражений везде

Например в значениях параметров по умолчанию:

class Money {
    __constuct($currency = new Currency('RUB')) {...}
}

Или даже в свойствах класса:

class Money {
    private $currency = new Currency('RUB');

    public function setCurrency(Currency $currency) {...}
}

А чтобы не засорять память, создавать объект при необходимости, первом обращении к свойству или если не был передан аргумент.

На самом деле, это всего лишь сахар, который чуть-чуть уменьшит объем кода в некоторых случаях, потому что можно сделать так:

class Money {
    private $currency;

    __constuct(Currency $currency = null) {
        $this->currency = $currency ?? new Currency('RUB');
    }
}

Использование выражений на создании объектов налету не заканчивается. Вместо этого можно было бы подставить любое выражение:

public function someMethod($value = $this->defaultValue): void

public $statuses = SomeClass::getStatuses();

Мне было бы очень удобно.


7. Указание типа переменным

Тут много написать не получится, так как (1) и так все понятно, (2) и так всем известно. Хочу сказать только то, что из PHP не обязательно делать язык со строгой типизацией. Достаточно только дать такую возможность. А дальше пусть каждый решает сам, как ему пользоваться этим. Но для тех, кто пришел с языков со строгой типизацией, PHP станет более привлекательным.


8. Убрать function

Не совсем понимаю необходимость ключевого слова function в указании методов класса.

public function someMethod()
// Или
public someMethod()

Это чисто субъективный момент. Но в тех языках, которые я знаю помимо PHP, а именно Java и JavaScript, я этого не наблюдаю. Действительно, а зачем? Семантически это слово никакой роли не играет. Я и без того пойму, где функция, а где свойство. Но кажется, что убрав лишние 9 символов, будет немного больше места для аргументов и меньше визуального шума. А это небольшой, но плюс.


9. Навести порядок

Беспорядок, пожалуй, самый весомый аргумент против языка. Сложно отрицать, что в PHP много странных непоследовательных решений, в отношении названия функций, порядка аргументов и п.р. Всё понимаю: обратная совместимость, «исторически сложилось», влияние других языков. Но нужно двигаться дальше!
Как вариант, можно большинство функций перенести внутрь объектов, как я описал в самом первом пункте, остальные реализовать в качестве статических методов этих классов (например Array::method()), а все остальное пометить как deprecated. А в следующей версии повыкидывать все!

Как по мне, PHP неплохо развивается как ОО язык, и большинство функций, констант и п.р. можно переместить в классы (json_encode -> Json::encode, cUrl).
Если бы кто-нибудь сделал расширение, которое решает эти проблемы, переносит все функции в статичные методы классов, написал полифил, если вдруг расширение не установлено, и для Codesniffer написал варнинги при использовании старых функций… Я бы уже сейчас стал использовать его. Мне кажется, что не только я. Вопрос только в том, что это должно быть именно расширение, которое максимально эффективно прокидывает аргументы из метода в нативную функцию.


Вместо заключения

Легко рассуждать, сидя за диваном (компьютерным столом) и жалуясь на недостатки языка, который помогает мне зарабатывать на жизнь. Но я не жалуюсь. Я очень благодарен всем тем, кто вкладывается в развитие языка и его экосистемы. Но ничто не мешает мне высказывать свои мысли и пожелания. Как я уже говорил, я люблю PHP и хочу, чтобы он становился еще лучше.

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

Еще что хочу сказать. Это все субъективно. Это то, что хочу конкретно я. К тому же, на мое мнение повлияло изучение Java. И мне понравились некоторые возможности этого языка. Но это вовсе не говорит о том, что так надо или что это единственно верное решение.

А что Вы хотели бы увидеть или изменить в PHP?

© Habrahabr.ru