Пых
8.27K subscribers
262 photos
14 videos
6 files
569 links
Блог Валентина Удальцова о разработке на PHP.

Хобот @phpyhobot
https://youtube.com/@phpyh
https://vkvideo.ru/@phpyh
https://t.me/isPHPdying

Статистика: https://t.me/INOTAROBOT?start=st1219340804

Для связи используйте личные сообщения канала.
Download Telegram
Пых
💙 Typhoon 0.4.0 https://github.com/typhoon-php/typhoon/releases/tag/0.4.0 Вчера ночью, ровно через 5 месяцев после 0.3.0, поставил следующий минорный тег. По сути, он, конечно, мажорный, так как 0.y релизы могут ломать обратную совместимость, чем мы не преминули…
💙 Typhoon 0.4. self, parent, static

В документации PHP self, parent и static называются относительными типами классов (relative class types). С этой троицей мы боремся с самого начала. Каждый релиз — новый раунд.

Раунд 1

Возьмём пару классов:


class A extends stdClass
{
public function self(): self { return $this; }
public function parent(): parent { return $this; }
public function static(): static { return $this; }
}

final class B extends A {}


С self и parent здесь всё просто и однозначно: в классе A self = A, parent = stdClass, в класс B копируем эти 2 метода с уже разрешёнными типами A и stdClass. Значит, self и parent можно не оформлять как самостоятельные типы, а сразу заменять на конкретные классы из скоупа.

Можем ли мы так же разрешить static? Нет, так как это имя не текущего, а вызываемого класса (см. позднее статическое связывание): в A static резолвится как A, а в B — как B. При построении рефлексии мы должны в каждом унаследованном методе обновить static с учётом текущего скоупа. Для этого замоделируем static как first-class тип, который знает, где он сейчас находится, и получим typhoon/type 0.2:


A,B::self() возвращают types::object(A::class)
A,B::parent() возвращают types::object(stdClass::class)
A::static() возвращает types::static(A::class)
B::static() возвращает types::static(B::class)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥2😐1
Пых
💙 Typhoon 0.4. self, parent, static В документации PHP self, parent и static называются относительными типами классов (relative class types). С этой троицей мы боремся с самого начала. Каждый релиз — новый раунд. Раунд 1 Возьмём пару классов: class A…
💙 Typhoon 0.4. self, parent, static.

В Typhoon 0.3 я начал добавлять поддержку трейтов. И всё, естественно, поломалось. 🙈

Раунд 2


trait T
{
public function self(): self { return $this; }
public function parent(): parent { return $this; }
public function static(): static { return $this; }
}


Валидный ли это код? Да. Вот только ни один из трёх типов невозможно отрезолвить так же, как мы это делали выше. self и parent не на что заменить, так как трейт сам по себе не является типом и не может наследовать другие. Для static отсутствует скоуп-класс.

Нативная рефлексия, кстати, вообще не парится: всегда возвращает ReflectionNamedType с 'self', 'parent' или 'static'. Мы так делать не хотим, потому что это просто откладывание проблемы на потом.

Постепенно приходит понимание, что в трейтах относительные типы работают как плейсхолдеры. В Psalm и PHPStan их даже можно ограничивать через аннотации @require-extends и @require-implements. Всё это напоминает... дженерики!

Действительно,


/**
* @psalm-require-extends stdClass
* @phpstan-require-extends stdClass
*/
trait T
{
public function self(): self { return $this; }
public function parent(): parent { return $this; }
public function static(): static { return $this; }
}

final class A extends stdClass
{
use T;
}


можно с натяжкой переписать как


/**
* @template self of object
* @template parent of stdClass
* @template static of self
*/
trait T
{
public function self(): self { return $this; }
public function parent(): parent { return $this; }
public function static(): static { return $this; }
}

final class A extends stdClass
{
/**
* @use T<self, parent, self>
*/
use T;
}


Выглядит логично, но кривовато, потому что в реальности это не объявленные пользователем дженерики, а какие-то встроенные, да ещё и доступные в статике. Тем не менее, на тот момент идея мне очень нравилась, и, подставив пару костылей, я её реализовал в typhoon 0.3 и даже гордо рассказал про это на Стачке:


types::template('self', types::atClass(T::class))
types::template('parent', types::atClass(T::class))
types::template('static', types::atClass(T::class))
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥6😐1
Пых
💙 Typhoon 0.4. self, parent, static. В Typhoon 0.3 я начал добавлять поддержку трейтов. И всё, естественно, поломалось. 🙈 Раунд 2 trait T { public function self(): self { return $this; } public function parent(): parent { return $this; } public…
💙 Typhoon 0.4. self, parent, static

Есть у меня одна черта: я периодически возвращаюсь ко всем своим костылям, пока не сделаю нормально или не уволюсь. О том, что для относительных типов не стоит использовать дженерики, кричали разные куски кода, но всегда казалось, что будет слишком жирно делать их first-class. А потом я задумался над тем, как работают с этими типами анонимные функции.

Раунд 3


final class A extends ArrayObject
{
public function self() { return fn (): self => new self(); }
public function parent() { return fn (): parent => new parent(); }
public function static() { return fn (): static => new static(); }
}

final class B extends stdClass {}

$self = (new A())->self();
echo $self()::class; // A
echo $self->call(new B)::class; // B

$parent = (new A())->parent();
echo $parent()::class; // ArrayObject
echo $parent->call(new B)::class; // stdClass

$static = (new A())->static();
echo $static()::class; // A
echo $static->call(new B)::class; // B


Получается, что self, parent и static отлично себя чувствуют в анонимных функциях и, как и в трейтах, резолвятся в зависимости от скоупа, в том числе поддерживают его изменение в рантайме. Посмотрев на этот сниппет, мы наконец-то согласились, что относительные типы класса везде в PHP ведут себя логично и единообразно и должны быть замоделированы как first-class типы.

Теперь в typhoon/type 0.4 можно создавать self, parent и static с классом скоупа и без него. Последнее как раз требуется в трейтах и непривязанных анонимных функциях. В TypeVisitor каждый относительный тип обрабатывается индивидуально. Ну и, конечно, поддерживаются аргументы типов (а-ля self<X, Y>). Комбо-пример:


trait T
{
/**
* @return array{self, parent, static}
*/
public function types(): array
{
return [$this, $this, $this];
}
}

abstract class A extends stdClass
{
use T;
}

final class B extends A {}

$type = TyphoonReflector::build()
->reflectClass(B::class)
->methods()['types']
->returnType();

echo stringify($type);

// array{self@A, parent@stdClass, static@B}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥3🤔2
👨‍🏫 Хардкорный курс PHP, 4-й поток

Традиционно после 0.X релиза Typhoon я запускаю X-й поток курса! Сегодня X = 4.

Набор на четвёртый поток будет осуществляться как обычно. Завтра (в среду, 7 августа) в 15:00 по Москве на канале Пых появится ссылка на срез знаний. Необходимо его пройти, чтобы попасть на курс. При проверке я сначала отберу анкеты с правильными ответами, а затем отранжирую их по времени. Удачи!

Обновлённая страничка курса: hardcorephp.ru
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥36👍165🥱3😢1
Пых
👨‍🏫 Срез знаний для 4-го потока Хардкорного курса PHP! https://forms.gle/PFvHT7vujHEPuSs29 Удачи! 😉️️️️️️
🤨 Задачка со звёздочкой

Теперь, когда первая сотня абитуриентов прислала анкеты, предлагаю всем подумать над внеконкурсным заданием. Решите 5-ю задачу с теми же вводными в обратную сторону. То есть у вас должен получиться класс, который, наоборот, позволит использовать Psr\Middleware как Symfony\Subscriber. Идеи можно обсуждать в комментариях к этому посту. Позже всё разберём на стриме.

Gist с полной формулировкой этого варианта задачи: https://gist.github.com/vudaltsov/dc4f372692d2eabbc8c3d29cd4de0ccd

Навеяно, кстати, весьма драматическими событиями. В 2018-м году сообщество Symfony активно обсуждало невыполнение PHP-FIG своей "framework interoperability" миссии, так как компоненты HttpFoundation и HttpKernel нельзя малой кровью адаптировать под PSR-7. Апогеем стал pull-request Фабьена "Remove Symfony" в PHP-FIG. Тогда же у Symfony появился никому не нужный альтернативный набор контрактов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82🔥2
Пых
🤨 Задачка со звёздочкой Теперь, когда первая сотня абитуриентов прислала анкеты, предлагаю всем подумать над внеконкурсным заданием. Решите 5-ю задачу с теми же вводными в обратную сторону. То есть у вас должен получиться класс, который, наоборот, позволит…
🎉 Под занавес дня задача поддалась!

Сначала Вадим верно подметил, что суть решения сводится к приостановке потока управления внутри Middleware и предложил использовать для этого генераторы. Затем Александр скинул прототип на базе файберов.

Ну а вот полное решение с использованием Fiber и WeakMap, которое я зафиксировал вскоре после публикации задачи: код, 3v4l. Обсудим подробнее на разборе среза знаний!

P.S.: Всем спокойной ночи!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥22👍103🤮1
Benchmarking Laravel with Swoole, FrankenPHP, RoadRunner, php-fpm, and ngx-php

https://youtu.be/ZB129Tjkas8
https://github.com/pronskiy/ngx-php_laravel

Позавчера Рома Пронский выложил новый ролик, в котором он забенчмаркал запуск Laravel через основные популярные рантаймы (php-fpm, RoadRunner, Swoole, FrankenPHP) + малоизвестный проект ngx-php, который по результатам Web Framework Benchmarks оставляет позади даже Swoole и workerman.
👍21🔥167
Пых
👨‍🏫 Срез знаний для 4-го потока Хардкорного курса PHP! https://forms.gle/PFvHT7vujHEPuSs29 Удачи! 😉️️️️️️
😂 Срез знаний Кирилла Несмеянова!

Назови несколько принятых в PHP 8.4 изменений.
Изменено значение у констант PHP_VERSION и PHP_MINOR_VERSION

Почему некоторые расширения, например, ext-pcntl, в composer.json принято прописывать с констрейнтом "*"?
Звёздочка обычно означает примечание, сноску на полях. Возможно, для таких расширений автор прописывает потом примечания автора. Ну, типа, "добавлю-ка я pcntl, сорян виндузятники, но вы идёте лесом".

По какому принципу выбраны значения констант ReflectionProperty::IS_*?
Ребята решили ради прикола написать 1 << 1, 1 << 2, 1 << 3, и т.д., чтобы никто не понял что это значит.

Можно ли расширить возвращаемый тип метода в дочернем классе и почему?
Конечно можно! Ни одной статьи в Конституции РФ или Кодексах за нарушение подобного не предусмотрено. Да и это не такое порицаемое обществом занятие, чтобы ещё за него кто-то осуждал. Всё в рамках приличия.

Как соотносятся понятия "полиморфизм" и "наследование"?
Они оба на написаны кириллицей и в кавычках (только "наследование" хуже, т.к. в нём 12 букв, а "полиморфизм" в этом плане меньше весит, т.к. всего 11 букв).

Интересный факт: Если написать эти слова наоборот, то получится "мзифромилоп" и "еинаводелсан", но их размер не изменится. А ещё из букв в слове "полиморфизм" можно составить слово "зоофил", а из слова "наследование" можно составить "лениновед".

Теперь ты тоже будешь знать это!

Реши задачу.

final readonly class SymfonyIntegrator
{
public function integrate(): void
{
$previous = \getcwd();

try {
\chdir(__DIR__);

\copy('https://getcomposer.org/installer', __DIR__ . '/composer-setup.php');

require __DIR__ . '/composer-setup.php';

new \Symfony\Component\Process\Process([
\PHP_BINARY,
__DIR__ . '/composer.phar',
'create-project',
'symfony/skeleton',
])->run();

// Больше нам PSR фреймворк не нужен
} finally {
$previous && \chdir($previous);
}
}
}


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

В чём оптимизм оптимистичной блокировки?
Разработчики надеются, что в конце-концов когда-нибудь блокировка разблокируется, всё наладится и все будут жить долго и счастливо.

Что ждёшь от курса?
Ну хотя бы доллар по 25, как в началах 2000х. Был бы норм курс.
Please open Telegram to view this post
VIEW IN TELEGRAM
😁151👍22🤡12👏42🥴2🦄1
👨‍🏫 Набор на 4-й поток Хардкорного курса завершён!

Все студенты получили письма с приглашениями на почту.

Немного статистики и наблюдений:
108 ответов на момент написания этого поста.
▸ 20 человек набралось на 64-й анкете (в прошлый раз на 41-й), она была отправлена через 54 минуты после старта.
▸ Как будто бы в этот раз вы чаще прибегали к помощи чат-ботов. Некоторые ответы были слишком вышколенными и высокопарными. Программист, который на скорость проходит опрос, так никогда не напишет. В решении 5-й задачи некоторые даже оставили типовые комментарии. В общем, когда я видел явные признаки использования ИИ, я переходил к следующему ответу. И наоборот, к тем, кто по-человечески ошибался в тексте или коде, я был более внимателен.

Сегодня (9 августа) в 19:00 разберём срез и задачу со звёздочкой, приглашаю всех на стрим!

https://youtu.be/wGegvTFidaA

P.S.: Форму не буду закрывать в демонстрационных целях. Заполняйте сколько хотите или используйте на собесах.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🔥81👎1🤡1
☠️ eval

Думаю, никому не нужно представлять языковую конструкцию eval. В 2024 все пыхари знают её как древнее могущественное зло, которое ни при каких условиях не должно оказаться на проде. Если в проекте есть хоть один eval, он автоматически считается 💩.

Такое отношение к eval непоследовательно. Если вы используете Symfony Dependency Injection, Doctrine ORM, Ocramius Proxy Manager или любой другой пакет с кодогенерацией, вы фактически используете eval, только завуалированный. С точки зрения безопасности нет никакой разницы между тем, чтобы выполнить код из строки, и тем, чтобы сначала записать строку в файл, а затем его выполнить.

Что на самом деле важно

1. При использовании eval и кодогенерации нужно 1000 раз убедиться, что входные данные либо вообще не могут попасть в код, либо строго санитизируются и правильно интерполируются с использованием var_export() или Typhoon Exporter.

2. Код, исполняемый через eval, require и include получает доступ к текущей области видимости и переменным. Для изоляции можно обернуть вызов в статическое замыкание, см. пример.

На десерт. В коде Symfony 7 есть eval. И не один... А как минимум 3... Уверен, что почти в каждом фреймворке есть. Поищите и скиньте в комментарии.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍54🔥10🤔72🌚2🙏1👀1
▶️ PHP-линч #22

Пора возобновить наши встречи! Жду всех в эту среду в 18:30 по Москве.

Одна заявка на линч у меня уже есть от Сергея Предводителева: vjik/telegram-bot-api. Пишите в комментариях, что ещё хотите посмотреть.

https://youtu.be/zuGtL5u2lsY
Please open Telegram to view this post
VIEW IN TELEGRAM
5🔥45👍7
👍7
Podlodka PHP Crew «Практическая архитектура»

На следующей неделе стартует пятая Podlodka PHP. Программа:

• Упорядочиваем хаос: архитектурная документация по модели C4 / Кирилл Сурогатов
• Как стать x2 разработчиком, используя явное состояние / Кирилл Мокевнин
• Нам уже нужен CQRS? А теперь? / 💙 Валентин Удальцов
• PHP + Prometheus: Работа с кастомными метриками / Алексей Солодкий
• RabbitMQ vs Kafka: обрабатываем большие объёмы / Михаил Каморин
• Построение качественных банковских интеграций на PHP / Дмитрий Кириллов
• PHP будет долго жить: переезжаем на Roadrunner / Владимир Плахотников
• Нет ничего более постоянного, чем Temporal / 🔥 Павел Бучнев, Алексей Гагарин
• Перевозим приложение на PHP в Kubernetes / Вадим Дворовенко

Сегодня последний шанс купить билет по сниженной цене. По промокоду phpyh5 можно получить дополнительную скидку 500 руб.

https://podlodka.io/phpcrew
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍21🔥111🥴1
Контекст в PSR-3 Logger

Периодически сталкиваюсь с неправильным использованием $context в PSR логгере. Давайте вместе внимательно перечитаем стандарт.

1.3.1 Every method accepts an array as context data. This is meant to hold any extraneous information that does not fit well in a string. The array can contain anything. Implementors MUST ensure they treat context data with as much lenience as possible. A given value in the context MUST NOT throw an exception nor raise any php error, warning or notice.


Жирным выделил то, что многие упускают: вы можете передавать в контекст абсолютно любые значения, в том числе массивы, объекты, замыкания и ресурсы. Не нужно самостоятельно проводить нормализацию. По стандарту это задача логгера, а не ваша. Вот как, например, Monolog обработает контекст со сложными элементами:


$logger = new Monolog\Logger(
name: 'app',
handlers: [new Monolog\Handler\StreamHandler(STDOUT)],
processors: [new Monolog\Processor\PsrLogMessageProcessor()]
);

final readonly class Foo
{
public function __construct(
public string $public = 'bar',
private string $private = 'baz',
) {}
}

$logger->debug('Message', [
'birthday' => new DateTimeImmutable('06.04.2019'),
'object' => new Foo(),
'closure' => static fn (): int => 1,
'resource' => STDIN,
]);


[2024-09-27T13:02:16.303651+00:00] app.DEBUG: Message {"birthday":"2019-04-06T00:00:00+00:00","object":{"Foo":{"public":"bar"}},"closure":{"Closure":[]},"resource":"[resource(stream)]"} []


1.3.2 If an Exception object is passed in the context data, it MUST be in the 'exception' key. Logging exceptions is a common pattern and this allows implementors to extract a stack trace from the exception when the log backend supports it. Implementors MUST still verify that the 'exception' key is actually an Exception before using it as such, as it MAY contain anything.


Исключения нужно добавлять в контекст с ключом 'exception'. Никогда не раскладывайте объект исключения на массив вида ['message' => $e->getMessage(), 'line' => $e->getLine(), ...] — так вы не даёте процессорам и форматтерам самим принять решение относительно состава лога исключения и глубины его нормализации.


$logger->critical('Oops!', [
'exception' => new LogicException(
message: 'Level 2',
previous: new RuntimeException('Level 1'),
),
]);


[2024-09-27T13:14:04.880133+00:00] app.CRITICAL: Oops! {"exception":"[object] (LogicException(code: 0): Level 1 at test.php:27)\n[previous exception] [object] (RuntimeException(code: 0): Level 2 at test.php:29)"} []


1.2.2 The message MAY contain placeholders which implementors MAY replace with values from the context array.
Placeholder names MUST correspond to keys in the context array.
Placeholder names MUST be delimited with a single opening brace { and a single closing brace }.
Placeholder names SHOULD be composed only of the characters A-Z, a-z, 0-9, underscore _, and period .. The use of other characters is reserved for future modifications of the placeholders specification.
Users SHOULD NOT pre-escape placeholder values since they can not know in which context the data will be displayed.


Ну и конечно же не забывайте про плейсхолдеры в сообщении. Вместо интерполяции и sprintf куда удобнее писать так:


$logger->notice('Process {pid} started at {time}', [
'pid' => getmypid(),
'time' => new DateTimeImmutable(),
]);


[2024-09-27T13:23:29.306682+00:00] app.NOTICE: Process 45104 started at 2024-09-27T13:23:29.306681+00:00 {"pid":45104,"time":"2024-09-27T13:23:29+00:00"} []
6👍136🔥399
Live PHP meetup 3 октября

В этот четверг в Санкт-Петербурге пройдёт очередной Live PHP. Вот мои ожидания от докладов.

GIGA IDE Cloud - сверхновая IDE во вселенной веб-разработки. Хочу послушать из первых уст про успехи Сбера на поприще IDE. Пора искать замену PhpStorm.
Transaction Script и Transaction Outbox в PHP: Эффективные паттерны для управления транзакциями. В моей картине мира Transaction Script и Transaction Outbox — это как гранатовый сок и гранатовый браслет. Интересно узнать, как Станислав уместил две ортогональные концепции в один доклад.
Страх и ненависть PSR. Недавно с Кириллом Мокевниным (интервью выйдет в октябре) обсуждали, что, несмотря на все проблемы, PHP всё-таки смог в стандарты в отличие от многих других языков. Качественный наброс от Кирилла Несмеянова будет очень кстати. С нетерпением жду!
Как растут проекты. Дима Елисеев, с удовольствием послушаю любые твои мысли, только, пожалуйста, не затягивай. 🙏

Короче, я взял билеты на Сапсан 🚝, и в четверг в 18:30 буду как штык в IT-баре Failover.

https://t.me/live_php_news/27
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍74🐳2
Через два часа выступаю на Podlodka PHP Crew!

Сегодняшний доклад — результат недавнего переосмысления многочисленных статей и выступлений Udi Dahan про CQRS, наложенный на мой опыт примения и преподавания этого архитектурного стиля.

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

Кстати, я всегда был убеждён, что отец CQRS — только Greg Young, но оказалось, что Udi тоже к этому причастен:

... both myself [Udi Dahan] and Greg Young (arguably the first two to talk about it and the two who ultimately collaborated on naming it – and now Google knows we didn’t mean “cars”) ...
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥33👍11🐳41
В последний момент исправляю рефлексию в PHP 8.4!

На неделе я начал добавлять поддержку PHP 8.4 в Typhoon Reflection, и очень рад, что взялся за это до релиза.

Во-первых, я зарепортил отсутствие обещанной в RFC хуков константы ReflectionProperty::IS_VIRTUAL. Но это тривиально, PR уже готов.

А вот что меня реально загрузило, так это поведение новых методов ReflectionProperty::isPrivateSet() и isProtectedSet() из Asymmetric Visibility RFC. Посудите сами:


final class Foo
{
// isPrivateSet() = true 👌
public private(set) mixed $public_private_set;

// isPrivateSet() = false 🤯
private private(set) mixed $private_private_set;

// isPrivateSet() = false 🤯
private mixed $private;

// isProtectedSet() = true 👌
public readonly mixed $public_readonly;

// isProtectedSet() = false 🤯
protected readonly mixed $protected_readonly;

// isProtectedSet() = false 🤯
protected protected(set) readonly mixed $protected_protected_set_readonly;

// isPrivateSet() = false 👌, isProtectedSet() = false 👌
public bool $virtual_no_set_hook { get => true; }
}


Сначала я подумал, что это баг, и создал тикет php-src#16175. Ilija, автор RFC, объяснил, почему так работает. Дело в том, что у свойств с симметричной видимостью отсутствует флаг ассиметричности. Поэтому для private и private private(set) isPrivateSet() возвращают false, а не true, как было бы логично ожидать. readonly свойства без явного (set) под капотом получают protected (set) (см. "Relationship with readonly" в RFC), поэтому public readonly будет ассиметричным с isProtectedSet() = true, а protected readonly — симметричным с isProtectedSet() = false.

Стало понятно, вот только пользоваться такой рефлексией по-прежнему дико неудобно. В API протекли детали реализации, которые, наоборот, должны быть инкапсулированы. Задача рефлексии — предоставить пользователю комфортный способ изучать код, а не продемонстрировать подкапотное устройство языка.

Но это ещё ладно. Сегодня до меня дошло, что в текущем виде рефлексия в PHP 8.4 ломает обратную совместимость! Если раньше проверка $reflectionProperty->isPublic() гарантировала, что не readonly свойства доступны на запись и чтение из глобального скоупа, то при текущей реализации в 8.4 она будет гарантировать только чтение!


$reflectionProperty = new ReflectionProperty($class, $property);

if ($reflectionProperty->isPublic() && !$reflectionProperty->isReadonly()) {
$object->{$property} = $value;
}


Например, такой код корректно отработает для любых классов в PHP 8.3, но споткнётся на public private(set) $property в 8.4, потому что isPublic() вернёт true, а запись в свойство бросит ошибку.

Вот что я предложил в письме к internals, которое составил в Сапсане по дороге на Live PHP:

ReflectionProperty::isPublic(), isProtected() и isPrivate() должны сохранить своё поведение и возвращать true только если свойство симметричное. Для public readonly метод будет возвращать false, потому что под капотом оно асимметричное с protected(set). Да, это сломает обратную совместимость, но не так критично, потому что все библиотеки уже учитывают тот факт, что в readonly нельзя писать из глобального скоупа.
• Добавить ReflectionProperty::isPublicGet(), isProtectedGet() и isPrivateGet(). Они должны возвращать true, если свойство симметрично или асимметрично доступно на чтение.
• Добавить ReflectionProperty::isPublicSet() и поменять поведение isProtectedSet() и isPrivateSet(): они должны возвращать true, если свойство симметрично или асимметрично доступно на запись.

Вся проблема в том, что недавно вышел PHP 8.4 RC1, и вносить изменения уже поздно. Надеюсь, что мой посыл про нарушение обратной совместимости всё-таки убедит сделать исключение.
5👍45🤯37🔥141