Теперь у Symfony тоже есть библиотека для работы с UUID:
ramsey/uuid
- процесс генерации реализован прямо в библиотеке и разложен по ООП полочкам, легко расширяется;
- поддержка GUID;
- поддержка экспериментального UUID v6;
- интеграции: ramsey/uuid-doctrine , ramsey/uuid-console .
symfony/uid
- элементарная обёртка над полифилом symfony/polyfill-uuid, который имитирует функционал PHP-расширения UUID и при этом работает быстрее;
- поддержка UUID v6;
- поддержка ULID;
- обсуждаются планы по интеграции с другими компонентами и библиотеками.
Как только допилят интеграцию, можно будет в новых проектах ставить
____
Есть недовольные, мол, Symfony продолжает добавлять компоненты, которые заменяют существующие неплохие проекты. Возможно, имеется в виду
Мое мнение, что если пакет лицензирован MIT, то по определению ни у кого не должно быть никаких претензий к тем, кто создает похожие библиотеки.
Во-вторых, Symfony не всегда делает хорошо, например, к тому же Messenger и другим компонентам немало вопросов по архитектуре. Разработки Symfony никак не блокируют стремление к прекрасному, скорее стимулируют. Да и в целом конкуренция — это почти всегда хорошо, она препятствует застою, предоставляет выбор. Может быть Ben Ramsey не релизнул бы 4.0 еще полгода, если бы Symfony не анонсировала свой UID 😉
composer req symfony/uid. Сравним.ramsey/uuid
- процесс генерации реализован прямо в библиотеке и разложен по ООП полочкам, легко расширяется;
- поддержка GUID;
- поддержка экспериментального UUID v6;
- интеграции: ramsey/uuid-doctrine , ramsey/uuid-console .
symfony/uid
- элементарная обёртка над полифилом symfony/polyfill-uuid, который имитирует функционал PHP-расширения UUID и при этом работает быстрее;
- поддержка UUID v6;
- поддержка ULID;
- обсуждаются планы по интеграции с другими компонентами и библиотеками.
Как только допилят интеграцию, можно будет в новых проектах ставить
symfony/uid просто из-за скорости работы.____
Есть недовольные, мол, Symfony продолжает добавлять компоненты, которые заменяют существующие неплохие проекты. Возможно, имеется в виду
symfony/messenger, который оставил в тени simple-bus/simple-bus.Мое мнение, что если пакет лицензирован MIT, то по определению ни у кого не должно быть никаких претензий к тем, кто создает похожие библиотеки.
Во-вторых, Symfony не всегда делает хорошо, например, к тому же Messenger и другим компонентам немало вопросов по архитектуре. Разработки Symfony никак не блокируют стремление к прекрасному, скорее стимулируют. Да и в целом конкуренция — это почти всегда хорошо, она препятствует застою, предоставляет выбор. Может быть Ben Ramsey не релизнул бы 4.0 еще полгода, если бы Symfony не анонсировала свой UID 😉
Изучаем свежезакрытые уязвимости в Symfony и обновляем
https://symfony.com/blog/cve-2020-5274-fix-exception-message-escaping-rendered-by-errorhandler
https://symfony.com/blog/cve-2020-5255-prevent-cache-poisoning-via-a-response-content-type-header
https://symfony.com/blog/cve-2020-5275-all-access-control-rules-are-required-when-a-firewall-uses-the-unanimous-strategy
Советую просматривать CVE и разбираться, в чем была проблема и как её решили. Это отличная возможность поучиться на чужих ошибках и начать писать более безопасный код.
Но никогда не обольщайтесь на эту тему. Если ваша система не проходит регулярный профессиональный аудит силами внутреннего отдела безопасности или внешней службы на аутсорсе — я могу с уверенностью сказать, что она уязвима.
4.4.x до 4.4.7 и 5.0.x до 5.0.7.https://symfony.com/blog/cve-2020-5274-fix-exception-message-escaping-rendered-by-errorhandler
https://symfony.com/blog/cve-2020-5255-prevent-cache-poisoning-via-a-response-content-type-header
https://symfony.com/blog/cve-2020-5275-all-access-control-rules-are-required-when-a-firewall-uses-the-unanimous-strategy
Советую просматривать CVE и разбираться, в чем была проблема и как её решили. Это отличная возможность поучиться на чужих ошибках и начать писать более безопасный код.
Но никогда не обольщайтесь на эту тему. Если ваша система не проходит регулярный профессиональный аудит силами внутреннего отдела безопасности или внешней службы на аутсорсе — я могу с уверенностью сказать, что она уязвима.
Symfony
CVE-2020-5274: Fix Exception message escaping rendered by ErrorHandler (Symfony Blog)
CVE-2020-5274 fixes Exception message escaping rendered by ErrorHandler.
Недавно обновил
Первое, что приходит в голову, захардкодить в
Другой вариант — переписать ограничение в
Я предпочитаю добавлять проблемные версии в секцию conflict. Так я могу свободно менять глобальный констрейнт в
vendor/package до 3.18.1, а там баг. Понятное дело, сразу создал тикет, но как быть в проекте, пока он не закрыт?Первое, что приходит в голову, захардкодить в
require версию 3.18.0. Это самый плохой вариант, потому что через какое-то время выйдет новая версия, там все пофиксят, добавят фич, а я так и буду сидеть на 3.18.0 даже после composer update. Это ещё и небезопасно, ведь так можно пропустить security patch. Это как нарушить open/closed — стандартное обновление не работает, а чтобы обновить нужно руками править composer.json.Другой вариант — переписать ограничение в
require на ^3.18, !=3.18.1, но, на мой взгляд, такую запись будет труднее воспринимать визуально. Кроме того, правило исключения в этой секции второстепенно.Я предпочитаю добавлять проблемные версии в секцию conflict. Так я могу свободно менять глобальный констрейнт в
require / require-dev и при необходимости точечно подправлять его в conflict.{
"name": "vudaltsov/phpyh",
"require": {
"vendor/package": "^3.18"
},
"conflict": {
"vendor/package": "3.18.1"
}
}getcomposer.org
The composer.json schema - Composer
A Dependency Manager for PHP
📖 Правильная регистрация консольных команд Symfony в DI
Год назад на одном большом проекте исследовал мучительный баг. Команда
И вот наконец-то на выходных нашел время оформить подробный разбор проблемы.
Добро пожаловать на обновленный профиль на медиуме, где вы, возможно, читали мою статью Не игнорьте composer.lock.
http://bit.ly/3hnPoIs
Год назад на одном большом проекте исследовал мучительный баг. Команда
bin/console cache:clear при локальном развертывании и прогонах в CI требовала подключения к БД и Redis. Какого черта, я же просто компилирую контейнер?!И вот наконец-то на выходных нашел время оформить подробный разбор проблемы.
Добро пожаловать на обновленный профиль на медиуме, где вы, возможно, читали мою статью Не игнорьте composer.lock.
http://bit.ly/3hnPoIs
Medium
Правильная регистрация консольных команд Symfony в DI
Если ваше приложение требует БД при чистке кэша, эта статья для вас
В продолжение разговора о консольном приложении, предлагаю вот такой базовый класс команды для вашего проекта.
https://gist.github.com/vudaltsov/22c9498e891669d36bbbd366cc3705ef
Некоторые пояснения:
- благодаря пустому конструктору при создании подкласса PhpStorm не дублирует необязательные и ненужные аргументы оригинального конструктора
-
- в
- методы
- зафиксированы все корректные типы, в частности, execute(): int.
Опробовано в продакшне 😂, пользуйтесь.
https://gist.github.com/vudaltsov/22c9498e891669d36bbbd366cc3705ef
Некоторые пояснения:
- благодаря пустому конструктору при создании подкласса PhpStorm не дублирует необязательные и ненужные аргументы оригинального конструктора
Symfony\Component\Console\Command\Command;-
abstract public static function name() форсирует статическое имя, речь о котором шла в статье постом выше;- в
getDefaultName добавлен префикс, чтобы исключить коллизии с вендорными командами;- методы
doExecute и doInteract сразу получают удобный хэлпер SymfonyStyle, о котором я когда-то тут рассказывал;- зафиксированы все корректные типы, в частности, execute(): int.
Опробовано в продакшне 😂, пользуйтесь.
Gist
ConsoleCommand.php
GitHub Gist: instantly share code, notes, and snippets.
Как пропустить первый элемент итератора в цикле? А пройти пять начиная с третьего?
💩
💩
Все гораздо проще и лаконичнее с LimitIterator.
Обратите внимание, что первый аргумент
Итак, проход по первым трём элементам любого обходимого объекта будет выглядеть так:
Неплохие базовые вопросы для собеседования 😉
💩
if (!isset($firstSkipped))💩
if ($i++ < 2)Все гораздо проще и лаконичнее с LimitIterator.
foreach (new LimitIterator($iterator, $offset = 2, $limit = 5) as $item) {
// ...
}Обратите внимание, что первый аргумент
LimitIterator имеет тип Iterator. То есть можно передать, например, Generator или ArrayIterator. Однако часто простые обходимые объекты реализуют IteratorAggregate, который не является подтипом Iterator. Как тут быть? Сначала на ум приходит забрать из него итератор вызовом $object->getIterator(). Она неверная, потому что IteratorAggregate::getIterator возвращает супертип Traversable, то есть это опять-таки может быть IteratorAggregate. Правильное решение — обернуть наш объект в IteratorIterator, который превращает любой Traversable в Iterator.Итак, проход по первым трём элементам любого обходимого объекта будет выглядеть так:
foreach (new LimitIterator(new IteratorIterator($traversable), 0, 3) as $item) {
// ...
}Неплохие базовые вопросы для собеседования 😉
В текущем проекте мы активно юзаем
• Ставим пакет с полезными автокомплитами для zsh.
• Открываем новое окно терминала и игнорируем появившееся предупреждение, нажав
• Затем фиксим права командой
• В новом терминале заходим в проект, набираем
Кстати, линуксоидам тоже рекомендую посмотреть в сторону zsh: https://habr.com/ru/post/326580/.
Makefile, как-нибудь напишу про это подробнее. В macOS Catalina по умолчанию используется оболочка Z shell, и из коробки автокомплит для make не настроен. Вот как это исправить:• Ставим пакет с полезными автокомплитами для zsh.
brew install zsh-completions
echo 'if type brew &>/dev/null; then
FPATH=$(brew --prefix)/share/zsh-completions:$FPATH
autoload -Uz compinit
compinit
fi' >> ~/.zshrc
• Открываем новое окно терминала и игнорируем появившееся предупреждение, нажав
y.• Затем фиксим права командой
compaudit | xargs chmod g-w.• В новом терминале заходим в проект, набираем
make <tab> и, вуаля, получаем список команд!Кстати, линуксоидам тоже рекомендую посмотреть в сторону zsh: https://habr.com/ru/post/326580/.
Один из подписчиков сразу порекомендовал менеджер конфигов Oh My Zsh https://ohmyz.sh/, там из коробки
Установил, выглядит круто, посмотрим, как оно в деле 😁
make и еще уйма всего.Установил, выглядит круто, посмотрим, как оно в деле 😁
Oh My Zsh!
oh my zsh
Oh My Zsh is a popular open-source Zsh configuration framework loved by developers worldwide. It includes 300+ plugins, themes, and tweaks to supercharge your terminal experience.
Наконец-то в Symfony состоялся большой рефакторинг Security!
Экспериментальная версия будет поставлена в рамках
https://wouterj.nl/2020/04/authenticators-new-symfony-security
Экспериментальная версия будет поставлена в рамках
5.1. Инсайты в статье основного контрибьютора этой идеи Wouter De Jong.https://wouterj.nl/2020/04/authenticators-new-symfony-security
Кстати, о релизах. Судя по дорожной карте, Symfony 5.1 выйдет как и все нечетные миноры — в конце мая.
Предварительный список изменений можно найти в серии статей Living on the Edge.
Предварительный список изменений можно найти в серии статей Living on the Edge.
Допустим, мы проектируем пакетный обработчик команд. Чтобы узнавать о каждой успешной операции, добавим простой аргумент-слушатель
Теперь можно, например, инкрементировать прогресс-бар при вызове из консольной команды.
Однако не всегда слушатель будет нужен, поэтому для простоты контракта сделаем его необязательным аргументом. Решение "в лоб":
👹 Fatal error: Default value for parameters with callable type can only be NULL.
Эхх, видимо, без null здесь никак не обойтись. Но мы не сдаемся и красиво комбинируем.
🎉 Ура, так работает.
Плюсы этого подхода по сравнению с if:
• лаконичность: 1 строка вместо 3;
• простота восприятия: одно выражение в начале функции имеет меньшую цикломатическую сложность, чем условие в цикле;
• универсальность: легко переиспользовать во всех подобных ситуациях.
$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-версией мы получаем новый инструмент — атрибуцию (чуть не написал аннотирование) параметров. Можно будет написать более красивые маппинги для контроллеров. Чтобы, например, для эндпойнта
К синтаксису, конечно, придется привыкать, но я почему-то рад, что 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 не пользуется инструментами статического анализа, поэтому иногда можно напороться на что-то вроде
Встречаем 5.1.0-beta1 🎊
Что можно делать с beta-релизом?
Во-первых, пробежаться по списку изменений и просмотреть зацепившие взгляд пулл-реквесты. Так вы не только узнаете о нюансах нововведений, но и обязательно почерпнёте что-то новое о PHP или устройстве Symfony. Кроме того, документация для фичей нередко запаздывает, а о её готовности уже никто специально не сообщает, поэтому просмотр релиза может стать главным источником информации для вас.
Во-вторых, если у вас есть проект на 5.0 и немного свободного времени, можете обновить его в отдельной ветке, прогнать тесты, составить баг-репорты и предложить фиксы. К сожалению, Symfony не пользуется инструментами статического анализа, поэтому иногда можно напороться на что-то вроде
Call to a member function X on null и прочую дичь. Также можно встретить нарушения обратной совместимости, их обязательно поправят перед релизом. Перед созданием issue желательно убедиться, что её ещё не зарепортили и не пофиксили, но при прочих равных лучше создать дубликат, чем не написать вообще.Symfony
Symfony 5.1.0-BETA1 released (Symfony Blog)
Symfony 5.1.0-BETA1 has just been released.
Поговорим про удаление.
В общем случае я рекомендую придерживаться совета Udi Dahan и ничего не удалять. В слабосвязных системах, где сущности ссылаются друг на друга по идентификатору, удаление нарушает связи и историю, усложняет восстановление в случае ошибки. В сильносвязных системах, где сущности явно ссылаются друг на друга (например, через
В целом, при использовании удаления система сильно усложняется, повышаются риски потери информации. Нужно быть особенно осторожным с правами на кнопку ❌. Нужно писать тесты на все варианты каскадных операций. Нужно быть уверенным в том, что бэкапы делаются с достаточной периодичностью.
Альтернатива? Пользуйтесь техникой мягкого удаления, флагом "в архиве", датами активности и т.д. Да, придется добавить соответствующие условия в некоторые запросы, скорректировать индексы (см. PostgreSQL Partial Index). Да, вырастут в объеме хранилища, хотя в наше время это копейки. Но всё это не сравнится с болью восстановления данных, удалённых по ошибке.
Однако есть ситуации, когда необходимо стереть окончательно, например, по требованию пользователя или правоохранительных органов. Система должна быть спроектирована так, чтобы чистка была возможной, хотя бы в полуручном режиме. Благо, как правило, это касается только конкретных модулей, оперирующих чувствительной информацией.
Про удаление в системах с Event Sourcing рекомендую посмотреть фрагмент доклада David Schmitz.
Про чистку в распределённых системах можно почитать в статье инженера Twitter.
Обе ссылки из чата @symfony_php.
В общем случае я рекомендую придерживаться совета 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-файлы и писать в них построчно и очень быстро. Расход памяти константный из коробки, без всяких плясок с кэшем (по факту она, конечно, создает какие-то временные файлы в
Stay tuned! В следующем посте я расскажу, как удобно стримить данные в браузер в формате Excel средствами Symfony HttpFoundation.
А какая аналитика без выгрузок в 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 напрямую в браузер и как вынести повторяющийся код в сервис, чтобы получить лаконичный экшн следующего вида:
Из неё вы узнаете, как стримить 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));
}
});Medium
Экспорт в Excel просто, быстро и красиво
Пример интеграции Box Spout в проект на базе Symfony
Очень классный канал с маленькими тестами по PHP для поддержания тонуса 🥭
https://news.1rj.ru/str/phpquiz
https://news.1rj.ru/str/phpquiz
Telegram
PHP задачи с собеседований
Задачи, тесты и теоретические вопросы по PHP.
Прислать задачу/вопрос в дар: @cyberJohnny
Сотрудничество: @cyberJohnny
Прислать задачу/вопрос в дар: @cyberJohnny
Сотрудничество: @cyberJohnny