Comments 58
Union-типы — ужасная ошибка. Вместо них должны быть Generic Types, а их лучше бы и не вводить. Это ненормально, когда функция принимает непонятно что и возвращает непонятно что. Неудобно работать с функцией, которая возвращает тип вроде string|A, так как перед любым действием надо проверять этот тип.
Перегрузка операторов не нужна, так как вызывает WTF при чтении кода. Абсолютно неочевидно, что та или иная операция на самом деле будет вызывать какой-то магический метод.
Синтаксис атрибутов громоздкий и неудачный в сравнении с @attribute.
Null-safe оператор выглядит коряво — целых три символа. throw в выражении тоже коряво, не лучше ли тут было использовать if вместо тернарного оператора?
Куда-то PHP катится не туда. Что особенно печально, никто не отменяет старый синтаксис. И в итоге из языка, синтаксис которого можно изучить за пару дней, мы получаем язык с годами накопленными синтаксисами, где одну и ту же вещь можно сделать несколькими способами (match/switch), где есть куча оговорок и исключений. Тяжелое наследие Си тянется из года в годы.
Пример с иммутабельными переменными выглядит неудачно. Его, наоборот тяжело читать, так как там теперь 3 переменных с длинными названиями вместо одной. А если нам надо сделать 5 преобразований, заводить 5 переменных? Плохой пример, скорее отталкивающий. Не надо слепо тащить фичи из других языков, если непонятно какая от них выгода. Даром не нужны эти иммутабельные переменные (тем более, что в том же JS в такой иммутабельной переменной может быть ссылка на мутабельный объект, что вообще идиотски выглядит. Если уж называете что-то константой — так следуйте этой идее до конца, иначе это не константа, а полуконстанта и должно обозначаться не как const а как halfconst. Впрочем, это Яваскрипт, что с него возьмешь).
Union-типы — ужасная ошибка. Вместо них должны быть Generic Types, а их лучше бы и не вводить. Это ненормально, когда функция принимает непонятно что и возвращает непонятно что.
Union'ы — это как раз-таки способ превратить непонятно что в нечто гораздо более определённое и понятное. Между mixed
, в котором может быть что угодно, и int|string
— большая разница. Например, int|string
точно можно конкатенировать с другой строкой. int|float
— это вообще прекрасно — гарантирует корректную работу почти любой математики.
Вместо них должны быть Generic Types
Generic Types по факту можно сделать с помощью статического анализа, например, у Psalm'а очень неплохие возможности по этому поводу, Phan и phpstan, кажется, тоже умеют дженерики, и синтаксис у них, кстати, очень похожий. Может быть, в PHP 9 это просто сделают официальной частью языка, как с union и атрибутами, которые тоже выросли из аннотаций?
Перегрузка операторов не нужна
Тут я согласен с Вами и с сообществом, которое фичу не поддержало
Null-safe
Сахар он и есть сахар, равно как и объявление свойств в конструкторе, который выглядит для меня куда мене очевидным, чем ?->
Пример с иммутабельными переменными выглядит неудачно.
И здесь тоже согласен. Вот специфицировать тип переменной было бы полезно (по аналогии с типизацией свойств в 7.4)
В целом почти все замечания справедливы, но уж вывод какой-то слишком пессимистичный. Нелогичностей в PHP и так хватает, и, кстати, часть из перечисленного в этой статье уже исправлена. Ну и я не могу согласиться с тем, что синтаксис стал сильно сложнее для освоения новичками. Вот, например, строгий синтаксис для атрибутов позволяет не иметь головной боли с Doctrine 2, а просто пользоваться автокомплитом, и т. п.
На счет Generic, то все бы хотели этого. Но, как говорит Никита Попов, внедри они Generics, код станет намного медленней работать, даже если он не использует этот функционал. Не знаю что там у них в исходном коде твориться, но это очень странно.
Перегрузка операторов не нужна, так как вызывает WTF при чтении кода. Абсолютно неочевидно, что та или иная операция на самом деле будет вызывать какой-то магический метод.
Я добавил пример, посмотрите его пожалуйста.
Пример с иммутабельными переменными выглядит неудачно
Изменил пример. Надеюсь теперь более понятно что я хотел донести.
Union-типы — ужасная ошибка
Сильное заявление. Отчасти я согласен с вами. Как говорят разработчики пыхи, эту вещь они сделали не для написания нового кода, а для хоть какой-то типизации старого, где могут быть переданы разные типы параметров. Но это не решение первичной проблемы — отсутствия перегрузки методов. Люди бы не писали непонятный хлам в параметрах, если была бы перегрузка методов.
С другой стороны, в будущем будет сделать что-то вроде SomeInterface1&SomeInterface2
(параметр должен реализовать перечисленные интерфейсы). Это хорошо, когда мы следуем принципу ISP. Тогда у нас появляется много интерфейсов, и мы могли бы явно указать в параметре что должен уметь делать объект.
Синтаксис атрибутов громоздкий и неудачный в сравнении с @attribute.
Да, мне тоже больше по душе джавовский синтаксис.
Не знаю что там у них в исходном коде твориться, но это очень странно.
А что именно странно? Как еще проверки в рантайме можно сделать? Во многих других языках нет таких проверок, поэтому и не возникает проблема.
отсутствия перегрузки методовКоторый не имеет смысла в РНР из-за runtime types и, следовательно, невозможности статического диспатча.
Было бы хорошо, если бы этот Union был отдельным типом, но это в текущем синтаксисе невозможно и в целом принятое решение соответствует РНР. Например, Psalm под капотом по сути имеет только один тип — Union, который может содержать другие типы.
Почему было не починить, чтобы код из примера работал нормально и логично (как в других языках, например, в js)?
let $a = 2
console.log($a == 1 ? 'one'
: $a == 2 ? 'two'
: $a == 3 ? 'three'
: $a == 4 ? 'four'
: 'other') // two
Их черезчур много, язык деградировал.
Надо было наоборот всё делать строже, более читабельным и предсказуемым, а тут наоборот, неявностей ещё больше только добавили
Теперь он точно обречён на забвение.
Неочевидностей? Каких именно?
— определение свойств класса в конструкторе
— union types
— switch/match
2. нет совершенных IDE, которые все разрулят и все подскажут абсолютно правильно
2. согласен, но в случае php8 изменения простые, легко подхватываемые ide
- автоимплементация интерфейса Stringable
- новые правила приведения чисел и строк
- тернарные операторы
- изменение приоритета оператора конкатенации
Тоже, чтоль, написать статью о том, что в PHP8 появилось?.. А то все, кому не лень, уже написали, а я еще нет...
Как раз наоборот. Если раньше надо было смотреть и и в объявление и в конструктор, то теперь достаточно только конструктора.
А если придерживаться практики "один параметр — одна строка", то вообще сказка просто.
Правило «один параметр — одна строка» звучит, конечно, хорошо, но на практике будет просто каша из того и другого. Потому что не всегда все свойства объекта передаются в конструктор, так что часть будет объявлено отдельно, часть в конструкторе.
Наговнокодить можно на любом языке с любым синтаксисом. Так-то никто не мешает использовать старый подход там, где он оправдан.
а именно в объектах, все свойства которых передаются в конструктор
Что является признаком хорошего дизайна.
Сейчас вот протыркал 15 случайных классов, какие под руку попались и только в одном были какие-то дополнительные свойства, не выставляемые через конструктор. Но и тот оказался legacy помеченным под рефакторинг. (константы не в счёт)
Да, иногда нужно внутреннее изменяемое состояние, но довольно редко.
Таки да, получим «Fatal error: Uncaught Error: Typed property Point::$x must not be accessed before initialization». Прелестно.
Ровно ту же ошибку вы получите и сейчас, если обратитесь к типизированному, но не инициализированному свойству. Новый синтаксис конструктора (по сути сахар) тут вообще не при чём.
class Point {
public float $x = 0.0;
public float $y = 0.0;
public function __construct(float $x, float $y) {
$this->x = $x;
$this->y = $y;
}
}
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
) {}
}
Меня удивляет, что разработчики даже добавили ReflectionProperty::isPromoted() для «сахарных» свойств, но при этом вышеописанный квирк вообще нигде не упоминается, а это ещё даст о себе знать во всяких тестах и DTOшках.
Во-первых, аналогом второго будет не то, что вы написали, а вот это
class Point {
public float $x;
public float $y;
public function __construct(float $x = 0.0, float $y = 0.0) {
$this->x = $x;
$this->y = $y;
}
}
Во-вторых, то, что вы написали вообще не имеет смысла в обычных условиях. Максимально синтетический кейс для демонстрации newInstanceWithoutConstructor
.
И если вы используете такую инстанциацию на продакшене, то вариантов всего два: а) вы понимаете, что делаете и это действительно нужно и б) вы не понимаете, что
делаете и старшие товарищи будут больно бить по рукам на код-ревью
В первом случае разобраться с новым синтаксисом конструктора и его особенностями, кмк, не составит труда.
$Actions=[
['Action1', 'Arg1', 'Arg2'],
#['Action2', 'Arg1', 'Arg2'],
['Action3', 'Arg1', 'Arg2'],
];
И такого кода было много.
2) Очень не хватает многопоточности, хотя бы в cli
1) Что мешало сделать: Ctrl+R + #[
-> //[
?
2) Хотите многопоточность — читайте мануал по ОС и используйте, благо доступ к сисколлам есть из коробки, начиная с 7.4
А чего принципиально нового в этой статье по сравнению с десятками подобных?
С другой стороны это сломает все текущие пхп-доки, где mixed подразумевает null. Надеюсь, в 9 версии что-нибудь придумают с этим.
Проходное обновление, на 8 версию точно не тянет. имхо.
Параметры методов можно расширить, но нельзя сузить.
А кто может пояснить почему так? Я вот когда детям показывал ООП, приводил простейший пример:
Есть класс "Прямоугольник" с методом "Нарисуй". "Нарисуй" принимает два аргумента: ширину и высоту.
От его наследуется класс "Квадрат" который изменяет метод "Нарисуй" и принимает только один параметр (что логично).
Как подобное решается на PHP?
А еще лучше не наследовать квадрат от прямоугольника.
Вы же сами выше написали про нарушение L. Мы не можем использовать квадрат везде, где используется прямоугольник, поскольку квадрат — это частный случай прямоугольника с наложенными на него ограничениями, которых у прямоугольника нет. (А вот прямоугольник от квадрата можно отнаследовать кстати фигню написал)
То, что каждый квадрат – прямоугольник, не означает, что класс квадрата должен или может являться потомком класса прямоугольник. Наличие связи между объектами в реальном мире не означает наличия связи между представлениями этих объектов в программном коде.
Согласен. Разное количество параметров у методов базового класса и наследника убирает возможность динамической отправки (dynamic dispatch), что делает использование иерархии этих объектов более ограниченным: вызывающий код теперь обязан знать с кем он работает – с базовым классом или наследником...
В общем, как я понял, вся соль в том, чтобы сигнатура вызова в родительском и дочернем классах были совместимы. Мой пример в целом рабочий если мы говорим про JavaScript, так как там можно передавать в функцию больше параметров чем в ней объявлено. То есть
Square.draw(100, 100)
Не вызовет ошибки, не смотря на то, что метод ожидает только один аргумент. Таким образом вызывающий код может обрабатывать и "Квадрат" и "Прямоугольник" не зная точного типа.
Демо.
Многое, конечно, зависит от задачи. Но в вашем случае это может привести к путанице и потенциальным проблемам. Какой физический смысл есть у вызова
item.draw(100, 50)
для случая, если item
– это квадрат?
А вызывающий код, если он не знает, квадрат это или прямоугольник, вполне может себе вызвать и с такими параметрами. Откуда ваш квадрат знает, что вызывающему коду важнее – чтобы ширина была 100, или чтобы высота была 50?
либо (если уж так надо разделить квадрат и его ширину) — перенести метод «Рисовать()» в что-то вроде РисовальщикКвадрата и передавать параметры в его конструктор.
Хмм, а треугольник вы тоже наследуете от трапеции? Ведь трапеция с нулевой длиной основы — тоже треугольник. Это ведь разные фигуры. Зачем здесь наследование?
новый Квадрат(сторона)
новый Прямоугольник(ширина, длина)
новый Художник(какие-то параметры, диплом, опыт...)
Художник->нарисуй(Квадрат) //возможно так
Мой взгляд упал на ваш пример, про использование выражений в обьявлении свойств класса:
Как по мне, этого критически не хватает.
Вот пример кода, где это очень полезно:
```php
use Spatie\ModelStates\State;
abstract class OrderStatus extends State
{
public static string $name = static::getName();
abstract protected function getName(): string;
}
```
При первом обращении к $name, будет вызван метод getName финального класса. Это дает нам возможность настраивать какие значения будут попадать в поля в зависимости от каких-либо условий. А в данном примере это использовано с шаблоном «Template Method», и финальные классы обязаны предоставить нам значение для поля.
Мне, кажется те, кто предлагали эту идею, не планировали использовать ее так.
Пример, не совсем корректный, вы обьявляете статическое свойство, но значение по умолчанию описываете как результат работы метода экземпляра класса.
Либо уж используйте только статику везде, либо используйте
abstract class SomeClass
{
public string $name = static::getName();
abstract protected function getName(): string;
}
PHP 8 — Что нового?