Telegram Web Link
⚓️ Podlodka PHP про тесты!

Вряд ли среди нас найдётся пыхарь, который считает, что тесты вообще не нужны. Здесь всё очевидно. Но вот про само тестирование существует куча холиваров. Верна ли пирамида? Integrated tests are a scam? Класическое или мокистское тестирование? Чистый PHPUnit или дополнительные фреймворки? White box плохо? Нужно ли 100%-ное покрытие или мутационное тестирование? И так далее...

Не знаю, какие из этих вопросов будут затронуты в рамках докладов, но раз собирается знающий народ (Дима Елисеев, ребята из Vi.Tech, Skyeng, SpaceWeb), то обсудить это всё точно получится.

Вот некоторые из заявленных в программе сессий:
• «Как теория тестирования помогает при написании тестов» (Виктор Раев),
• «Как продать автотестирование "бизнесу"» (Виталий Шароватов),
• «Контрактное тестирование (тестирование API)» (Альгис Фатеев),
• «Работа с фреймворками для написания тестов: Codeception» (Евгений Жильцов),
• «Нагрузочное тестирование» (Александр Харченко),
• «Генерация автотестов» (Семен Русин).

https://podlodka.io/phpcrew
Enum и память

Вчера мне стало интересно, в какой момент инстанциируются кейсы enum. При декларации или лениво? Логично второе, но я решил проверить при помощи скрипта:

enum A
{
case X;
}

var_dump(spl_object_id(new stdClass())); // int(1)
var_dump(spl_object_id(A::X)); // int(1)
var_dump(spl_object_id(new stdClass())); // int(2)


Тут видно, что сразу после декларации енама A::X не инстанциировался, так как новому объекту был выдан идентификатор 1. А вот при обращении A::X кейс уже завис в памяти, и следующий объект получил идентификатор 2. Короче, логичное вроде бы верно.

Но что-то мне подсказало перепроверить результат. Для этого я сгенерировал "мегаенам" на 60к кейсов, а затем все их запросил, измерив память до и после (скрипт):

$code = 'enum Mega {';
for ($i = 0; $i < 60_000; ++$i) {
$code .= "case C{$i};";
}
$code .= '}';
eval($code);
unset($code);

var_dump(memory_get_usage() / 1024 / 1024); // 14.9

for ($i = 0; $i < 60_000; ++$i) {
constant("Mega::C{$i}");
}

var_dump(memory_get_usage() / 1024 / 1024); // 13.1 😳

И тут я сильно удивился. Потребление памяти не то что выросло, оно упало!!! Не долго думая, я написал Ilija Tovilo, автору PHP RFC: Enumerations. Вот его ответ:

Enums are generally instantiated when accessed, as you expected. The reason why the memory actually decreases was not obvious to me so I had to take a look. Before enums are instantiated, they have a pseudo-representation. That is, an AST node that contains the name of the enum case and its value if the enum is backed. Once that enum case is instantiated the name and value are copied from the pseudo AST node to the actual object, and the AST node is freed. And it turns out that the AST node actually requires more space than the object itself. If you use opcache this won't be the case, because the AST node is stored in shared memory (assuming you don't use eval), whereas the object lives in "userspace".

Выходит, что кейс enum действительно инстанциируется при обращении. Вот только в этот момент ещё и высвобождается псевдо-AST представление этого кейса, а так как оно тяжелее объекта, потребление памяти сокращается.
Valentin Gyver или не софтом единым

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

Выбор трекболов на рынке невелик, самый крутой — Logitech MX Ergo. Посудите сами: удобный шарик под большой палец, Bluethooth с поддержкой 2-ух устройств, эргономичный дизайн с изменяемым углом наклона, миддл-клик, отклонение колёсика и ещё 3 кнопки, программируемые в удобном macOS-совместимое приложение Logi Options+. Короче, шедевр.

Конкретно трекболом, который в разных агрегатных состояниях вы видите на фото, я пользуюсь с 2019-ого года (в Happy Inc. у меня был ещё один такой же в офисе). За это время он, конечно, постарел: microUSB разъём для зарядки, аккумулятор держит меньше месяца, левая кнопка мыши иногда пропускает клики. Ну а раз Logitech не обновляет трекбол, трекбол обновляю я!

Накануне забрал запчасти с Алика и вчера ночью решил первые две проблемы: проапгрейдил разъём до Type C и поставил новый аккумулятор большей ёмкости (1200 махов вместо 500). При пайке разъёма, конечно, пришлось поколхозить, потому что Type C шире и китайский вариант, уже готовый для моей простой пятивольтовой задачи, имеет две гигантские ножки. Но в итоге вышло приемлемо. И да, за счёт более ёмкого аккумулятора трекбол стал тяжелее (подковал блоху 😅), но это абсолютно неважно, ведь его не надо двигать! Также сфоткал кнопки левого и правого клика: их закажу и поменяю потом. Кстати, если кто-то в курсе, какие служат дольше, буду очень благодарен.

О результатах судите по фото. В процессе ремонта я испытал куда больший кайф, чем от покупки нового устройства. Этот трекбол точно заслужил такого отношения!
Как разбить итератор на батчи?

Представим, что нам надо распарсить какой-то гигантский документ и сформировать вставки в базу по 10к элементов. Для решения этой задачи не обязательно писать кастомный итератор, можно всё сделать "на коленке". Оборачиваем генератор с распарсенными данными в NoRewindIterator, чтобы избежать его перемотки, а затем применяем LimitIterator до тех пор, пока не закончатся данные:

final readonly class Importer
{
private const BATCH_SIZE = 10_000;

public function __construct(private Connection $connection) {}

public function import(): void
{
foreach ($this->parseBatched() as $batch) {
$sqlValues = '';
$values = [];

foreach ($batch as $value) {
$sqlValues .= ($sqlValues === '' ? '' : ',') . '(?)';
$values[] = $value;
}

$this->connection->execute(
'insert into data (val) values ' . $sqlValues,
$values,
);
}
}

private function parseBatched(): Generator
{
$parsed = new NoRewindIterator($this->parse());

while ($parsed->valid()) {
yield new LimitIterator($parsed, limit: self::BATCH_SIZE);
}
}

private function parse(): Generator
{
// ...
}
}
Пых
Valentin Gyver или не софтом единым Предыстория. Уже много лет я использую трекбол в качестве основного указательного устройства. Во-первых, это удобно: не надо елозить по столу, не нужен специальный коврик. Во-вторых, при игре на барабанах нагрузка на кисти…
Ремонт Logitech MX ERGO, часть 2

Вчера я решил третью проблему своего трекбола: поменял кнопки. Оказалось, что оригинальные японские Omron D2FС-F-7N(10M) весьма неплохие, но рассчитаны на 10 миллионов кликов (указано в маркировке), так что, вероятно, они своё честно отслужили. У Omron целая серия таких элементов с разными характеристиками, все они взаимозаменяемы. Но, посоветовавшись с одним мышиным мастером, я в итоге выбрал геймерские Каilh GM 8.0 (80М). Заказал на Авито, так как терпеть ещё месяц прерывающееся перетаскивание невмоготу, а разница в цене для разовой покупки не так принципиальна: 500 рублей за пару против ~300 на AliExpress.

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

В третий раз собрал свой бедный трекбол, и наконец-то всё работает идеально! Надеюсь, несколько лет ещё прослужит.
ContainerBuilder в PHP-конфигах Symfony

@oneNevan на курсе показал крутую штуку: в PHP-конфигах Symfony можно запрашивать не только конфигураторы сервисов и расширений DI, но и текущую среду, а также ContainerBuilder!

Последнее безумно удобно в сложных инфраструктурных модулях, которые помимо сервисов добавляют свои CompilerPass-ы, автоконфигурируемые интерфейсы и обработчики атрибутов. Получается, можно не тянуться в Kernel::build через весь проект, чтобы всё это настроить.

В документации Symfony про такие чудеса ни слова. Узнать о них можно лишь "случайно" заглянув в код. 😅
Оказывается, в доке есть пример с ContainerBuilder, спасибо @SymfonyAnton, раскрыл глаза. 👀

Выше я, конечно, подразумевал модульную структуру проекта, при которой конфиг пакета размещается вутри него самого, а не в config (смотрите мой доклад про package-by-feature и модульный скелетон для Symfony).
Please open Telegram to view this post
VIEW IN TELEGRAM
Упрощаем тесты с участием файлов

Представим, что мы написали функцию parseCSVFile, которая принимает путь до CSV-файла и возвращает распарсенные данные в удобном нам формате. Как её протестировать?

Первая мысль — написать несколько CSV-файликов, положить их рядом с тестом, а в самом тесте сравнить результат их парсинга с ожидаемыми значениями. Очень просто, но неудобно: тест разбросан по нескольким файлам, сложно читать и вносить изменения.

Вторая мысль — mikey179/vfsstream. Это пакет, который позволяет налету в памяти создавать файловую систему и взаимодействовать с ней как с реальной. Круто, но для тестирования нашей простенькой функции слишком мощно.

А теперь третий вариант, оптимальный. В PHP есть data stream wrapper (а-ля RFC 2397), который позволяет инлайнить содержимое файла прямо в "путь". Формула проста: data://{MIME-тип},{Содержимое}. В итоге тест будет выглядеть так:

$csv = <<<'CSV'
data://text/csv,PHP Version,2022-01,2022-07,2023-01,2023-07
8.0,23.9%,20.6%,16.2%,12.3%
8.1,9.1%,24.5%,38.8%,39.3%
8.2,0.0%,0.0%,4.7%,17.2%
CSV;
$expected = [...];

$parsed = parseCSVFile($csv);

self::assertSame($expected, $parsed);


https://www.php.net/manual/ru/wrappers.data.php
Ребята обратили внимание, что на моём курсе нет девушек. Это не специально, у меня даже не было данных по полу. Но вот стало интересно, сколько на канале разработчиц?
Anonymous Poll
5%
Я разработчица 👩‍💻
79%
Я разработчик 👨‍💻
16%
Я по приколу 👀
Курсы, деньги, две работы

Вчера с Петром записали подкаст про мои пертурбации в этом году. Рассказал, почему я ушёл с двух работ подряд, как запустил курс хардкорного PHP ну и конечно же про доходы.

https://www.tg-me.com/tg_5minphp/1214
Кэш через OPcache на RnD PHP

В субботу наконец-то выступлю в Ростове-на-Дону! Два года назад митап отменили из-за вспышки ковида, поэтому смыслом поездки стало знакомство с городом, Николаем @drup8 и его замечательной семьёй. В этот раз проблема только в том, что не получится быстренько долететь на самолёте, но к поездам мне не привыкать.

Мой сказ будет хардкорным: порефлексим, попишем в приват, повызываем opcache_* функции. Хорошо, что меня поставили первым, загружу всех в самом начале. 😈

Также в программе доклады Александра Дубовского про инструменты профилирования и Сергея Ивченко про Transactional Outbox.

Регистрация: https://php-rnd.timepad.ru/event/2663787/. Ставь 🤝, если придёшь. Для всех остальных будет трансляция.
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from PHP умирает?!
Ответочка для https://www.tg-me.com/tg_5minphp/1211.
Service Dumper Bundle

При работе с DI в Symfony бывает полезно вывести сервис на экран, чтобы убедиться, что он правильно сконфигурирован. Да, есть очень полезные команды debug:container, debug:autowiring и lint:container, они помогают отладить контейнер, но это не то же самое, что увидеть дамп реального объекта.

Год назад я добавил в проект консольную команду, которая выводила на экран любой сервис. Для простоты она переиспользовала test.service_container из FrameworkBundle для доступа к приватным сервисам.

Недавно утилитка пригодилась мне на курсе. Когда мы проходим Symfony Dependency Injection, я всегда показываю, как выглядят сконфигурированные серисы в рантайме. Становится гораздо понятнее, как работает тот же tagged_locator или inline_service.

До сих пор я таскал команду из проекта в проект, но вчера, наконец, оформил её в виде бандла. На этот раз я скопировал и упростил оригинальные TestServiceContainerWeakRefPass и TestServiceContainerRealRefPass, чтобы не зависеть от FrameworkBundle и деталей объявления test.service_container, добавил разные варианты вывода сервисов на экран (var_dump, symfony/var-dumper, xdebug_break, кастом), а также разрешил менять алгоритм поиска сервисов по id.

В общем, ставьте, пробуйте, предлагайте идеи по улучшению!

https://github.com/phpyh/service-dumper-bundle
Я сделал перегрузку методов для PHP!

К выходу PHP 8.3 я решил реализовать фичу, которая в новой версии не только не появилась, но и была окончательно выпилена (с чем я согласен).

Итак, вот мой вариант оверлоадинга/перегрузки/Ad-hoc-полиморфизма для PHP:


use Typhoon\Overloading\Overload;

final readonly class WhateverHandler
{
public function handle(mixed ...$args): string
{
return Overload::call();
}

#[Overload('handle')]
public function handleInt(int $int): string
{
return __METHOD__;
}

#[Overload('handle')]
public function handleIntAndFloat(int $int = 0, float $float = M_E): string
{
return __METHOD__;
}
}

$handler = new WhateverHandler();

// WhateverHandler::handleInt
var_dump($handler->handle(300));

// WhateverHandler::handleIntAndFloat
var_dump($handler->handle(float: 1.5));


В перегружаемый метод пишем один-единственный вызов Overload::call(). Он, кстати, специально ничего не принимает, чтобы упростить использование и избежать ошибок. Затем добавляем перегружающие методы с такой же статичностью/видимостью и помечаем их атрибутом #[Overload('перегружаемый метод')]. Вуаля! Теперь можно вызывать перегружаемый метод с аргументами различных типов, а Overload будет передавать поток управления в методы с подходящей сигнатурой.

Главный вопрос, конечно, скорость. Но несмотря на то, что это реализация в первом приближении и есть идеи по улучшению, уже работает достаточно шустро. Благодаря мемоизации в воркерах капитальной разницы с прямым вызовом не будет, особенно если перегруженных методов не очень много. Для PHP-FPM предусмотрен кэш с поддержкой OPcache (примерно как в докладе).

Короче, ставьте, экспериментируйте, закидывайте идеи в комментарии и в Issues в репозиторий.

https://github.com/typhoon-php/overloading

P.S. Всех с третьей восьмёркой! 🎉
Please open Telegram to view this post
VIEW IN TELEGRAM
🎅 Через пару часов PHP Community Meetup!

Затусим в конце года, как обычно! Обсудим PHP 8.3, заслушаем доклады, запустим опрос сообщества и наверняка похоливарим на разные темы.

Что будет:
• Евгений Прохоров прямо на наших глазах ускорит PHP,
• Кирилл Несмеянов докажет, что никто, кроме него, не знает PHP,
• шеф-повар Александр Макаров приготовит Composer под новым соусом,
• Валентин Удальцов (это я) покажу вам PHP 8.3 во всей красе.

Проведёт мероприятие Михаил Каморин, а подсказывать текст из-за кулис будет наш бессменный режиссёр и продюсер — Алиса Круглова!

Залетайте в трансляцию на PHP Point, будет весело, как мне сейчас! Всех ждём в 12 по Москве.

https://youtu.be/JyxGieyBj3k
Please open Telegram to view this post
VIEW IN TELEGRAM
2025/07/03 22:22:20
Back to Top
HTML Embed Code: