ar
Feedback
Библиотека пхпшника | PHP, Laravel, Symfony, CodeIgniter

Библиотека пхпшника | PHP, Laravel, Symfony, CodeIgniter

الذهاب إلى القناة على Telegram

Все самое полезное для пхпшника в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/bca892d6 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5d13cd6fa92100ee6f68b

إظهار المزيد

📈 نظرة تحليلية على قناة تيليجرام Библиотека пхпшника | PHP, Laravel, Symfony, CodeIgniter

تُعد قناة Библиотека пхпшника | PHP, Laravel, Symfony, CodeIgniter (@phpproglib) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 10 715 مشتركاً، محتلاً المرتبة 11 607 في فئة التكنولوجيات والتطبيقات والمرتبة 61 272 في منطقة روسيا.

📊 مؤشرات الجمهور والحراك

منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 10 715 مشتركاً.

بحسب آخر البيانات بتاريخ 04 يونيو, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -37، وفي آخر 24 ساعة بمقدار -1، مع بقاء الوصول العام مرتفعاً.

  • حالة التحقق: غير موثّقة
  • معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 15.37‎%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً 8.92‎% من ردود الفعل نسبةً إلى إجمالي المشتركين.
  • وصول المنشورات: يحصل كل منشور على متوسط 1 647 مشاهدة. وخلال اليوم الأول يجمع عادةً 956 مشاهدة.
  • التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 9.
  • الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل php, laravel, пхпшника, artisan, api.

📝 الوصف وسياسة المحتوى

يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
Все самое полезное для пхпшника в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/bca892d6 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5d13cd6fa92100ee6f68b

بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 05 يونيو, 2026) تحافظ القناة على حداثتها ومستوى وصول مرتفع. وتُظهر التحليلات تفاعلاً نشطاً من الجمهور، ما يجعلها نقطة تأثير مهمة ضمن فئة التكنولوجيات والتطبيقات.

10 715
المشتركون
-124 ساعات
-117 أيام
-3730 أيام
أرشيف المشاركات
📌 Итог Preloading — бесплатный буст перформанса без изменения кода. Но только если прелоадите то, что реально используется, обрабатываете ошибки загрузки и не забываете reload FPM при деплое. В связке с Deployer это одна строка в пайплайне. Библиотека пхпшника (https://t.me/phpproglib) Библиотека пхпшника

⚡️ Как настроить OPcache Preloading в PHP 8.3 и не положить прод Прелоадинг компилирует классы один раз при старте FPM и держит их в shared memory. Минус ~30% к latency на каждый реквест. Но 90% настраивают его неправильно. 1️⃣ Включаем в php.ini ini
opcache.enable=1
opcache.preload=/var/www/app/preload.php
opcache.preload_user=www-data
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
validate_timestamps=0 — на проде файлы не меняются между деплоями. Не тратим syscall на stat() каждого файла. Сброс кэша — через рестарт FPM (Deployer сделает сам). 2️⃣ Пишем preload.php правильно Наивный подход — загрузить всё подряд. Получите fatal при старте FPM. php
// ❌ так делать нельзя
$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('/var/www/app/vendor')
);
foreach ($files as $file) {
    if ($file->getExtension() === 'php') {
        opcache_compile_file($file->getRealPath());
    }
}
Проблема: интерфейсы, трейты, абстрактные классы — порядок загрузки важен. Одна битая зависимость = FPM не стартует. php
// ✅ через Composer class map + мягкий fallback
// preload.php
require __DIR__ . '/vendor/autoload.php';

$classMap = require __DIR__ . '/vendor/composer/autoload_classmap.php';

$ignore = [
    '/Tests/', '/test/', '/fixtures/', '/stub/',
    '/migrations/', '/seeders/', '/factories/',
];

foreach ($classMap as $class => $file) {
    foreach ($ignore as $pattern) {
        if (str_contains($file, $pattern)) continue 2;
    }

    try {
        if (!class_exists($class, false)
            && !interface_exists($class, false)
            && !trait_exists($class, false)
            && !enum_exists($class, false)
        ) {
            require_once $file;
        }
    } catch (\Throwable) {
        // класс не загрузился — пропускаем, не роняем FPM
    }
}
class_exists($class, false)false отключает автолоад. Проверяем, не загружен ли класс уже по цепочке зависимостей. 3️⃣ Для Laravel — берём готовое bash
composer require laragear/preload
php artisan preload:generate
Пакет анализирует opcache_get_status() на работающем приложении и генерирует preload-список из реально используемых классов. Не из всего vendor. php
// config/preload.php
return [
    'condition' => true,
    'memory_limit' => 64, // MB под прелоад
    'include' => [],
    'exclude' => [
        'Illuminate\Support\Facades\*',
    ],
    'overwrite' => true,
];
Фасады исключаем — они резолвятся лениво через контейнер, прелоад им мешает. 4️⃣ Проверяем, что всё работает php
// диагностика из CLI не работает, только через FPM
// создаём роут или скрипт

$status = opcache_get_status();
$preload = $status['preload_statistics'] ?? [];

echo "Preloaded classes: " . count($preload['classes'] ?? []) . PHP_EOL;
echo "Preloaded funcs:   " . count($preload['functions'] ?? []) . PHP_EOL;
echo "Memory used:       " . round($preload['memory_consumption'] / 1024 / 1024, 2) . " MB" . PHP_EOL;
Важно: php -r "opcache_get_status();" в CLI покажет пустоту. Прелоад живёт только в памяти FPM-мастера. 5️⃣ Интегрируем в деплой php
// deploy.php (Deployer)
task('php-fpm:reload', function () {
    run('sudo systemctl reload php8.3-fpm');
});

after('deploy:symlink', 'php-fpm:reload');
reload, не restart. Reload делает graceful: мастер-процесс перечитывает конфиг и прелоад, старые воркеры дорабатывают текущие реквесты. Zero downtime. 6️⃣ Подводные камниПамять: прелоад жрёт shared memory. 2000 классов ≈ 30–50 MB. Если memory_consumption мал — часть классов молча не загрузится — Наследование: если прелоадите дочерний класс, родитель должен быть загружен раньше. Composer classmap решает это, ручной glob — нет — Enum + PHP 8.1+: backed enum'ы с интерфейсами иногда падают при прелоаде. Если ловите segfault при старте FPM — исключайте enum'ы и обновляйтесь — Xdebug: прелоад несовместим с xdebug. На dev-окружении выключайте opcache.preload

🛠 Реактивный SPA на Symfony без единой строчки React Согласись, что неприятен JS-налог: дублирование роутинга, валидации и моделей на фронте, возня с Vite и раздутые бандлы. Hotwire переворачивает схему — сервер шлёт готовые куски HTML, Turbo подменяет DOM, страница не перезагружается. 🚀 Стек первой части туториала: — фреймворк Symfony 7.4 + AssetMapper (без Webpack и Node); — компоненты Turbo Drive/Frames/Streams для бесшовных обновлений; — библиотека Stimulus там, где реально нужен JS (drag & drop); — протокол Mercure через SSE вместо возни с WebSockets на PHP; — стили Tailwind через symfonycasts-бандл (снова без npm); Крутой приём → колонки доски генерятся из PHP Enum:
enum TaskStatus: string
{
    case TODO = 'todo';
    case IN_PROGRESS = 'in_progress';
    case DONE = 'done';
}

#[ORM\Column(length: 50, enumType: TaskStatus::class)]
private TaskStatus $status = TaskStatus::TODO;
Достаточно передать TaskStatus::cases() в Twig и новый статус добавится одной строкой в Enum. Никакой правки шаблонов и магических строк (Doctrine сам сериализует значения в varchar). Во второй части туториала будет немного нативного HTML5 DnD и Turbo Streams через Mercure для синхронизации между вкладками. 🔗 Подробнее Библиотека пхпшника

Сохраняйте шпаргалку по паттернам проектирования

До 31 мая можно забрать любой курс Proglib Academy со скидкой 40% Если давно хотели прокачаться в Python, ML, алгоритмах или
До 31 мая можно забрать любой курс Proglib Academy со скидкой 40% Если давно хотели прокачаться в Python, ML, алгоритмах или AI-агентах, сейчас самое время выбрать программу и начать обучение по сниженной цене. 🎁 Разработка AI-агентов от 49.000 ₽ (вместо 69.000 ₽) Практический курс по разработке AI-агентов для автоматизации задач, работы и собственных проектов 🎁 Курс AgentOps129.000 ₽ (вместо 149.000 ₽) Для разработчиков и LLM-инженеров, которые хотят внедрять AI-логику в бэкенд и сохранять стабильность сервиса. 🎁 Математика для разработки AI-моделей 23.990 ₽ (вместо 31.990 ₽) Практическая база по математике для анализа данных, ML и дальнейшего развития в AI. 🎁 Математика для Data Scienceот 29.990 ₽ (вместо 39.990 ₽) Курс для тех, кто хочет решать задачи, которые дают на собеседованиях на позицию дата-сайентиста в бигтехе. 🎁 ML для старта в Data Science28.990 ₽ (вместо 38.990 ₽) Разберётесь в машинном обучении: от базовых понятий и линейных моделей до ансамблей, бустинга и рекомендательных систем. 🎁 Основы IT для непрограммистов16.990 ₽ (вместо 28.990 ₽) Курс для IT-рекрутеров, маркетологов, проджектов, продактов и всех, кто работает с IT, но не пишет код. 🎁 Архитектуры и шаблоны проектирования27.990 ₽ (вместо 37.900 ₽) Освоите основные паттерны проектирования и прокачаете навыки архитектора программного обеспечения. 🎁 Специалист по ИИ89.000 ₽ (вместо 113.900 ₽) Курс для тех, кто хочет получить профессию в сфере ИИ, собрать портфолио из 5 проектов и научиться разрабатывать сложных AI-агентов. 🎁 Алгоритмы и структуры данных 33.990 ₽ (вместо 57.990 ₽) Подготовитесь к алгоритмическим собеседованиям, разберёте структуры данных и научитесь писать более эффективный код. 🎁 Программирование на языке Python27.990 ₽ (вместо 47.390 ₽) Освоите Python на практике: без сухой теории, с пошаговой прокачкой навыков и итоговым проектом в портфолио. 🙌 Выбирайте курс по ссылке, оставляйте заявку, и менеджер поможет подобрать программу под ваши цели — https://clc.to/HglYew

🔧 Работаем с kubectl Нужно быстро форварднуть порт сервиса из Kubernetes на localhost для отладки? kubectl port-forward — ваш туннель в кластер без Ingress, LoadBalancer и VPN. 🔹 Зачем это нужно — Доступ к сервисам внутри кластера без внешнего endpoint-а. — Подключение к БД, Redis, RabbitMQ для отладки прямо с рабочей машины. — Не нужно менять конфиги, создавать NodePort или разворачивать Ingress. 🔹 Как использовать — К поду: kubectl port-forward pod/my-pod 8080:80 — К сервису: kubectl port-forward svc/my-service 5432:5432 — К деплойменту: kubectl port-forward deploy/my-app 3000:3000 — Другой локальный порт: kubectl port-forward svc/postgres 15432:5432 (localhost:15432 → postgres:5432) — Слушать на всех интерфейсах: kubectl port-forward --address 0.0.0.0 svc/my-service 8080:80

💥 «Clean Code» — самая переоценённая книга в индустрии Я не говорю, что книга плохая. Я говорю, что она принесла массовому разработчику больше вреда, чем пользы. Uncle Bob написал набор рекомендаций, а индустрия превратила их в религию. «Метод не длиннее 5 строк». «Каждый класс — одна ответственность». «Имя должно быть самодокументирующим». Звучит красиво, а на практике: — 47 приватных методов, каждый вызывается ровно один раз — AbstractNotificationStrategyProviderFactory — 6 слоёв абстракции ради эндпоинта, который возвращает список товаров — Интерфейс с единственной реализацией «на будущее» Такие проекты всегда «чистые». И на каждом из них новый разработчик тратит две недели, чтобы найти бизнес-логику. Откройте исходники любого успешного фреймворка в вашей экосистеме. God-классы, методы на 80 строк, комменты «don't ask why». Работает, развивается, приносит деньги. Моё мнение: → Код должен быть понятен через 6 месяцев. Не «чист» — а понятен. → Абстракция оправдана, когда у неё больше одной реализации прямо сейчас, а не «потенциально». → YAGNI важнее SOLID. Книгу стоит прочитать, принять к сведению, понять фундаментальные основы. Но не применять подходы в каждом методе. 💬 Пишите в комменты Ваше мнение

🔍 Ловим каждый SQL-запрос в Laravel Дебажить «а что вообще улетело в базу?» зачастую непросто. Но через DB::listen() можно перехватить каждый запрос вместе с биндингами и временем выполнения.
DB::listen(function (QueryExecuted $query) {
    dump($query->sql);      // select * from `users` where `id` = ? limit 1
    dump($query->bindings); // [0 => 1]
    dump($query->time);     // 6.05 (ms)
});
По сути, это не только про дебаг. Можно слать алерт в Slack, если запрос тормозит дольше порога. А для полного лога сессии есть связка DB::enableQueryLog() + DB::getRawQueryLog(). Кто-нибудь использует это в проде (или только whenQueryingForLongerThan)? Библиотека пхпшника

Какой из перечисленных алгоритмов хэширования поддерживается функцией password_hash()?
Anonymous voting

📌 Зачем дата-сайентисту матанализ? Основная компетенция специалиста по Data Science – способность анализировать и интерпрети
+6
📌 Зачем дата-сайентисту матанализ? Основная компетенция специалиста по Data Science – способность анализировать и интерпретировать данные, а математика является фундаментом для начала работы. В карточках мы разбираем основные разделы математики, с которых стоит начать изучение специалисту по анализу данных. Хотите подготовиться к офферу или подтянуть знания? Оставляйте заявку на наш курс по математике для Data Science 💙 P.S. Только до 31 мая на курс (и вообще на все программы Академии) действует СКИДКА 40% А как у вас дела с высшей математикой? ❤️ — Помню всё 🔥 — Знаю основы 🌚 — Ничего не знаю 🏃‍♀️ Proglib Academy

⚔️ nwidart vs internachi Считается, что internachi/modular быстрее nwidart/laravel-modules, потому что тащит модули через нативный Composer, а не сканирует module.json. Автор прогнал 50 запросов на батч от 25 до 200 модулей на Laravel 13 / PHP 8.4 — и цифры рушат это утверждение. ⚡️ Что показал замер с OPcache ON: — на 25–175 модулях nWidart стабильно быстрее по бутстрапу (+12 мс на каждые 25 модулей, линейно); — у internachi на 75–100 модулях нелинейный спайк от резолва classmap; — перелом наступает только на 200 модулях, где modules:cache даёт 621 мс против 1521 мс у nWidart — в 2.4 раза быстрее; — по памяти internachi выигрывает всегда: на 10–12 МБ меньше на запрос (registry nWidart живёт в куче, classmap — в shared memory OPcache). OPcache режет время бута у internachi на ~49%, у nWidart — только на ~33%. Логично: байткод кэшируется, а чтение module.json на каждом запросе — нет. 📊 Инженерный вывод: до 50 модулей разница в пределах шума, можно руководствоваться удобством DX. На 50–175 nWidart предсказуемее, а свыше 175 спасает только internachi с modules:cache, иначе линейный скан сильно замедлит работу. 🔗 Репа с данными

🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24 Знакомьтесь, Сергей Нотевский. AI Platform Lead в
🔥 База по экономике токенов и кэшированию от AI Platform Lead из Bitrix24 Знакомьтесь, Сергей Нотевский. AI Platform Lead в Bitrix24. Он один из ключевых экспертов нашего курса AgentOps. На своих лекциях он детально разбирает экономику AI-агентов, кэширование токенов, LLM-инфраструктуру и вывод генеративных систем в стабильный прод. Мы попросили Сергея поделиться материалами для тех, кто хочет оптимизировать косты на LLM в проде. Сохраняйте методичку по prefix cache метрике, которая напрямую влияет на ваши деньги. Как говорят создатели Manus:
“KV-cache hit rate is the single most important metric for a production-stage AI agent.”
🛠 Что внутри методички (комбо из 3 статей + код):
Экономика кэширования — особенности провайдеров и как правильно считать затраты. Частые анти-паттерны — почему ваш кэш постоянно сбрасывается и вы платите больше. Кэш в AI-агентах — специфика работы с памятью в автономных системах.
🍒 Вишенка на торте: готовый SKILL для агента, который делает ревью вашего проекта, находит анти-паттерны и предотвращает низкое попадание в кэш. — Забрать комбо-материалы на GitHub P.S. Если хотите послушать Сергея вживую — ловите его на конференциях Kode Waves (май), Conversations AI и Highload Spb (июнь). 🎁 Акция в честь старта продаж! Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок. 👉 Забрать 2 курса по цене 1 и начать обучение

🚀 Laravel 13.6.0: дебаунс для очередей из коробки Частое сохранение документа плодит в очереди дублирующиеся задачи переиндексации. Раньше спасал интерфейс ShouldBeUnique, который блокирует диспатчи на входе. Теперь добавлен полноценный debounce — выполняется только последняя задача. Достаточно повесить атрибут на класс, и все дубли в заданном временном окне схлопываются на этапе выполнения:
#[DebounceFor(30, maxWait: 120)]
class RebuildSearchIndex implements ShouldQueue
{
    public function __construct(public int $documentId) {}

    public function debounceId(): string
    {
        return (string) $this->documentId;
    }

    public function handle(): void
    {
        SearchIndex::rebuild($this->documentId);
    }
}
Запустить дебаунс можно и без правки класса: dispatch(new SyncData($id))->debounceFor(30). Другие обновления релиза:роут /up отдает JSON по заголовку Accept (полезно для балансировщиков); — добавлен JsonFormatter для структурных логов; — появился транспорт Cloudflare Email. 🔗 Читать подробнее Библиотека пхпшника #release_radar

🔥 Еще с Laravel 8 появился Prunable С его помощью вы можете удалять старые модели по расписанию. Вам больше не нужно писать
🔥 Еще с Laravel 8 появился Prunable С его помощью вы можете удалять старые модели по расписанию. Вам больше не нужно писать пользовательские команды. Библиотека пхпшника #vardump

😎 Знакомьтесь с экспертом Proglib.academy: AI-архитектор Андрей Носов Андрей — один из ключевых спикеров нашего курса AgentO
😎 Знакомьтесь с экспертом Proglib.academy: AI-архитектор Андрей Носов Андрей — один из ключевых спикеров нашего курса AgentOps. Он выстраивает архитектуру, которая выживает в суровом проде и активно делится своим опытом. За что его ценит IT-комьюнити: 🟣 Топ-спикер AI Conf 2026
Его доклад про мифы семантического поиска и провалы Naive RAG стал одним из самых рейтинговых на конференции.
🟣 Эксперт по GraphRAG и Knowledge Graphs
Андрей внедряет инженерный подход в сложные системы, заменяя «слепую веру» в эмбеддинги строгой логикой графов.
🟣 Автор «14 кругов ада для RAG»
Разработал уникальный набор из 14 unit-тестов, на которых ломается стандартный векторный поиск (от слепоты к отрицаниям до конфликта версий).
🟣 Спикер Saint HighLoad
Регулярно выступает на крупнейших хайлоад-площадках, разбирая архитектуру отказоустойчивых ИИ-сервисов.
Андрей упаковал свои наработки в Google Colab, где можно пощупать 14 сценариев ошибок RAG и их решения: 🔗 Забрать Colab-ноутбук На курсе Андрей отвечает за самые «мясные» блоки: RAG, оркестрацию агентов и их промышленную эксплуатацию. Узнать больше о программе и обучении у Андрея: 👉 Курс о том, как внедрять AI-логику в бэкенд и сохранять стабильность сервиса Так, продолжаем знакомить вас с командой? 👍 — Да, ждем новых лиц 🔥 — Пойду тестить Colab Носова

🔧 Работаем с kubectl Под завис в Pending, а вы не знаете почему. Нет ресурсов, не тот nodeSelector или PVC не биндится? kubectl describe pod покажет секцию Events — там Kubernetes прямым текстом пишет причину. 🔹 Зачем это нужно — Events содержат сообщения от scheduler, kubelet и controller manager. — Показывает FailedScheduling с причиной: Insufficient cpu, node affinity mismatch, и т.д. — Видны ошибки pull-а образов, mount томов, readiness/liveness probe failures. 🔹 Как использовать — Полное описание пода: kubectl describe pod my-pod — Смотреть только Events (хак): kubectl describe pod my-pod | grep -A 20 "Events:" — События всего неймспейса: kubectl get events --sort-by='.lastTimestamp' — Только warning-и: kubectl get events --field-selector type=Warning — Следить за новыми: kubectl get events -w 💡 Про-тип: Events живут только 1 час по умолчанию. Если под висит в Pending давно и Events пустые, посмотрите kubectl get events -A --sort-by='.lastTimestamp', возможно, событие уже из другого неймспейса.

✔️ PHP-тест: клон, который мутирует оригинал Пользователь нажал «Повторить заказ». Цены в его старом заказе изменились. 📦 Задание Фича: кнопка «Повторить заказ» в личном кабинете. Копирует предыдущий заказ, применяет промокод, создаёт новый черновик. Оригинал помечается флагом repeated для аналитики. Через неделю — тикет от бухгалтерии: суммы в старых заказах не сходятся с тем, что было при оплате. Проблема только у заказов, которые хотя бы раз «повторяли». Суммы занижены ровно на размер скидки по промокоду.
// src/Order/Order.php
class Order
{
    public function __construct(
        private ?int   $id,
        private int    $userId,
        private array  $items, 
        private string $status,
        private bool   $repeated = false,
    ) {}

    public function getId(): ?int      { return $this->id; }
    public function getItems(): array  { return $this->items; }
    public function getStatus(): string { return $this->status; }

    public function resetForRepeat(): void
    {
        $this->id       = null;
        $this->status   = 'draft';
        $this->repeated = false;
    }

    public function markAsRepeated(): void
    {
        $this->repeated = true;
    }

    public function calculateTotal(): int
    {
        return array_sum(array_map(
            fn(OrderItem $i) => $i->getSubtotal(),
            $this->items
        ));
    }
}

// src/Order/OrderItem.php
class OrderItem
{
    public function __construct(
        private int $productId,
        private int $qty,
        private int $price,
    ) {}

    public function getProductId(): int { return $this->productId; }
    public function getQty(): int       { return $this->qty; }
    public function getPrice(): int     { return $this->price; }

    public function getSubtotal(): int
    {
        return $this->qty * $this->price;
    }

    public function applyDiscount(int $percent): void
    {
        $this->price = (int) round(
            $this->price * (1 - $percent / 100)
        );
    }
}

// src/Order/RepeatOrderHandler.php
class RepeatOrderHandler
{
    public function __construct(
        private OrderRepository $orders,
        private PromoService    $promo,
    ) {}

    public function handle(int $originalId, ?string $promoCode): Order
    {
        $original = $this->orders->findById($originalId);

        $copy = clone $original;
        $copy->resetForRepeat();

        if ($promoCode !== null) {
            $discount = $this->promo->resolve($promoCode);

            foreach ($copy->getItems() as $item) {
                $item->applyDiscount($discount->percent);
            }
        }

        $this->orders->save($copy);

        $original->markAsRepeated();
        $this->orders->save($original);

        return $copy;
    }
}

// src/Repository/OrderRepository.php
class OrderRepository
{
    public function __construct(private PDO $pdo) {}

    public function save(Order $order): void
    {
        if ($order->getId() === null) {
            $this->insert($order);
        } else {
            $this->update($order);
        }
    }

    private function update(Order $order): void
    {
        $this->pdo->prepare(
            'UPDATE orders SET status = ?, repeated = ? WHERE id = ?'
        )->execute([$order->getStatus(), (int) $order->isRepeated(), $order->getId()]);

        $this->pdo->prepare('DELETE FROM order_items WHERE order_id = ?')
            ->execute([$order->getId()]);

        foreach ($order->getItems() as $item) {
            $this->pdo->prepare(
                'INSERT INTO order_items (order_id, product_id, qty, price) VALUES (?, ?, ?, ?)'
            )->execute([$order->getId(), $item->getProductId(), $item->getQty(), $item->getPrice()]);
        }
    }
}
🔹 Задачи — Объяснить, каким образом цены в оригинальном заказе оказались изменены в базе — Исправить код так, чтобы оригинал гарантированно не мутировал Ставьте → 🔥 если нравится формат. Если нет → 🌚 💬 Решения пишите в комменты под спойлер — сравним подходы.