Хабрахабр

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

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

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

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

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

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

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

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

let str = "1,2,3"; let arr = str.split(","); arr = arr.map(_ => _ * 2); console.log(arr); // [2, 4, 6]

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

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

Почему бы в 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<SomeService>();

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

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

public function getItems(): Array<Item> public function setItems(Array<Item>): void

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

public function getItems(): Collection<Item> public function setItems(Collection<Item>): void

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

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

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

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

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

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

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

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

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;
});

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

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

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

На ум сразу же приходят работа с большими БД запросами и HTTP запросами. В PHP может быть множество задач, которые хотелось бы сделать асинхронно, не дожидаясь окончания выполнения. В моем случае, в проекте в большом количестве используются 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. Указание типа переменным

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

8. Убрать function

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

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

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

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

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

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

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть