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?