Пых
Open in Telegram
Блог Валентина Удальцова о разработке на PHP. Хобот @phpyhobot https://youtube.com/@phpyh https://vkvideo.ru/@phpyh https://t.me/isPHPdying Статистика: https://t.me/INOTAROBOT?start=st1219340804 Для связи используйте личные сообщения канала.
Show more8 091
Subscribers
-824 hours
-297 days
-1430 days
Data loading in progress...
Similar Channels
Tags Cloud
Incoming and Outgoing Mentions
---
---
---
---
---
---
Attracting Subscribers
July '26
July '26
+6
in 0 channels
June '26
+106
in 0 channels
Get PRO
May '26
+141
in 0 channels
Get PRO
April '26
+66
in 0 channels
Get PRO
March '26
+72
in 1 channels
Get PRO
February '26
+92
in 3 channels
Get PRO
January '26
+83
in 2 channels
Get PRO
December '25
+69
in 3 channels
Get PRO
November '25
+89
in 0 channels
Get PRO
October '25
+90
in 1 channels
Get PRO
September '25
+131
in 3 channels
Get PRO
August '25
+162
in 4 channels
Get PRO
July '25
+161
in 3 channels
Get PRO
June '25
+144
in 17 channels
Get PRO
May '25
+143
in 2 channels
Get PRO
April '25
+138
in 3 channels
Get PRO
March '25
+125
in 3 channels
Get PRO
February '25
+238
in 5 channels
Get PRO
January '25
+177
in 6 channels
Get PRO
December '24
+235
in 1 channels
Get PRO
November '24
+383
in 5 channels
Get PRO
October '24
+269
in 4 channels
Get PRO
September '24
+103
in 1 channels
Get PRO
August '24
+132
in 2 channels
Get PRO
July '24
+89
in 0 channels
Get PRO
June '24
+106
in 4 channels
Get PRO
May '24
+177
in 2 channels
Get PRO
April '24
+266
in 0 channels
Get PRO
March '24
+365
in 1 channels
Get PRO
February '24
+658
in 0 channels
Get PRO
January '24
+352
in 5 channels
Get PRO
December '23
+274
in 3 channels
Get PRO
November '23
+140
in 3 channels
Get PRO
October '23
+121
in 1 channels
Get PRO
September '23
+191
in 0 channels
Get PRO
August '23
+354
in 0 channels
Get PRO
July '23
+328
in 0 channels
Get PRO
June '23
+354
in 0 channels
Get PRO
May '23
+493
in 0 channels
Get PRO
April '23
+290
in 0 channels
Get PRO
March '23
+689
in 0 channels
Get PRO
February '23
+353
in 0 channels
Get PRO
January '23
+250
in 0 channels
Get PRO
December '22
+217
in 0 channels
Get PRO
November '22
+146
in 0 channels
Get PRO
October '22
+113
in 0 channels
Get PRO
September '22
+74
in 0 channels
Get PRO
August '22
+166
in 0 channels
Get PRO
July '22
+143
in 0 channels
Get PRO
June '22
+142
in 0 channels
Get PRO
May '22
+67
in 0 channels
Get PRO
April '22
+54
in 0 channels
Get PRO
March '22
+57
in 0 channels
Get PRO
February '22
+141
in 0 channels
Get PRO
January '22
+157
in 0 channels
Get PRO
December '21
+181
in 0 channels
Get PRO
November '21
+92
in 0 channels
Get PRO
October '21
+64
in 0 channels
Get PRO
September '21
+47
in 0 channels
Get PRO
August '21
+63
in 0 channels
Get PRO
July '21
+75
in 0 channels
Get PRO
June '21
+127
in 0 channels
Get PRO
May '21
+62
in 0 channels
Get PRO
April '21
+113
in 0 channels
Get PRO
March '21
+238
in 0 channels
Get PRO
February '21
+316
in 0 channels
Get PRO
January '21
+242
in 0 channels
Get PRO
December '20
+3 333
in 0 channels
| Date | Subscriber Growth | Mentions | Channels | |
| 03 July | +3 | |||
| 02 July | +1 | |||
| 01 July | +2 |
Channel Posts
| 2 | No text... | 2 020 |
| 3 | 🍆 Thesis Dic
Прошло полтора года с тех пор, как я всерьёз начал пилить DI контейнер для Thesis. Набор требований у меня был большой:
Модульность
Всё есть модуль, включая само приложение.
PHP DSL
Выразительный и лаконичный, не на массивах.
Типобезопасность
Не строковые идентификаторы, а объекты с дженериком. Легко написать плагин статанализатора для тайпчека аргументов.
Импорт/экспорт
У модуля прозрачный типизированный контракт на вход (конфигурация) и на выход (экспорт сервисов).
Scoped сервисы
В асинхронном приложении можно писать код как в умирающем: инжектить репозиторий по интерфейсу в конструктор обработчика и не замечать, что у него под капотом транзакция, открытая специально для текущего сообщения.
Disposer-коллбэки
К сервису, не меняя его код, можно привязать функцию, которая будет вызвана по завершении скоупа. Удобно для закрытия ресурсов и остановки фоновых корутин.
Предсказуемый автовайринг
Модуль из vendor не может внезапно затереть сервис в модуле проекта. Все биндинги локализованы, нет глобального пространства имён.
Автоконфигурация
Возможность порефлексировать сервисы перед сборкой и по подтипу или атрибуту накинуть теги.
Теги
Имплементируют Tag<T> и содержат типизированные данные. Через хук можно собрать протегированные сервисы, преобразовать их и заинжектить куда угодно.
First-class callable сервисы
myFunc(...) и $service->method(...) легко объявить сервисами, частично применить аргументы, прочитать атрибуты и подключить как обработчики сообщений, не написав CompilerPass на 1000 строк.
И вот мы здесь. 30 июня, я только что поставил тег 0.5.0 с осознанием, что до 1.0 рукой подать и я готов с вами поделиться!
В README есть всё необходимое для первого знакоства. В docs/ несколько рецептов.
https://github.com/thesis-php/dic
https://github.com/thesis-php/symfony-console-module
/**
* @implements Module<Ref<Application>>
*/
final readonly class App implements Module
{
public function configure(Dic $dic): mixed
{
$dic->apply(new AutoconfigureCommands());
$dic->function(self::greet(...));
return $dic->import(new SymfonyConsoleModule(name: 'Пых'));
}
#[AsCommand('greet', aliases: ['g'])]
private static function greet(#[Argument] string $name, SymfonyStyle $io): int
{
$io->title("Hello, {$name}!");
return Command::SUCCESS;
}
}
exit(Dic::run(
module: new App(),
main: static fn(Application $app) => $app->run(),
)); | 2 409 |
| 4 | Скидывайте идеи футболок под этим сообщением.👇 | 3 500 |
| 5 | Скидывайте идеи футболок под этим сообщением.👇 | 1 |
| 6 | Video message | 3 262 |
| 7 | Какой стиль оформления значений в исключениях лучше читается? | 4 092 |
| 8 | Как тебе идея? | 5 522 |
| 9 | Пыхник’26
Привет, дорогие Пыхари!
На закрытии Пых.конф’25 мы с вами договорились, что встретимся снова в 2026. В этом году вместо полноценной Пых.конф мы решили попробовать другой формат — Пыхник!
Примерный план:
• конец августа / начало сентября;
• загородный отель Art Village (12 км от МКАД);
• 100-120 офлайн-участников;
• 1 зал без параллельных потоков;
• 8 лучших докладов про PHP и экосистему;
• фуршет от конференции + рестораны на территории;
• записи докладов для всех участников уже на следующий день;
• возможность приехать с семьёй и провести в отеле ещё пару дней.
По предварительным расчётам стоимость участия может составить 6000₽. Добиться такой цены получится за счёт простого формата: без нескольких залов, сложного продакшена и прочих атрибутов больших конференций.
Чтобы не рисковать деньгами и понять, нужна ли такая встреча сообществу, мы думаем запустить краудфандинг на Planeta.ru (у нас уже был положительный опыт со слониками). Собираем необходимую сумму — проводим мероприятие, нет — деньги возвращаются.
Пожалуйста, проголосуй ниже безотносительно даты и программы — так мы сможем предварительно оценить интерес. И ждём побольше вопросов в комментариях, чтобы мы ничего не забыли учесть! | 4 868 |
| 10 | Video message | 6 083 |
| 11 | Whole Value a.k.a. Value Object
Я сейчас работаю над кодом, которому для работы требуется список параметров функции:
/**
* @param list<ReflectionParameter> $parameters
*/
function make(array $parameters): Something {}
На первый взгляд, всё здорово. list<ReflectionParameter> выражает минимально необходимое знание для решения задачи да и вообще это список из объектов-значений, а не каких-то там примитивов.
Но есть нюанс.
В такой make можно передать невозможный список параметров, например:
$trimReflection = new ReflectionFunction(trim(...));
make(array_reverse($trimReflection->getParameters()));
С точки зрения типов всё верно, но инвариант "опциональные параметры идут после обязательных" в переданном списке нарушен, что может привести к неправильной работе функции. Можно передать параметры от разных функций (тогда $parameters[$i]->getDeclaringFunction() будет давать неконсистентный результат), можно поставить вариадик в начало — способов задать неверный список много, потому что list<ReflectionParameter> ничего толком не регламентирует.
Если функция работает с концепцией "список параметров валидной сигнатуры", она должна принимать ReflectionFunctionAbstract и сама вызывать getParameters():
function make(ReflectionFunctionAbstract $function): Something
{
$parameters = $function->getParameters();
// ...
}
Теперь в теле make можно быть уверенным, что список параметров удовлетворяет всем инвариантам, ведь отрефлексировать функцию с неверной сигнатурой не получится...
Хотя подождите... Можно же так поломать:
final class MessedReflectionFunction extends ReflectionFunction
{
public function getParameters(): array
{
return array_reverse(parent::getParameters());
}
}
make(new MessedReflectionFunction(trim(...)));
Но это уже разговор про LSP и что наследники не должны нарушать инварианты родителей — тема для другого поста. 😊
Мораль такая: принимайте не просто минимум знаний, а минимум, гарантирующий необходимые инварианты. Это и есть концепция Whole Value.
⸻
Этот пост я разместил неделю назад в 🐘 PHPeople. | 5 488 |
| 12 | No text... | 5 709 |
| 13 | Горю с PHPStan
Все привыкли ограничивать дженерики типов сверху (upper bound): @template T of U. Но бывают ситуации, когда дженерик логично ограничить снизу (lower bound), в частности другим дженериком.
Пример. Представьте, что существует ссылка на какое-то значение и нужно заапкастить (преобразовать в супертип) тип значения в этой ссылке:
use Psr\Log\LoggerInterface as Logger;
use Monolog\Logger as Monolog;
use Typhoon\Type;
/**
* @template T
*/
interface Ref
{
/**
* @template S, где S — супертип для T
* @param Type<S> $super
* @return self<S>
*/
public function as(Type $super): self;
}
/**
* @param Ref<Monolog> $ref
* @return Ref<Logger>
*/
function upcastLogger(Ref $ref): Ref
{
return $ref->as(Type\objectT(Logger::class));
}
В Java wildcards это выражается так: ? super T. В Scala оператором: [S >: T].
В PHPStan, о чудо, тоже начали об этом думать аж в 2021 году: phpstan/phpstan/issues/5179.
Я был в курсе, и, не глядя в issue, решил попробовать. Пишу:
/**
* @template S super T
* @param Type<S> $super
* @return self<S>
*/
public function as(Type $super): self;
Анализатор говорит No issues!, я радостно продолжаю работать. А потом намеренно пробую вызвать $ref->as(Type\stringT) и всё равно вижу No issues!
Разбираюсь и понимаю, что PHPStan поддержал пару лет назад синтаксис super, но только в PHPDoc-парсере! Исходный тикет в анализаторе всё ещё не закрыт!
Представьте, что в PHP добавили property hooks только на уровне синтаксиса — по факту они вообще не работают. Как вам такая фича?
Между PHP-анализатором и PHPDoc-парсером есть фундаментальный каплинг (то есть, кохижен). Нельзя поправить одно и не учесть это в другом. Нормально разделять компоненты и делать один более универсальным, но тогда должны быть фича-флаги, при помощи которых второй компонент выключает (opt-out) или не включает (opt-in) неподдерживаемое поведение в первом.
⸻
Этот пост я разместил неделю назад в 🐘 PHPeople. | 6 818 |
| 14 | base64url
Хотел предложить base64url_encode(), base64url_decode() в Core — давно стандартизованная кодировка, используется в JWT, OAuth 2.0 и WebAuthn.
Но потом нашёл в почте прошлогодний тред с куда более фундаментальным RFC:
namespace Encoding;
enum Base64
{
case Standard;
case UrlSafe;
case Imap;
}
enum PaddingMode
{
case VariantControlled;
case StripPadding;
case PreservePadding;
}
enum DecodingMode
{
case Forgiving;
case Strict;
}
enum TimingMode
{
case Variable;
case Constant;
}
/**
* @throws UnableToEncodeException
*/
function base64_encode(
string $data,
Base64 $variant = Base64::Standard,
PaddingMode $paddingMode = PaddingMode::VariantControlled,
TimingMode $timingMode = TimingMode::Variable,
): string;
/**
* @throws UnableToDecodeException
*/
function base64_decode(
string $data,
Base64 $variant = Base64::Standard,
DecodingMode $decodingMode = DecodingMode::Strict,
TimingMode $timingMode = TimingMode::Variable,
): string;
Ну и аналогичные функции для base16, base32, base58 (Bitcoin) и base85 (PDF).
https://wiki.php.net/rfc/data_encoding_api
Спросил автора, не планирует ли он возобновить работу над RFC. Вот его ответ:
Hi Valentin,
We are still working on it with Tim Düsterhus doing the full implementation.
Best regards
Так что ждём!
⸻
Этот пост я разместил неделю назад в 🐘 PHPeople. | 6 437 |
| 15 | Используешь Dev Containers для локальной разработки? | 0 |
| 16 | Video message | 0 |
| 17 | Хроники пакета exceptionally
Вы наверняка знаете проект thecodingmachine/safe.
A set of core PHP functions rewritten to throw exceptions instead of returning false when an error is encountered.
// вместо
$contents = @file_get_contents($file);
if ($contents === false) {
throw new RuntimeException("File {$file} is not readable");
}
// пишем
$contents = Safe\file_get_contents($file);
Такой подход мне всегда казался максимально бредовым: скодогенерировать обёртки над всеми стандартными PHP-функциями и написать плагины под каждый статанализатор вместо того, чтобы воспользоваться композицией функций.
Поэтому ночью 3 августа 2019 года я написал библиотечку vudaltsov/exceptionally, которая решала проблему куда более элегантно:
$contents = exceptionallyCall('file_get_contents', $file);
Тогда ещё не было стрелочных функций и first class callable синтаксиса, поэтому краткая запись могла выглядеть только так.
Забавно, что утром в тот же день в паре кварталов от моей квартиры загорелся склад, подробнее в личном канале.
Через 2 дня я предложил RFC в Symfony, а ещё через неделю Yonel Ceruto добавил статический метод ErrorHandler::call(), он там есть и по сей день.
Но настали времена, когда я больше не пишу на Symfony, а функция всё ещё актуальна. Поэтому я решил перенести vudaltsov/exceptionally в thesis/exceptionally, упростить и приправить дженериками:
/**
* @template T
* @param callable(): (T|false) $function
* @return T
* @throws \ErrorException
*/
function exceptionally(
callable $function,
int $errorLevels = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED,
): mixed {}
// PHP < 8.6
$contents = exceptionally(static fn () => file_get_contents($file));
// PHP >= 8.6 с partial function application
$contents = exceptionally(file_get_contents($file, ...));
Исключение ErrorException, кстати, существует в ядре ровно для этой задачи, поэтому помимо стандартных параметров оно принимает файл и номер строки.
Пользуйтесь Thesis Exceptionally на здоровье!
composer require thesis/exceptionally
⸻
Этот пост я разместил неделю назад в 🐘 PHPeople. Кстати, общая группа там теперь бесплатная — подписывайся! | 0 |
Available now! Telegram Research 2025 — the year's key insights 
