Telegram Web Link
Один из подписчиков сразу порекомендовал менеджер конфигов Oh My Zsh https://ohmyz.sh/, там из коробки make и еще уйма всего.

Установил, выглядит круто, посмотрим, как оно в деле 😁
Наконец-то в Symfony состоялся большой рефакторинг Security!

Экспериментальная версия будет поставлена в рамках 5.1. Инсайты в статье основного контрибьютора этой идеи Wouter De Jong.

https://wouterj.nl/2020/04/authenticators-new-symfony-security
Кстати, о релизах. Судя по дорожной карте, Symfony 5.1 выйдет как и все нечетные миноры — в конце мая.

Предварительный список изменений можно найти в серии статей Living on the Edge.
Допустим, мы проектируем пакетный обработчик команд. Чтобы узнавать о каждой успешной операции, добавим простой аргумент-слушатель $onEach.

/**
* @template T of object
* @psalm-param iterable<T> $commands
* @psalm-param callable(T): void $onEach
*/
function handleBatch(iterable $commands, callable $onEach): void
{
foreach ($commands as $command) {
// ...

$onEach($command);
}
}


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

handleBatch($commands, static function () use ($progressBar): void {
$progressBar->advance();
});


Однако не всегда слушатель будет нужен, поэтому для простоты контракта сделаем его необязательным аргументом. Решение "в лоб": ?callable $onEach = null и потом if (null !== $onEach) { $onEach($command) }.

А теперь применим паттерн NullObject. Для этого добавим в проектный functions.php элементарную function void(): void {} и попробуем её в качестве значения по умолчанию в сигнатуре обработчика: callable $onEach = 'void'.

👹 Fatal error: Default value for parameters with callable type can only be NULL.

Эхх, видимо, без null здесь никак не обойтись. Но мы не сдаемся и красиво комбинируем.

function handleBatch(iterable $commands, ?callable $onEach = null): void
{
$onEach ??= 'void';

// ...
}


🎉 Ура, так работает.

Плюсы этого подхода по сравнению с if:
• лаконичность: 1 строка вместо 3;
• простота восприятия: одно выражение в начале функции имеет меньшую цикломатическую сложность, чем условие в цикле;
• универсальность: легко переиспользовать во всех подобных ситуациях.
Всех поздравляю с принятием RFC Attributes v2. Это огромный шаг вперед, сопоставимый по значению со статической типизацией.

К синтаксису, конечно, придется привыкать, но я почему-то рад, что PHP здесь не повторяет Java.

По сравнению с PHPDoc-версией мы получаем новый инструмент — атрибуцию (чуть не написал аннотирование) параметров. Можно будет написать более красивые маппинги для контроллеров. Чтобы, например, для эндпойнта POST /api/employee/hire?department=IT экшн выглядел так:

<<Route('/api/employee/hire', 'POST')>>
function hireEmployee(
<<QueryParam('department')>> string $department,
<<JsonBody>> Employee $employee
): Response {
// ...
}
Скоро браузеры будут заметно подвисать при загрузке новости об очередном минорном релизе Symfony 😅

Встречаем 5.1.0-beta1 🎊

Что можно делать с beta-релизом?

Во-первых, пробежаться по списку изменений и просмотреть зацепившие взгляд пулл-реквесты. Так вы не только узнаете о нюансах нововведений, но и обязательно почерпнёте что-то новое о PHP или устройстве Symfony. Кроме того, документация для фичей нередко запаздывает, а о её готовности уже никто специально не сообщает, поэтому просмотр релиза может стать главным источником информации для вас.

Во-вторых, если у вас есть проект на 5.0 и немного свободного времени, можете обновить его в отдельной ветке, прогнать тесты, составить баг-репорты и предложить фиксы. К сожалению, Symfony не пользуется инструментами статического анализа, поэтому иногда можно напороться на что-то вроде Call to a member function X on null и прочую дичь. Также можно встретить нарушения обратной совместимости, их обязательно поправят перед релизом. Перед созданием issue желательно убедиться, что её ещё не зарепортили и не пофиксили, но при прочих равных лучше создать дубликат, чем не написать вообще.
Поговорим про удаление.

В общем случае я рекомендую придерживаться совета Udi Dahan и ничего не удалять. В слабосвязных системах, где сущности ссылаются друг на друга по идентификатору, удаление нарушает связи и историю, усложняет восстановление в случае ошибки. В сильносвязных системах, где сущности явно ссылаются друг на друга (например, через @ORM\One|Many...), удаление ещё и ставит под вопрос глубину каскадных операций в различных сценариях. Например, при удалении автора можно стереть все его блоги и посты, но при удалении поста в блоге вряд ли вы ожидаете исчезновение автора.

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

Альтернатива? Пользуйтесь техникой мягкого удаления, флагом "в архиве", датами активности и т.д. Да, придется добавить соответствующие условия в некоторые запросы, скорректировать индексы (см. PostgreSQL Partial Index). Да, вырастут в объеме хранилища, хотя в наше время это копейки. Но всё это не сравнится с болью восстановления данных, удалённых по ошибке.

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

Про удаление в системах с Event Sourcing рекомендую посмотреть фрагмент доклада David Schmitz.
Про чистку в распределённых системах можно почитать в статье инженера Twitter.
Обе ссылки из чата @symfony_php.
Проект Happy Job, где я занимаюсь проектированием и разработкой бэкенда,  — это в первую очередь аналитика.
А какая аналитика без выгрузок в Excel 😉

Раньше я всегда использовал библиотеку PHPOffice/PhpSpreadsheet (бывший PHPOffice/PHPExcel). Она предоставляет почти полный инструментарий для работы с таблицами, но есть один большой минус — формирование листа по умолчанию происходит в памяти. Одна ячейка вместе с метаданными весит примерно 1 Кбайт, поэтому выгрузка лишь 400 000 строк в три колонки уже обойдется более чем в 1 Гбайт памяти. В библиотеке предусмотрена возможность кэшировать ячейки на базе PSR-16, но это значительно снижает скорость записи.

Для нашего кейса я нашёл решение получше. Куда менее популярная библиотека box/spout позволяет читать Excel-файлы и писать в них построчно и очень быстро. Расход памяти константный из коробки, без всяких плясок с кэшем (по факту она, конечно, создает какие-то временные файлы в sys_get_temp_dir()). Пакет тоже поддерживает листы и типы данных, но не умеет в автоширину, объединение ячеек и продвинутое форматирование. Я уверен, что как и для нас, для большинства проектов это приемлемый компромисс.

Stay tuned! В следующем посте я расскажу, как удобно стримить данные в браузер в формате Excel средствами Symfony HttpFoundation.
Писал-писал я про интеграцию вышеупомянутой Box Spout в Symfony и написал целую статью 📰🤓

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

return $factory->createResponse('report.xlsx', static function (Writer $writer) use ($report): void {
$writer->getCurrentSheet()->setName('Степени чисел');
$writer->addRow(WriterEntityFactory::createRowFromArray(['Число', 'В квадрате', 'В кубе']));

foreach ($report as $values) {
$writer->addRow(WriterEntityFactory::createRowFromArray($values));
}
});
В Psalm версии 3.11.0 и выше можно использовать @psalm-trace $var для отладки типа переменной.

В тикете предложил добавить отладку значения нижестоящего выражения без указания переменной.
Аргумент "непустой индексный массив строк" на чистом PHP и с использованием Psalm:

function native(string $name, string ...$names): void
{
foreach ([$name, ...$names] as $name) {
// ...
}
}

/**
* @psalm-param non-empty-list<string> $names
*/
function psalm(array $names): void
{
foreach ($names as $name) {
// ...
}
}

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

Если аргументы объявляются при вызове, я советую использовать оператор ... Например, в билдере SQL запросов колонки удобнее передавать аргументами:

$db
->select('respondent_id', 'value')
->from('evaluation')

В остальных случаях передаваемое значение скорее всего уже будет "на руках" в виде списка, поэтому аргумент лучше объявить массивом.
В эту субботу в 11:00 пройдёт 3-й виртуальный PHP-митап.

Представлены 5 интересных докладов, в том числе "Psalm не предлагать: малоизвестные инструменты статического анализа кода" от Александра Новикова из Spiral Scout.

Сразу после выступления мы с Кириллом Несмеяновым встретим вас в дискуссионной Zoom-комнате для обсуждения нюансов статического анализа и предложим вам Psalm.

Всех ждём 😉

https://meetups-online.ru/php-may-2020
Если вы используете PostgreSQL и Doctrine DBAL на чтение и вам нужно получить text[] как массив PHP, то не стоит писать свой PgTextArrayType.

Проблема в том, что select array['a', 'b'] PostgreSQL возвращает как '{a,b}', а select array['a', 'b c'] как '{a,"b c"}'. Очевидно, что explode(',', trim($value, '{}')) тут не работает. Более-менее корректный парсинг выглядит монструозно и стоит дорого. При этом от массивов в запросах отказываться тоже не хочется, так как работать с ними зачастую очень удобно.

Предлагаю простое и эффективное решение: select array_to_json(array['a', 'b c']). Мудрёный парсинг тут не нужен, PgTextArrayType тоже писать не надо — можно использовать родной JsonType.
nikic_php8.pdf
874 KB
Слайды Никиты Попова What's new in PHP 8.0? с конференции PHP fwdays 2020.

https://twitter.com/nikita_ppv/status/1269706258300506114
Как бы вы оформили приватный метод, который опционально преобразует несколько значений?
Anonymous Poll
27%
Передача по ссылке
73%
Возврат кортежа
Приглашаю обсудить в нашем чате.
Подведу итоги прений.

Однозначного ответа конечно же нет, есть ±, компромиссы и предпочтения 🤪

Вариант с передачей по ссылке:
немногословный;
менее затратный по ресурсам;
вызов $this->processByReference($value1, $value2) при чтении не выглядит как мутация переменных, это затрудняет ревью и поддержку;
передача по ссылке — побочный эффект для контекста вызывающей функции.

Вариант с возвращением кортежа:
более многословный (хотя выглядит все равно опрятно);
потребует дополнительную память на массив (но разговоры про это пахнут нанооптимизацией);
строка [$value1, $value2] = $this->processAndReturnTuplet($value1, $value2) говорит сама за себя — я преобразую значения этих переменных;
передача значений копированием оставляет нам шанс написать чистый (pure) метод.

Ещё было предложено возвращать объект, но это, по сути, частный случай второго варианта. Персональный тип для результата приватного метода избыточен, с @psam-return array{string, int} Psalm и без него отловит все ошибки.

Всем спасибо за обсуждение!
Я выбираю второй вариант с кортежем, так как поддерживаемость и прозрачный control flow перевешивают 😉
2025/07/10 22:32:00
Back to Top
HTML Embed Code: