Пых

Description
Блог @vudaltsov о разработке на PHP.
Чтобы поддержать канал, подпишись на Пых Boosted: https://boosty.to/phpyh.
Реклама и вакансии НЕ размещаются.
We recommend to visit

Самый популярный русскоязычный Telegram канал.

Новости присылать сюда: @Toporchan_Bot

Live канал – https://t.me/+oDf_lVJzbNQyYWFi

Открытый канал – @topor

По вопросам сотрудничества: @toporch

Топор ВК: https://vk.com/toportg

Last updated 3 weeks, 1 day ago

🛒 Магазин сообществ в соц. сетях 24/7
⚡️ В наличии любые тематики и количества, связь в ЛС @timur_chik1


ac99e5f0c33c6df9805b

Last updated 2 months ago

4ch
4ch
1,343,506 @nefourch

Единственный и неповторимый 4ch

Реклама: @adv4ch

Пригласить друга: @eto4ch

Last updated 2 weeks, 1 day ago

1 month ago
1 month ago

Упрощаем тесты с участием файлов

Представим, что мы написали функцию 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

1 month, 1 week ago
**ContainerBuilder в PHP-конфигах Symfony**

ContainerBuilder в PHP-конфигах Symfony

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

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

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

Выше я, конечно, подразумевал модульную структуру проекта, при которой конфиг пакета размещается вутри него самого, а не в config (смотрите мой доклад про package-by-feature и модульный скелетон для Symfony).

1 month, 1 week ago
Пых
1 month, 1 week ago
**Ремонт Logitech MX ERGO, часть 2**

Ремонт Logitech MX ERGO, часть 2

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

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

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

1 month, 1 week ago

Как разбить итератор на батчи?

Представим, что нам надо распарсить какой-то гигантский документ и сформировать вставки в базу по 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 = [];

<pre> foreach ($batch as $value) { $sqlValues .= ($sqlValues === '' ? '' : ',') . '(?)'; $values[] = $value; } $this\->connection\->execute( 'insert into data (val) values ' . $sqlValues, $values, ); } </pre>

}

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

<pre>while ($parsed\->valid()) { yield new LimitIterator($parsed, limit: self::BATCH\_SIZE); } </pre>

}

private function parse(): Generator
{
// ...
}
}
```

1 month, 2 weeks ago
Пых
1 month, 2 weeks ago
Пых
1 month, 2 weeks ago

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 представление этого кейса, а так как оно тяжелее объекта, потребление памяти сокращается.

3 months ago

▶️ PHP-линч #21 К сожалению, в 19 не получается, поэтому проведу на час раньше из кафе. Надеюсь, будет нормально слышно. https://youtu.be/DxmX2A_WJTk

We recommend to visit

Самый популярный русскоязычный Telegram канал.

Новости присылать сюда: @Toporchan_Bot

Live канал – https://t.me/+oDf_lVJzbNQyYWFi

Открытый канал – @topor

По вопросам сотрудничества: @toporch

Топор ВК: https://vk.com/toportg

Last updated 3 weeks, 1 day ago

🛒 Магазин сообществ в соц. сетях 24/7
⚡️ В наличии любые тематики и количества, связь в ЛС @timur_chik1


ac99e5f0c33c6df9805b

Last updated 2 months ago

4ch
4ch
1,343,506 @nefourch

Единственный и неповторимый 4ch

Реклама: @adv4ch

Пригласить друга: @eto4ch

Last updated 2 weeks, 1 day ago