Оба инструмента про «выполнить что-то не прямо сейчас». Но они решают разные задачи, и смешивать их не стоит.
Cron — планировщик. Он про время. «Запусти это в 03:00». «Запусти каждые 5 минут». Ему всё равно, что именно запускать — он просто триггерит процесс по расписанию.
Queue — очередь. Она про события и нагрузку. «Отправь письмо, когда пользователь зарегистрировался». «Обработай 10 000 строк CSV асинхронно». Очередь не знает про время, она знает про задачи.
🔹 Где Cron уместен
— Агрегация данных за период (ночные отчёты)
— Очистка устаревших записей
— Синхронизация с внешним источником по расписанию
— Задачи, которые должны выполняться независимо от пользовательской активности
🔹 Где Queue уместен
— Всё, что пользователь спровоцировал, но ждать не должен
— Задачи с потенциальными ретраями (внешний API может упасть)
— Параллельная обработка (несколько воркеров)
— Отправка уведомлений, генерация файлов, вебхуки
Если ты пишешь cron-задачу, которая ищет записи со статусом "pending" — ты уже строишь очередь. Используй нормальную: Redis + Laravel Horizon, RabbitMQ, SQS, Beanstalkd.
Комбинация: cron запускает job, который кладёт задачи в очередь. Это нормально — диспетчер по расписанию и обработчики асинхронно.
#элементарный_выбор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🥱5❤2
Forwarded from Библиотека собеса по PHP | вопросы с собеседований
«Мы перешли на генераторы, чтобы не грузить память» — но память всё равно растёт 👇
📦 Задание
Команда переписала импорт CSV на генераторы — раньше падало с OOM на файлах больше 500 МБ. После рефакторинга память перестала расти... на стейджинге. На проде с реальными файлами на 2M+ строк потребление всё равно ползёт вверх.
// src/Import/CsvImporter.php
class CsvImporter
{
private array $processedIds = [];
private array $errors = [];
private int $totalRows = 0;
public function import(string $filePath): ImportResult
{
foreach ($this->readRows($filePath) as $row) {
$this->totalRows++;
try {
$id = $this->processRow($row);
$this->processedIds[] = $id;
} catch (RowException $e) {
$this->errors[] = [
'row' => $this->totalRows,
'message' => $e->getMessage(),
'data' => $row,
];
}
}
return new ImportResult($this->processedIds, $this->errors, $this->totalRows);
}
private function readRows(string $filePath): \Generator
{
$handle = fopen($filePath, 'r');
$headers = fgetcsv($handle);
while (($raw = fgetcsv($handle)) !== false) {
yield array_combine($headers, $raw);
}
fclose($handle);
}
private function processRow(array $row): int
{
// Валидация, маппинг, вставка в БД
// Возвращает inserted ID
return $this->repository->upsert($row);
}
}
// src/Import/ImportResult.php
class ImportResult
{
public function __construct(
public readonly array $processedIds,
public readonly array $errors,
public readonly int $totalRows,
) {}
}
🔹 Задачи
— Найти все источники роста памяти
— Объяснить, почему проблема не воспроизводится на стейджинге
— Предложить решение
Ставьте → 🔥 если нравится формат. Если нет → 🌚
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤4👍1
🔒 Prepared statements
Prepared statements защищают от инъекций. Но также их можно осознанно использовать для оптимизации.
Когда вы выполняете один и тот же запрос в цикле — БД каждый раз парсит и строит план выполнения заново.
Плохо:
Хорошо — prepare один раз, execute много раз:
БД кеширует план выполнения запроса\. На 10 000 итераций разница ощутима\.
Ещё лучше — batch-запрос:
Библиотека пхпшника
#vardump
Prepared statements защищают от инъекций. Но также их можно осознанно использовать для оптимизации.
Когда вы выполняете один и тот же запрос в цикле — БД каждый раз парсит и строит план выполнения заново.
Плохо:
foreach ($userIds as $id) {
$pdo->query("SELECT * FROM users WHERE id = $id"); // парсинг + план на каждой итерации
}Хорошо — prepare один раз, execute много раз:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
foreach ($userIds as $id) {
$stmt->execute([':id' => $id]);
$user = $stmt->fetch();
}БД кеширует план выполнения запроса\. На 10 000 итераций разница ощутима\.
Ещё лучше — batch-запрос:
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($userIds);
$users = $stmt->fetchAll();
// Один запрос вместо NБиблиотека пхпшника
#vardump
👍6❤1
Их сравнивают как два способа работать с БД. На самом деле это два разных взгляда на то, что такое доменная модель.
Eloquent — Active Record. Модель знает о базе данных. Она сама сохраняет себя, сама удаляет, сама строит запросы. Это удобно и это цена: бизнес-логика и слой персистентности перемешаны с рождения.
// Eloquent — модель и есть запись в таблице
$user = User::where('email', $email)->firstOrFail();
$user->status = 'active';
$user->save();
Doctrine — Data Mapper. Модель ничего не знает о БД. EntityManager знает, как сохранить объект. Объект остаётся чистым PHP-классом.
// Doctrine — модель не знает про persist
$user = $this->repository->findByEmail($email);
$user->activate(); // метод в домене, не save()
$this->em->flush();
— CRUD-приложения, где домен тонкий
— Быстрый старт, меньше конфигурации
— Команда знакома с Laravel — не надо учить новый mental model
— Сложные агрегаты с инвариантами
— Хочешь тестировать доменную логику без БД
— Один объект — несколько таблиц или наоборот
— Богатая доменная модель (DDD, агрегаты, value objects)
— Нужна полная изоляция домена от инфраструктуры
— Сложные маппинги, наследование, polymorphic relations с контролем
Главный сигнал: если ты ловишь себя на том, что пишешь методы в Eloquent-модели, которые делают $this->save() — домен начинает протекать. Это не баг Eloquent, это его дизайн. Просто решай осознанно.
#элементарный_выбор
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍9❤2🥱1
Forwarded from Библиотека задач по PHP | тесты, код, задания
Какой признак характерен для архитектуры CQRS?
Anonymous Quiz
12%
Разделение клиентской и серверной частей
82%
Разделение команд (изменений) и запросов (чтения)
3%
Использование одного сервиса для чтения и записи
2%
Отказ от реактивщины
🥱6👍1🔥1
Стандартный ответ — «потому что in-memory». Это правда, но не объяснение. Куча баз данных хранит данные в памяти и не показывает таких цифр. Настоящий ответ в другом: каждое архитектурное решение Redis убирает конкретный источник latency.
🧵 Один поток
Redis обрабатывает команды последовательно в один поток. Никаких мьютексов, никакого context switching. Тысячи соединений при этом обслуживаются через epoll/kqueue — один поток следит за всеми сокетами через I/O multiplexing и не блокируется ни на одном из них.
📦 Адаптивные структуры
Hash с небольшим числом ключей хранится не как хэш-таблица, а как listpack — плотный байтовый массив, который влезает в один cache line. Никаких pointer chasing. Только когда данных становится много, Redis переключается на полноценную хэш-таблицу. Sorted Set — одновременно хэш-таблица и skiplist. O(1) по ключу и O(log N) для range-запросов без второго прохода.
💾 fork()
Снапшот RDB пишется дочерним процессом после fork(). Благодаря CoW основной процесс продолжает работу и не видит никакой паузы — ОС копирует только изменённые страницы памяти. AOF идёт дальше и логирует каждую команду, давая возможность восстановиться до последней записи. Точнее, но дороже — компромисс осознанный.
🧹 bounded expiry
Ключи с истёкшим TTL удаляются двумя способами: лениво при обращении и активно — каждые 100 мс Redis сэмплирует случайную выборку и чистит просроченные. Если таких больше 25%, повторяет цикл. Это не O(N) по всей базе, а предсказуемая нагрузка с жёстким потолком по CPU.
Redis — это не «база данных в памяти». Это система, где каждый выбор — однопоток, listpack, fork(), bounded expiry — существует потому, что альтернатива добавляла latency. In-memory здесь просто необходимое условие, а не причина скорости.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍5❤1
Классика: у тебя Value Object с readonly-свойствами, и нужно вернуть копию с одним изменённым полем. До 8.4 приходилось делать фабричный метод вручную:
class Money {
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {}
public function withAmount(int $amount): static
{
return new static($amount, $this->currency);
}
}В PHP 8.4 появился clone with:
$price = new Money(1000, 'USD');
$discounted = clone $price with {
amount: 850,
};
Никаких withField() методов на каждый чих. clone with работает даже с readonly — это единственное место, где PHP позволяет их «изменить» без рефлексии.
#vardump
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8👍6❤2🔥2👾1
🔥 Laravel Facades красота или костыль?
Скрытые зависимости, нарушение SRP, привязка к фреймворку через __callStatic() — звучит страшно. Но 64% PHP-девов используют Laravel и не парятся.
Как пишете вы, Facades или DI? И считаете ли, что Тейлор красиво «продал» антипаттерн?
💬 Пишите ваше мнение в комментарии
#междусобойчик
// Читаемо и быстро
Cache::get('user:' . $id);
// Явно и тестируемо
public function __construct(
private CacheInterface $cache,
) {}
Скрытые зависимости, нарушение SRP, привязка к фреймворку через __callStatic() — звучит страшно. Но 64% PHP-девов используют Laravel и не парятся.
Как пишете вы, Facades или DI? И считаете ли, что Тейлор красиво «продал» антипаттерн?
#междусобойчик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5😁3🌚1
Forwarded from Библиотека собеса по PHP | вопросы с собеседований
Код прошёл нагрузочное тестирование. На проде деньги задвоились 👇
📦 Задание
Фича: пользователь может переводить бонусные баллы другу. Логика простая — проверить баланс, списать, начислить. Тесты зелёные, нагрузочное прогнали — всё ок. Через три дня после релиза: у нескольких пользователей баланс ушёл в минус, у других — задвоился.
// src/Bonus/BonusTransferService.php
class BonusTransferService
{
public function __construct(
private PDO $pdo,
private BonusRepository $repo,
) {}
public function transfer(int $fromId, int $toId, int $amount): void
{
$this->pdo->beginTransaction();
try {
$fromBalance = $this->repo->getBalance($fromId);
if ($fromBalance < $amount) {
throw new InsufficientFundsException();
}
$this->repo->debit($fromId, $amount);
$this->repo->credit($toId, $amount);
$this->pdo->commit();
} catch (Throwable $e) {
$this->pdo->rollBack();
throw $e;
}
}
}
// src/Bonus/BonusRepository.php
class BonusRepository
{
public function __construct(private PDO $pdo) {}
public function getBalance(int $userId): int
{
$stmt = $this->pdo->prepare(
'SELECT balance FROM bonus_accounts WHERE user_id = ?'
);
$stmt->execute([$userId]);
return (int) $stmt->fetchColumn();
}
public function debit(int $userId, int $amount): void
{
$stmt = $this->pdo->prepare(
'UPDATE bonus_accounts SET balance = balance - ? WHERE user_id = ?'
);
$stmt->execute([$amount, $userId]);
}
public function credit(int $userId, int $amount): void
{
$stmt = $this->pdo->prepare(
'UPDATE bonus_accounts SET balance = balance + ? WHERE user_id = ?'
);
$stmt->execute([$amount, $userId]);
}
}
🔹 Задачи
— Объяснить, как именно происходит race condition в этом коде
— Почему транзакция здесь не защищает от проблемы
— Исправить getBalance так, чтобы устранить race condition
Ставьте → 🔥 если нравится формат. Если нет → 🌚
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤3👍1
🤔 Разрабатываете ИИ-агентов, но всё ещё не уверены в их стабильности и прогнозируемости?
Мы поговорили с десятками разработчиков ИИ-агентов и сделали отдельный курс по AgentOps.
🧠 На нём вы узнаете:
– как оптимизировать траты на токены;
– как на практике оценить качество работы агента;
– как «докручивать» RAG-системы без потери качества;
– как обеспечить устойчивость агента к сбоям внешних сервисов без падения всей системы и про многое-многое другое.
📅 Старт: 19 мая.
👥 Спикеры — практики с опытом в AI и Data Science в крупных IT-компаниях, таких как Яндекс, Huawei, МТС и др.
Длительность: 6-12 недель в зависимости от тарифа.
🔗 Программа курса и другие подробности
Мы поговорили с десятками разработчиков ИИ-агентов и сделали отдельный курс по AgentOps.
🧠 На нём вы узнаете:
– как оптимизировать траты на токены;
– как на практике оценить качество работы агента;
– как «докручивать» RAG-системы без потери качества;
– как обеспечить устойчивость агента к сбоям внешних сервисов без падения всей системы и про многое-многое другое.
📅 Старт: 19 мая.
👥 Спикеры — практики с опытом в AI и Data Science в крупных IT-компаниях, таких как Яндекс, Huawei, МТС и др.
Длительность: 6-12 недель в зависимости от тарифа.
🔗 Программа курса и другие подробности
🤯 Представьте, что ваш AI-агент работает так же предсказуемо, как обычный микросервис. Звучит утопически, но это именно то, к чему должна прийти разработка в 2026 году.
Основная боль текущих реализаций — полная непредсказуемость поведения. Сегодня агент выполнил задачу за два шага, а завтра ушёл в рекурсию и потратил все лимиты.
Наш обновлённый курс «Разработка AI-агентов» научит, как приручить этот хаос с помощью
✅ Что вы получите:
— понимание того, как управлять логикой агента на уровне кода;
— навыки работы с
— готовые паттерны для обработки ошибок и галлюцинаций;
— опыт создания систем, которые реально экономят время.
Есть пара мест со скидкой до завтра, решайтесь 👈🏻
Основная боль текущих реализаций — полная непредсказуемость поведения. Сегодня агент выполнил задачу за два шага, а завтра ушёл в рекурсию и потратил все лимиты.
Наш обновлённый курс «Разработка AI-агентов» научит, как приручить этот хаос с помощью
Python и современных фреймворков. Мы не будем учить «общаться» с нейросетью, мы будем строить из неё надёжный инструмент.✅ Что вы получите:
— понимание того, как управлять логикой агента на уровне кода;
— навыки работы с
LangChain и библиотеками оркестрации;— готовые паттерны для обработки ошибок и галлюцинаций;
— опыт создания систем, которые реально экономят время.
Есть пара мест со скидкой до завтра, решайтесь 👈🏻
🥱5❤1
🐳 Фишка Docker CLI
Запустите docker stats с правильными флагами, и вы увидите всё в реальном времени.
🔹 Зачем это нужно
— Показывает live-метрики по CPU, памяти, сети и I/O для всех запущенных контейнеров.
— Критично для Java-сервисов: помогает поймать утечки памяти, GC pressure и неожиданный CPU spike прямо в момент нагрузки.
— Работает без установки каких-либо агентов.
— В отличие от top внутри контейнера видит реальные лимиты cgroup, а не ресурсы хоста.
🔹 Как использовать
— Все контейнеры в реальном времени:
— Конкретный сервис:
— Один снимок без стриминга:
— Только нужные поля:
— В JSON для скриптов:
Флаг --format принимает Go-шаблоны — те же, что и в docker inspect. Можно вывести ровно то, что нужно, и скормить в jq или свой мониторинг-скрипт.
⚠️ Если контейнер запущен без -m / --memory, JVM видит всю память хоста и выставляет heap соответственно. docker stats мгновенно покажет, есть ли лимит — колонка MEM USAGE / LIMIT.
Запустите docker stats с правильными флагами, и вы увидите всё в реальном времени.
🔹 Зачем это нужно
— Показывает live-метрики по CPU, памяти, сети и I/O для всех запущенных контейнеров.
— Критично для Java-сервисов: помогает поймать утечки памяти, GC pressure и неожиданный CPU spike прямо в момент нагрузки.
— Работает без установки каких-либо агентов.
— В отличие от top внутри контейнера видит реальные лимиты cgroup, а не ресурсы хоста.
🔹 Как использовать
— Все контейнеры в реальном времени:
docker stats— Конкретный сервис:
docker stats my-spring-app— Один снимок без стриминга:
docker stats --no-stream— Только нужные поля:
docker stats --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"— В JSON для скриптов:
docker stats --no-stream --format jsonФлаг --format принимает Go-шаблоны — те же, что и в docker inspect. Можно вывести ровно то, что нужно, и скормить в jq или свой мониторинг-скрипт.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤1🔥1
Пока одни продолжают спорить «нужен ли AI в разработке», другие уже автоматизируют рутину через цепочки специализированных агентов — и делают это прямо в PHP-проектах.
Свежая статья на Хабре от PHP-разработчика, который прошёл путь от «AI неудобен» до полноценного пайплайна с оркестрацией агентов в продакшене.
Что внутри:
— Почему один большой промпт не работает, и как принцип "один агент — одна ответственность" решает проблему размытия контекста
— Как выстроить цепочку: php-developer → phpstan-developer → php-test-developer → авто-фиксер — каждый в своём чистом контексте
— Specification-Driven Development на практике: спека первична, код вторичен
— Реальный опыт с Claude Code, RooCode, KiloCode — с честными плюсами и минусами
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍1🔥1😁1🥱1
🔥 Настройка Pest PHP
Если ты всё ещё пишешь тесты на чистом PHPUnit с setUp() и простынями $this->assert... — этот пост для тебя. Настраиваем Pest с нуля в реальном Laravel-проекте.
📦 Шаг 1 — Установка
После этого в корне появится tests/Pest.php — файл для глобальных хелперов и настроек. Не игнорируй его.
✍️ Шаг 2 — Первый тест
Забудь про классы. Pest — это функции:
🧩 Шаг 3 — Datasets (параметризованные тесты)
Вместо копипасты одного теста на каждый кейс:
⚙️ Шаг 4 — Глобальные настройки в Pest.php
Открываем tests/Pest.php и настраиваем раз и навсегда:
Теперь RefreshDatabase не нужно писать в каждом файле, а кастомные матчеры делают тесты выразительнее.
🚀 Шаг 5 — Запуск
💡 Почему Pest, а не чистый PHPUnit?
Меньше бойлерплейта, человекочитаемый вывод, встроенные архитектурные тесты (arch()) и отличная интеграция с Laravel. Команда Laravel официально рекомендует его с 11-й версии фреймворка.
Ставьте 🔥 если используете Pest.
Если ты всё ещё пишешь тесты на чистом PHPUnit с setUp() и простынями $this->assert... — этот пост для тебя. Настраиваем Pest с нуля в реальном Laravel-проекте.
📦 Шаг 1 — Установка
composer require pestphp/pest --dev
composer require pestphp/pest-plugin-laravel --dev
php artisan pest:install
После этого в корне появится tests/Pest.php — файл для глобальных хелперов и настроек. Не игнорируй его.
✍️ Шаг 2 — Первый тест
Забудь про классы. Pest — это функции:
it('создаёт пользователя через API', function () {
$response = $this->postJson('/api/users', [
'name' => 'Ivan',
'email' => 'ivan@example.com',
]);
$response->assertCreated();
expect($response->json('data.name'))->toBe('Ivan');
});🧩 Шаг 3 — Datasets (параметризованные тесты)
Вместо копипасты одного теста на каждый кейс:
it('валидирует email', function (string $email) {
$response = $this->postJson('/api/users', [
'email' => $email
]);
$response->assertUnprocessable();
})->with([
'пустой' => [''],
'без собаки' => ['notanemail'],
'только домен' => ['@example.com'],
]);⚙️ Шаг 4 — Глобальные настройки в Pest.php
Открываем tests/Pest.php и настраиваем раз и навсегда:
uses(
Tests\TestCase::class,
Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');
expect()->extend('toBeSuccess', function () {
return $this->toMatchArray([
'status' => 'success'
]);
});
Теперь RefreshDatabase не нужно писать в каждом файле, а кастомные матчеры делают тесты выразительнее.
🚀 Шаг 5 — Запуск
./vendor/bin/pest
./vendor/bin/pest --filter="создаёт пользователя"
./vendor/bin/pest --coverage # требует Xdebug или PCOV
Меньше бойлерплейта, человекочитаемый вывод, встроенные архитектурные тесты (arch()) и отличная интеграция с Laravel. Команда Laravel официально рекомендует его с 11-й версии фреймворка.
Ставьте 🔥 если используете Pest.
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱11🔥6❤1👍1😁1
🐳 Фишка Docker CLI
Контейнер запустился, но ведёт себя странно — не то сетевое окружение, не те переменные, непонятно что вообще внутри? Просто запустите docker inspect, и у вас будет полная картина без лишних инструментов.
🔹 Зачем это нужно
— Возвращает полный JSON с внутренностями: переменные окружения, тома, сетевые настройки, лимиты ресурсов, entrypoint, restart policy.
— Работает не только с контейнерами — понимает образы, тома и сети.
— Незаменимо, когда нужно быстро понять, почему контейнер ведёт себя не так, как ожидается.
🔹 Как использовать
— Полный дамп:
— Только IP-адрес:
— Все переменные окружения:
— Смонтированные тома:
Флаг -f принимает Go-шаблоны — вытащите ровно то поле, которое нужно, без парсинга тысячи строк JSON вручную.
Контейнер запустился, но ведёт себя странно — не то сетевое окружение, не те переменные, непонятно что вообще внутри? Просто запустите docker inspect, и у вас будет полная картина без лишних инструментов.
🔹 Зачем это нужно
— Возвращает полный JSON с внутренностями: переменные окружения, тома, сетевые настройки, лимиты ресурсов, entrypoint, restart policy.
— Работает не только с контейнерами — понимает образы, тома и сети.
— Незаменимо, когда нужно быстро понять, почему контейнер ведёт себя не так, как ожидается.
🔹 Как использовать
— Полный дамп:
docker inspect my-container— Только IP-адрес:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container— Все переменные окружения:
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' my-container— Смонтированные тома:
docker inspect -f '{{json .Mounts}}' my-container | jqФлаг -f принимает Go-шаблоны — вытащите ровно то поле, которое нужно, без парсинга тысячи строк JSON вручную.
❤4🔥3👍2
Исследование облачных платформ
Apple Hills Digital собирает мнения специалистов об инфраструктуре: облака, гибридные решения, on-premise.
Можно пройти короткий отбор (~3 минуты) и попасть в основную часть исследования.
Дополнительно — участие в розыгрыше iPhone*
* Принимая участие в опросе Вы становитесь участником розыгрыша под наименованием «Если ты работаешь в IT, пройди опрос и выиграй iPhone 17 Pro». Информация об организаторе розыгрыша, сроках проведения, правилах проведения розыгрыша, количестве призов, сроках, месте, порядке получения призов размещена по ссылке.
Apple Hills Digital собирает мнения специалистов об инфраструктуре: облака, гибридные решения, on-premise.
Можно пройти короткий отбор (~3 минуты) и попасть в основную часть исследования.
Дополнительно — участие в розыгрыше iPhone*
* Принимая участие в опросе Вы становитесь участником розыгрыша под наименованием «Если ты работаешь в IT, пройди опрос и выиграй iPhone 17 Pro». Информация об организаторе розыгрыша, сроках проведения, правилах проведения розыгрыша, количестве призов, сроках, месте, порядке получения призов размещена по ссылке.
❤1