Проект 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 и с использованием 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
Представлены 5 интересных докладов, в том числе "Psalm не предлагать: малоизвестные инструменты статического анализа кода" от Александра Новикова из Spiral Scout.
Сразу после выступления мы с Кириллом Несмеяновым встретим вас в дискуссионной Zoom-комнате для обсуждения нюансов статического анализа
Всех ждём 😉
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.GitHub
doctrine-postgres-types/src/Doctrine/DBAL/PostgresTypes/TextArrayType.php at 5b3936e386c0992f76de8ff45487a4597d2c2064 · opensoft/doctrine…
Provide common Doctrine types for Postgres in use at Opensoft - opensoft/doctrine-postgres-types
nikic_php8.pdf
874 KB
Слайды Никиты Попова What's new in PHP 8.0? с конференции PHP fwdays 2020.
https://twitter.com/nikita_ppv/status/1269706258300506114
https://twitter.com/nikita_ppv/status/1269706258300506114
Как бы вы оформили приватный метод, который опционально преобразует несколько значений?
Anonymous Poll
27%
Передача по ссылке
73%
Возврат кортежа
Подведу итоги прений.
Однозначного ответа конечно же нет, есть ±, компромиссы и предпочтения 🤪
Вариант с передачей по ссылке:
➕ немногословный;
➕ менее затратный по ресурсам;
➖ вызов
➖ передача по ссылке — побочный эффект для контекста вызывающей функции.
Вариант с возвращением кортежа:
➖ более многословный (хотя выглядит все равно опрятно);
➖ потребует дополнительную память на массив (но разговоры про это пахнут нанооптимизацией);
➕ строка
➕ передача значений копированием оставляет нам шанс написать чистый (pure) метод.
Ещё было предложено возвращать объект, но это, по сути, частный случай второго варианта. Персональный тип для результата приватного метода избыточен, с
Всем спасибо за обсуждение!
Я выбираю второй вариант с кортежем, так как поддерживаемость и прозрачный control flow перевешивают 😉
Однозначного ответа конечно же нет, есть ±, компромиссы и предпочтения 🤪
Вариант с передачей по ссылке:
➕ немногословный;
➕ менее затратный по ресурсам;
➖ вызов
$this->processByReference($value1, $value2) при чтении не выглядит как мутация переменных, это затрудняет ревью и поддержку;➖ передача по ссылке — побочный эффект для контекста вызывающей функции.
Вариант с возвращением кортежа:
➖ более многословный (хотя выглядит все равно опрятно);
➖ потребует дополнительную память на массив (но разговоры про это пахнут нанооптимизацией);
➕ строка
[$value1, $value2] = $this->processAndReturnTuplet($value1, $value2) говорит сама за себя — я преобразую значения этих переменных;➕ передача значений копированием оставляет нам шанс написать чистый (pure) метод.
Ещё было предложено возвращать объект, но это, по сути, частный случай второго варианта. Персональный тип для результата приватного метода избыточен, с
@psam-return array{string, int} Psalm и без него отловит все ошибки.Всем спасибо за обсуждение!
Я выбираю второй вариант с кортежем, так как поддерживаемость и прозрачный control flow перевешивают 😉
PHP IDE, которой вы пользуетесь
Anonymous Poll
0%
Aptana Studio / Eclipse
0%
Komodo
1%
NetBeans
88%
PhpStorm
7%
Visual Studio Code
0%
Zend Studio
3%
Другая (пишите в обсуждении)
Если кто-то вдруг пропустил, вчера команда JetBrains запустила информативный и красивый таймлайн развития PHP в честь 25-летия языка.
Советую просмотреть сверху вниз и обратно, обязательно найдёте что-то любопытное. Например, я узнал, что в PHP некогда была функция leak(), что в 2014-ом Википедия перешла на HHVM.
Интересно пронаблюдать, как соотносятся во времени различные события. Например, Дмитрий Стогов присоединился к Zend незадолго до запуска Facebook, а Composer появился гораздо позже, чем большая часть фреймворков.
Ну и конечно не забывайте про скидку 50% на PhpStorm, она действует ещё целый день 😉
https://www.jetbrains.com/lp/php-25/
Советую просмотреть сверху вниз и обратно, обязательно найдёте что-то любопытное. Например, я узнал, что в PHP некогда была функция leak(), что в 2014-ом Википедия перешла на HHVM.
Интересно пронаблюдать, как соотносятся во времени различные события. Например, Дмитрий Стогов присоединился к Zend незадолго до запуска Facebook, а Composer появился гораздо позже, чем большая часть фреймворков.
Ну и конечно не забывайте про скидку 50% на PhpStorm, она действует ещё целый день 😉
https://www.jetbrains.com/lp/php-25/
JetBrains: Developer Tools for Professionals and Teams
25 Years of PHP History
Celebrate the PHP anniversary with JetBrains! Follow the timeline of the pivotal moments in PHP history.
Задачка от меня на канале PHP задачи с собеседований с пояснением к решению.
https://t.iss.one/phpquiz/222
https://t.iss.one/phpquiz/222
Telegram
PHP задачи с собеседований
Какая функция выведет "php"?
Однажды я услышал, что геттеры — это плохо.
И прошел все этапы реакции по Кюблер-Росс: отрицание, злость, торг, депрессию, принятие 😂
Надеюсь, этот пост поможет пропустить несколько стадий.
DTO. Если тело геттера
Кстати, я тут заметил, что геттер — это трюк. К существительному добавили глагол, чтобы удовлетворить фомуле subject.actionVerb(?object). Вот только get — это не предоставить, а получить. То есть мы не просим объект поделиться состоянием, мы его отбираем.
Value Object. Если метод представляет собой query по CQS и возвращает некоторое представление (проекцию) объекта, то его название не должно быть шаблонным, оно должно отражать семантику. Например,
Entity. Стараемся применять принцип Tell-Don't-Ask. Попробую сформулировать по-своему. Объект — это состояние + поведение. Если мы программируем объектно-ориентированно, мы должны просить объект совершить действие над принадлежащим ему состоянием, а не отбирать у него состояние и выполнять действие за пределами этого объекта. Если программируем не объектно-ориентированно (что тоже ок), то объекты по определению не используем и геттеры, соответственно, тоже.
---
Итак, в DTO вместо геттеров используем публичные свойства. В Value Object — семантические query методы, и то, если не добавлять "на всякий случай", их будет немного. В Aggregate Root оставляем только command методы.
Как тогда вывести состояние модели? Рассудим логически. Для вывода состояния поведение не нужно, нужно только состояние. Значит и сама модель для этой задачи не нужна. В простом случае состояние можно запросить напрямую из хранилища и выдать сразу без всяких ORM:
И прошел все этапы реакции по Кюблер-Росс: отрицание, злость, торг, депрессию, принятие 😂
Надеюсь, этот пост поможет пропустить несколько стадий.
DTO. Если тело геттера
return $this->privateProperty, заменяем его публичным свойством с аннотацией @psalm-readonly-allow-private-mutation или @psalm-readonly или объявляем весь класс @psalm-immutable. Так мы обеспечиваем инкапсуляцию да ещё и нанооптимизируем код (-N вызовов геттеров). Метод без каких-либо манипуляций не имеет смысла — это 4 строки визуального долга и 1 строка для покрытия тестами. Кстати, я тут заметил, что геттер — это трюк. К существительному добавили глагол, чтобы удовлетворить фомуле subject.actionVerb(?object). Вот только get — это не предоставить, а получить. То есть мы не просим объект поделиться состоянием, мы его отбираем.
Value Object. Если метод представляет собой query по CQS и возвращает некоторое представление (проекцию) объекта, то его название не должно быть шаблонным, оно должно отражать семантику. Например,
Birthday::format(string $format): string, Color::toHex(): string.Entity. Стараемся применять принцип Tell-Don't-Ask. Попробую сформулировать по-своему. Объект — это состояние + поведение. Если мы программируем объектно-ориентированно, мы должны просить объект совершить действие над принадлежащим ему состоянием, а не отбирать у него состояние и выполнять действие за пределами этого объекта. Если программируем не объектно-ориентированно (что тоже ок), то объекты по определению не используем и геттеры, соответственно, тоже.
---
Итак, в DTO вместо геттеров используем публичные свойства. В Value Object — семантические query методы, и то, если не добавлять "на всякий случай", их будет немного. В Aggregate Root оставляем только command методы.
Как тогда вывести состояние модели? Рассудим логически. Для вывода состояния поведение не нужно, нужно только состояние. Значит и сама модель для этой задачи не нужна. В простом случае состояние можно запросить напрямую из хранилища и выдать сразу без всяких ORM:
echo json_encode($pdo->query('select x1, x2 from y where z = ?')->fetch()). В более сложном случае можно посмотреть в сторону Event Sourcing, но про это как-нибудь в другой раз 😉martinfowler.com
bliki: Command Query Separation
a bliki entry for Command Query Separation
Media is too big
VIEW IN TELEGRAM
Открытое собеседование — ищем участников
Бывало, засидишься на одном месте и не знаешь, актуален ли ты еще на рынке... Хотя бы какие там тренды? Что спрашивают-то сейчас вообще на собеседованиях?
Вот и решили с Романом Пронским провести публичное онлайн-собеседование с вопросами на актуальные темы мира PHP.
Трудоустройство не гарантируем, скорее это возможность рассказать о себе, проверить знания, узнать что-то новое да и просто хорошо провести время.
Собеседование будет проходить в режиме стрима в теплой обстановке, примерно как на видео, только я буду без усов 😂
Требования для участия:
• уровень middle/senior;
• PHP 7.x, Composer, PSR;
• ООП, SOLID, coupling/cohesion, вот это все;
• тестирование, PHPUnit;
• желателен опыт с Symfony 4/5;
• SQL, желательно PostgreSQL;
• представление о современных трендах в архитектуре приложений.
Заявки на участие можно отправить до 8 июля через форму: https://forms.gle/ES3nXiwf4ycosGEy9.
Вопросы в личку: @vudaltsov, @pronskiy.
Бывало, засидишься на одном месте и не знаешь, актуален ли ты еще на рынке... Хотя бы какие там тренды? Что спрашивают-то сейчас вообще на собеседованиях?
Вот и решили с Романом Пронским провести публичное онлайн-собеседование с вопросами на актуальные темы мира PHP.
Трудоустройство не гарантируем, скорее это возможность рассказать о себе, проверить знания, узнать что-то новое да и просто хорошо провести время.
Собеседование будет проходить в режиме стрима в теплой обстановке, примерно как на видео, только я буду без усов 😂
Требования для участия:
• уровень middle/senior;
• PHP 7.x, Composer, PSR;
• ООП, SOLID, coupling/cohesion, вот это все;
• тестирование, PHPUnit;
• желателен опыт с Symfony 4/5;
• SQL, желательно PostgreSQL;
• представление о современных трендах в архитектуре приложений.
Заявки на участие можно отправить до 8 июля через форму: https://forms.gle/ES3nXiwf4ycosGEy9.
Вопросы в личку: @vudaltsov, @pronskiy.
Эффективное проектирование агрегатов
Эссе Вона Вернона в трёх коротких частях, которое поможет с пониманием паттерна агрегат.
Прочитав его, вы получите ответы на следующие вопросы:
• как и зачем проектировать маленькие агрегаты;
• что такое истинные инварианты и как их искать;
• почему Value Object предпочтительнее, чем Entity;
• когда нужна мгновенная (immediate), а когда конечная (eventual) согласованность;
• когда позволительно изменять несколько агрегатов в одной транзакции.
По сути, это квинтэссенция нескольких важных глав из синей и красной книги по DDD.
https://dddcommunity.org/library/vernon_2011/
Эссе Вона Вернона в трёх коротких частях, которое поможет с пониманием паттерна агрегат.
Прочитав его, вы получите ответы на следующие вопросы:
• как и зачем проектировать маленькие агрегаты;
• что такое истинные инварианты и как их искать;
• почему Value Object предпочтительнее, чем Entity;
• когда нужна мгновенная (immediate), а когда конечная (eventual) согласованность;
• когда позволительно изменять несколько агрегатов в одной транзакции.
По сути, это квинтэссенция нескольких важных глав из синей и красной книги по DDD.
https://dddcommunity.org/library/vernon_2011/
Иерархические структуры в реляционных СУБД
На этой неделе разгребаю в проекте разные тудушки, поэтому больше читаю и обдумываю, чем пишу код 😂
Вот вам цикл из двух статей, в которых автор сравнивает три подхода к хранению иерархических структур в реляционках:
• список смежных вершин (adjacency list),
• материализованный путь (materialized path),
• вложенное множество (nested set).
Метод вложенных множеств мне всегда казался очень заманчивым, но на работе мы так и не нашли ему применения. Наши наблюдения согласуются с результатами тестов из второй статьи — вложенные множества очень дорогие на вставку/перемещение и при этом не всегда выигрывают на чтение.
В конце первой статьи есть раздел про комбинирование подходов. Особенно удобно комбинировать на проектах с CQRS: списки смежных вершин на запись и списки + материализованные пути на чтение.
https://habr.com/ru/post/46659/
https://habr.com/ru/post/47280/
Забавно. В первой статье есть примеры на Doctrine 1, и они безбожно устарели. Но основной материал будет актуален ещё долго...
На этой неделе разгребаю в проекте разные тудушки, поэтому больше читаю и обдумываю, чем пишу код 😂
Вот вам цикл из двух статей, в которых автор сравнивает три подхода к хранению иерархических структур в реляционках:
• список смежных вершин (adjacency list),
• материализованный путь (materialized path),
• вложенное множество (nested set).
Метод вложенных множеств мне всегда казался очень заманчивым, но на работе мы так и не нашли ему применения. Наши наблюдения согласуются с результатами тестов из второй статьи — вложенные множества очень дорогие на вставку/перемещение и при этом не всегда выигрывают на чтение.
В конце первой статьи есть раздел про комбинирование подходов. Особенно удобно комбинировать на проектах с CQRS: списки смежных вершин на запись и списки + материализованные пути на чтение.
https://habr.com/ru/post/46659/
https://habr.com/ru/post/47280/
Забавно. В первой статье есть примеры на Doctrine 1, и они безбожно устарели. Но основной материал будет актуален ещё долго...
Хабр
Иерархические структуры данных и Doctrine
Введение Хранение иерархических данных (или попросту — деревьев) в реляционных структурах задача довольно нетривиальная и вызывает некоторые проблемы, когда разработчики сталкиваются с подобной...
Как справедливо заметил в обсуждении @dimmount, есть еще четвёртый подход — Closure Table. Мы, кстати, тоже его используем, но я не знал, что это так называется 🙈
По сути, это денормализация материализованного пути в отдельную таблицу ancestor/descendant с заменой like на джойны и подзапросы, подробнее в статье другого автора.
https://habr.com/ru/post/193166/
По сути, это денормализация материализованного пути в отдельную таблицу ancestor/descendant с заменой like на джойны и подзапросы, подробнее в статье другого автора.
https://habr.com/ru/post/193166/
Хабр
Хранение деревьев в базе данных. Часть первая, теоретическая
Полгода назад написал бандл ClosureTable для фреймворка Laravel 3. Поводом для написания стала вот эта замечательная презентация Билла Карвина о способах хранени...
Теперь канал можно поддержать 💵 на Patreon
Для фона долго искал что-то с глубинным смыслом 😂
На всякий случай оставлю тут исходное изображение.
https://www.patreon.com/phpyh
Для фона долго искал что-то с глубинным смыслом 😂
На всякий случай оставлю тут исходное изображение.
https://www.patreon.com/phpyh
Patreon
Get more from Пых on Patreon
создаёт блог о разработке на PHP https://t.iss.one/phpyh