Pull to refresh

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, который может содержать другие типы.
Вот пункт 12 меня прям разочаровал.
Почему было не починить, чтобы код из примера работал нормально и логично (как в других языках, например, в js)?
let $a = 2
console.log($a == 1 ? 'one'
     : $a == 2 ? 'two'
     : $a == 3 ? 'three'
     : $a == 4 ? 'four'
              : 'other') // two

Потому что это вызовет самые разные баги в существующем коде, который полагается на старую логику. Сначала все переходят на 8-ю версию, легко находят проблемные места благодаря Fatal error и чинят. В 9-й уже можно будет делать правильную логику.
Сначала все переходят на 8-ю версию, легко находят проблемные места благодаря Fatal error и чинят.

Или не чинят, потому что не все пути исполнения покрыты тестами.

В 8й версии добавили слишком много неочевидностей.
Их черезчур много, язык деградировал.
Надо было наоборот всё делать строже, более читабельным и предсказуемым, а тут наоборот, неявностей ещё больше только добавили
Теперь он точно обречён на забвение.
Как минимум
— определение свойств класса в конструкторе
— union types
— switch/match
IDE все неочевиности разрулит, как разурливает в js, golang и других
1. в Go нет таких этих неочевидностей
2. нет совершенных IDE, которые все разрулят и все подскажут абсолютно правильно
1. без ide или тулзов вы не узнаете какие интерфейсы реализовли и реализовали-ли вообще. не говорю что это плохо, но без явного указания имплементация интерфейсов неочивидна (вы явно это на структуре не видите)
2. согласен, но в случае php8 изменения простые, легко подхватываемые ide
Чем более неочевидные вещи позволяет делать язык, ьем больше мест для ошибок. IDE и прочее это способ решения проблем, которых могло не существовать вовсе.
+ к уже написанному:
  • автоимплементация интерфейса Stringable
  • новые правила приведения чисел и строк
  • тернарные операторы
  • изменение приоритета оператора конкатенации

Тоже, чтоль, написать статью о том, что в PHP8 появилось?.. А то все, кому не лень, уже написали, а я еще нет...

Все здорово, кроме, сахара в конструкторе. Это заметно усложнит чтение кода. Если раньше достаточно было посмотреть начало класса (а обычно там пишут определение свойств), то теперь надо смотреть два места.

Как раз наоборот. Если раньше надо было смотреть и и в объявление и в конструктор, то теперь достаточно только конструктора.
А если придерживаться практики "один параметр — одна строка", то вообще сказка просто.

А зачем надо смотреть в конструктор — там логика инициализации свойств объекта, это другое дело. Я говорю про просмотр самого набора свойств, а не про то как именно они инициализируются.

Правило «один параметр — одна строка» звучит, конечно, хорошо, но на практике будет просто каша из того и другого. Потому что не всегда все свойства объекта передаются в конструктор, так что часть будет объявлено отдельно, часть в конструкторе.

Наговнокодить можно на любом языке с любым синтаксисом. Так-то никто не мешает использовать старый подход там, где он оправдан.

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

а именно в объектах, все свойства которых передаются в конструктор

Что является признаком хорошего дизайна.
Сейчас вот протыркал 15 случайных классов, какие под руку попались и только в одном были какие-то дополнительные свойства, не выставляемые через конструктор. Но и тот оказался legacy помеченным под рефакторинг. (константы не в счёт)


Да, иногда нужно внутреннее изменяемое состояние, но довольно редко.

А что будет происходить с таким классом при вызове ReflectionClass::newInstanceWithoutConstructor? Свойства, объявленные в конструкторе, вообще не появятся?

Таки да, получим «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.


И если вы используете такую инстанциацию на продакшене, то вариантов всего два: а) вы понимаете, что делаете и это действительно нужно и б) вы не понимаете, что
делаете и старшие товарищи будут больно бить по рукам на код-ревью


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

у них же выходной там. Кто будет сегодня релизить восмерку
1) Из-за аттрибутов типа #[...] пришлось переписать много кода, где были закомментированы части конфигов на php с помощью #, например:
$Actions=[
  ['Action1', 'Arg1', 'Arg2'],
 #['Action2', 'Arg1', 'Arg2'],
  ['Action3', 'Arg1', 'Arg2'],
];

И такого кода было много.
2) Очень не хватает многопоточности, хотя бы в cli

1) Что мешало сделать: Ctrl+R + #[ -> //[?
2) Хотите многопоточность — читайте мануал по ОС и используйте, благо доступ к сисколлам есть из коробки, начиная с 7.4

1) Когда это в одном файле на одном компьютере — это легко, а когда множество проектов, это не так быстро.
2) Это всё будут костыли, которые не везде и не всегда будут работать.
UFO just landed and posted this here

А чего принципиально нового в этой статье по сравнению с десятками подобных?

ИМХО не стоило null в mixed включать. Столько проблем от него, безопаснее было бы его явно указывать mixed|null
С другой стороны это сломает все текущие пхп-доки, где mixed подразумевает null. Надеюсь, в 9 версии что-нибудь придумают с этим.
Null таки должен входить в top type, т.к. это значение (хоть и единственное у этого типа).
Такое ощущение, что php возвращается обратно к говнокоду. Я не могу представить адекватные ситуации в которых понадобились бы смешанные типы. Уважаемые разработчики, вы что нибудь о единой ответственности слышали? Из-за ваших "?" в возвращаемых значениях теперь приходится каждый раз на is_null проверять, теперь вы сделали mixed, который практически эквивалентен «этот код писал индус, который может вернуть bool|sting|int или один из 10 000 объектов в вашем коде»
Проходное обновление, на 8 версию точно не тянет. имхо.
Это нужно в РНР из-за принципиального отсутствия ad-hoc полиморфизма (проявляющегося в других ЯП, например, в перезагрузке функций). В противном случае нужно было бы либо использовать неограниченный mixed type и надеяться только на себя, либо придумывать по новому названию для каждого сочетания функции и типа.
Параметры методов можно расширить, но нельзя сузить.

А кто может пояснить почему так? Я вот когда детям показывал ООП, приводил простейший пример:
Есть класс "Прямоугольник" с методом "Нарисуй". "Нарисуй" принимает два аргумента: ширину и высоту.
От его наследуется класс "Квадрат" который изменяет метод "Нарисуй" и принимает только один параметр (что логично).
Как подобное решается на PHP?

Ваше объяснение нарушает O и L из SOLID. Лучше в данном случае вообще убрать параметры из Нарисуй

А еще лучше не наследовать квадрат от прямоугольника.

Вопрос философский, ведь каждый квадрат — прямоугольник, но не каждый прямоугольник — квадрат.

Вы же сами выше написали про нарушение L. Мы не можем использовать квадрат везде, где используется прямоугольник, поскольку квадрат — это частный случай прямоугольника с наложенными на него ограничениями, которых у прямоугольника нет. (А вот прямоугольник от квадрата можно отнаследовать кстати фигню написал)

Дополню, что квадрат является не только частным случаем прямоугольника, но и частным случаем ромба. Поэтому в принципе неправильно рассматривать квадрат как частный случай чего-то. Квадрат это самостоятельная фигура со своими, специфичными только для квадрата, свойствами.

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

Наличие или отсутствие такой связи должно определяться в каждом конкретном случае. Я же говорю, вопрос философский. Но вот разное количество параметров в «нарисуй меня» — это почти всегда неправильно, если прямоугольник и квадрат используются для объяснения ООП. В классических учебниках, вроде, как раз интерфейс «фигура», а у него уже «Нарисуй меня» и т.п.

Согласен. Разное количество параметров у методов базового класса и наследника убирает возможность динамической отправки (dynamic dispatch), что делает использование иерархии этих объектов более ограниченным: вызывающий код теперь обязан знать с кем он работает – с базовым классом или наследником...

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


Square.draw(100, 100) 

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


Демо.

Многое, конечно, зависит от задачи. Но в вашем случае это может привести к путанице и потенциальным проблемам. Какой физический смысл есть у вызова


item.draw(100, 50) 

для случая, если item – это квадрат?


А вызывающий код, если он не знает, квадрат это или прямоугольник, вполне может себе вызвать и с такими параметрами. Откуда ваш квадрат знает, что вызывающему коду важнее – чтобы ширина была 100, или чтобы высота была 50?

Вам надо либо сделать ширину и высоту свойствами класса (иначе у вас получается, что можно создать объект Квадрат без указания ширины, что нелогично),
либо (если уж так надо разделить квадрат и его ширину) — перенести метод «Рисовать()» в что-то вроде РисовальщикКвадрата и передавать параметры в его конструктор.

Хмм, а треугольник вы тоже наследуете от трапеции? Ведь трапеция с нулевой длиной основы — тоже треугольник. Это ведь разные фигуры. Зачем здесь наследование?

Мне, кажется, что прямоугольник, что квадрат не должны иметь метод нарисуй. Этим должен заниматься Художник. А наследовать квадрат от прямоугольника тоже не очень, нужен базовый класс — геометрическаяФигура, от которого всё происходит. У Художника метод нарисоватьПрямоугольник принимает один или 2 параметра, и, если один параметр на входе, то рисуем квадрат. Хотя, наверно, входным парметром должен быть сам объект… хм
новый Квадрат(сторона)
новый Прямоугольник(ширина, длина)
новый Художник(какие-то параметры, диплом, опыт...)
Художник->нарисуй(Квадрат) //возможно так
Увы, но так и не раскрыли архиважную тему для этого языка: Embedding его из других языков (того же C/C++) без извращений. Т.е., напрямую, прилинковать библиотеку, инициализировать, прочитать файл исходника и скормить в функцию, получить выхлоп.

Мой взгляд упал на ваш пример, про использование выражений в обьявлении свойств класса:


Как по мне, этого критически не хватает.
Вот пример кода, где это очень полезно:
```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;
}

Да, там была опечатка. Я имел в виду статический метод:


abstract protected static function getName(): string;

Уже поправил.

И все же, что вы думаете по этому поводу?

Да, это было бы удобно.

Есть пара кейсов, где мне это было нужно.

Но, мы обходили это через конструкторы.
Sign up to leave a comment.

Articles