⌨️ Топ-вакансий по PHP за неделю
PHP Developer (Laravel)
- 150 000 - 200 000₽ - офис/гибрид/удалёнка (Ульяновск)
Backend-разработчик (PHP) - до 220 000 ₽ - офис/гибрид (Москва)
Старший fullstack-разработчик PHP (Laravel) - от 300 000 до 350 000 ₽ - удалёнка
➡️ Еще больше топовых вакансий — в нашем канале PHP Jobs
PHP Developer (Laravel)
- 150 000 - 200 000₽ - офис/гибрид/удалёнка (Ульяновск)
Backend-разработчик (PHP) - до 220 000 ₽ - офис/гибрид (Москва)
Старший fullstack-разработчик PHP (Laravel) - от 300 000 до 350 000 ₽ - удалёнка
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍1
🧠 PHP-лайфхак
Хочешь ловить баги до запуска кода? Используй статический анализ.
Это инструмент, который проверяет твой PHP-код на ошибки, несоответствия типам и подозрительные конструкции — без выполнения программы.
🔍 Как это работает
🧩 Установи PHPStan:
Запусти анализ:
--level=max включает максимально строгую проверку.
Можно использовать уровни от 0 до 9 — начни с 5–6, чтобы не утонуть в предупреждениях.
⚡️ Что даёт PHPStan / Psalm
🐞 Находит ошибки типов ещё до запуска кода
🚫 Предупреждает о неиспользуемых переменных и мёртвом коде
🧩 Помогает навести порядок в архитектуре
🔒 Снижает риск багов при рефакторинге
💡 Интеграция с Composer и CI/CD
Добавь команду в composer.json:
Теперь можно запускать проверку просто:
А в CI — добавить шаг перед деплоем, чтобы ни один коммит с ошибками типов не ушёл в прод.
Библиотека пхпшника
Хочешь ловить баги до запуска кода? Используй статический анализ.
Это инструмент, который проверяет твой PHP-код на ошибки, несоответствия типам и подозрительные конструкции — без выполнения программы.
🔍 Как это работает
🧩 Установи PHPStan:
composer require --dev phpstan/phpstanЗапусти анализ:
./vendor/bin/phpstan analyse src --level=max--level=max включает максимально строгую проверку.
Можно использовать уровни от 0 до 9 — начни с 5–6, чтобы не утонуть в предупреждениях.
⚡️ Что даёт PHPStan / Psalm
🐞 Находит ошибки типов ещё до запуска кода
🚫 Предупреждает о неиспользуемых переменных и мёртвом коде
🧩 Помогает навести порядок в архитектуре
🔒 Снижает риск багов при рефакторинге
💡 Интеграция с Composer и CI/CD
Добавь команду в composer.json:
"scripts": {
"analyze": "phpstan analyse src --level=max"
}Теперь можно запускать проверку просто:
composer analyzeА в CI — добавить шаг перед деплоем, чтобы ни один коммит с ошибками типов не ушёл в прод.
Библиотека пхпшника
👍11❤1🔥1
♻️ Redis-кеш с AI
Настроить кеш через Redis несложно. Но зачем копировать код из старого проекта, бояться что-то потерять, когда можно сгенерировать эту часть.
Промпт, который закроет этот вопрос:
Библиотека пхпшника
Настроить кеш через Redis несложно. Но зачем копировать код из старого проекта, бояться что-то потерять, когда можно сгенерировать эту часть.
Промпт, который закроет этот вопрос:
Ты Senior PHP-разработчик. Сгенерируй модуль кеширования для Laravel с Redis, с учётом всех современных паттернов, устойчивости и тестирования.
Требования:
Использование Redis через phpredis или predis (с учётом конфигурации в .env)
Кеширование с разными TTL для разных типов данных (разделение по Cache Store)
2 сервиса с кешированием:
Стратегия cache-aside (чтение из кеша и обновление вручную)
Стратегия write-through (автоматическое обновление кеша при записи данных)
Обработка инвалидации кеша при изменении данных
Кастомная обработка ошибок:
Fallback на in-memory или file cache (без исключений при недоступности Redis)
Логирование ошибок кеширования с уведомлением
Применение circuit breaker и retry паттернов при сете ошибок Redis
Защита от слишком частых запросов через кеширование с TTL и expiration для разных кешей
Изоляция кеш-слоя с использованием сервисов и репозиториев (не Cache фасад в контроллерах)
Настройки в .env и конфигурации кеша
Простой и понятный конфиг с разделением кешей для разных типов данных
Юнит-тесты с использованием Testcontainers для запуска Redis в Docker
Тестирование fallback-механизмов
Мокирование с имитацией недоступности Redis
Тесты на использование кеша с разными TTL
Стек: PHP 8.1+, Laravel 9.x, Redis, PHPUnit, Docker, Testcontainers
Библиотека пхпшника
👍7🥱2❤1🔥1
😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует
Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.
Как адаптировать продукт и не исчезнуть из выдачи:
— интегрировать
— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.
Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.
Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.
Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.
Как адаптировать продукт и не исчезнуть из выдачи:
— интегрировать
MCP и A2A-взаимодействие, чтобы агенты могли вас читать;— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.
Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.
Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.
Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
❤1
🧠 array_map vs foreach
Многие считают
Бенчмарк на массиве из 100 000 элементов:
Когда всё равно использовать array_map?
Когда важна читаемость, а массив маленький (до ~1000 элементов) — разница незаметна. Не оптимизируйте там, где это не нужно.
💡 Правило: сначала профилируйте, потом оптимизируйте.
Библиотека пхпшника
#vardump
Многие считают
array_map более "функциональным" и современным. Но есть нюанс.Бенчмарк на массиве из 100 000 элементов:
// array_map — ~12ms
$result = array_map(fn($x) => $x * 2, $items);
// foreach — ~6ms
$result = [];
foreach ($items as $x) {
$result[] = $x * 2;
}
foreach в 1.5–2 раза быстрее на больших массивах, потому что array_map создаёт замыкание и имеет overhead на каждый вызов callback.Когда всё равно использовать array_map?
Когда важна читаемость, а массив маленький (до ~1000 элементов) — разница незаметна. Не оптимизируйте там, где это не нужно.
// Читаемо и достаточно быстро для небольших коллекций
$emails = array_map(fn($u) => $u->email, $users);
Blackfire и Xdebug в помощь.Библиотека пхпшника
#vardump
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20❤2🔥1
Forwarded from Библиотека собеса по PHP | вопросы с собеседований
Код выглядит аккуратно. Но данные теряются, и никто не знает почему 👇
📦 Задание
Есть сервис для обработки платежей. Код покрыт тестами, транзакции есть, ошибки логируются. На проде раз в несколько дней часть платежей пропадает — в БД нет записи, в логах нет ошибок, пользователь уверен что оплатил.
// src/Payment/PaymentService.php
class PaymentService
{
public function __construct(
private PDO $pdo,
private Logger $logger,
private Notifier $notifier,
) {}
public function process(PaymentDTO $dto): bool
{
try {
$this->pdo->beginTransaction();
$paymentId = $this->insertPayment($dto);
$this->updateBalance($dto->userId, $dto->amount);
$this->insertAuditLog($paymentId, $dto);
$this->pdo->commit();
$this->notifier->sendReceipt($dto->userId, $paymentId);
return true;
} catch (NotificationException $e) {
$this->logger->warning('Receipt failed', ['error' => $e->getMessage()]);
return true;
} catch (Throwable $e) {
$this->logger->error('Payment failed', ['error' => $e->getMessage()]);
$this->pdo->rollBack();
return false;
}
}
private function insertPayment(PaymentDTO $dto): int
{
$stmt = $this->pdo->prepare(
'INSERT INTO payments (user_id, amount, status) VALUES (?, ?, ?)'
);
$stmt->execute([$dto->userId, $dto->amount, 'pending']);
return (int) $this->pdo->lastInsertId();
}
private function updateBalance(int $userId, float $amount): void
{
$stmt = $this->pdo->prepare(
'UPDATE balances SET amount = amount - ? WHERE user_id = ?'
);
$stmt->execute([$amount, $userId]);
if ($stmt->rowCount() === 0) {
throw new \RuntimeException("Balance record not found for user $userId");
}
}
private function insertAuditLog(int $paymentId, PaymentDTO $dto): void
{
// Пишем в отдельную audit БД через отдельное соединение
$this->auditPdo->prepare(
'INSERT INTO audit_log (payment_id, user_id, amount) VALUES (?, ?, ?)'
);
// ... execute
}
}
🔹 Задачи
— Найти сценарий, при котором платёж коммитится в БД, но return true не доходит до контроллера — и данные считаются потерянными
— Объяснить проблему
— Предложить исправленную структуру
Ставьте → 🔥 если нравится формат. Если нет → 🌚
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍4🌚3❤2
strpos возвращает int|false. Это создаёт одну конкретную проблему: проверку на false нужно делать строго:
// Классическая ошибка
if (strpos($url, 'admin')) {
// Не выполнится, если 'admin' в позиции 0
}
// Правильно
if (strpos($url, 'admin') !== false) {
// Окей
}
Этот паттерн !== false живёт в кодбазах как ритуал, смысл которого половина команды уже не помнит.
PHP 8.0 дал str_contains, str_starts_with, str_ends_with. Они возвращают bool. Просто bool.
if (str_contains($url, 'admin')) { ... }
if (str_starts_with($route, '/api')) { ... }
if (str_ends_with($file, '.blade.php')) { ... }Никакого !== false. Никакой магии с позицией 0.
Что осталось за strpos:
— Нужна позиция вхождения, а не факт наличия
— Нужен offset для поиска со смещением
— substr_count, substr_replace всё ещё работают с позициями
$pos = strpos($text, '{{');
$end = strpos($text, '}}', $pos);
// Здесь strpos оправдан — тебе нужны числаПро производительность: str_contains не медленнее strpos на коротких строках. На длинных — профилируй конкретный кейс, не гадай.
Короткий вывод: str_contains и компания — это не синтаксический сахар. Это правильный тип возврата для правильного вопроса.
#элементарный_выбор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤2
Repository — это не про «обернуть Eloquent в класс». Это про то, что доменный код не должен знать, откуда берутся данные.
Плохой Repository, который можно часто встретить:
class UserRepository {
public function findActive(): Collection {
return User::where('status', 'active')->get();
}
public function findByEmailAndStatus(string $email, string $status): ?User {
return User::where('email', $email)
->where('status', $status)
->first();
}
// ...ещё 40 методов под каждый запрос
}Это не Repository — это коллекция запросов в обёртке. Домен по-прежнему диктует методы через свои нужды, а репозиторий распухает.
Хороший Repository работает с агрегатами и скрывает детали хранения:
interface UserRepository {
public function findById(UserId $id): ?User;
public function findByEmail(Email $email): ?User;
public function save(User $user): void;
public function remove(User $user): void;
}Всё остальное — спецификации, критерии, query objects. Или честный Query Builder там, где это не домен.
Где Query Builder напрямую оправдан:
— Read-модели (CQRS: команды через домен, запросы — прямо в БД)
— Отчёты, дашборды, агрегации — там нет смысла гидрировать объекты
— Простые CRUD-экраны без доменной логики
// Это нормально для read-модели
$stats = DB::table('orders')
->selectRaw('status, COUNT(*) as count, SUM(amount) as total')
->groupBy('status')
->get();
Пытаться пропустить это через Repository с Order-сущностями — оверинжиниринг.
Правило: Repository живёт на границе домена. Если у тебя нет домена, нет смысла в Repository. Если домен есть, Repository скрывает инфраструктуру, а не просто переносит запросы в другой файл.
#элементарный_выбор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6😁5🥱2👍1
⌨️ Топ-вакансий по PHP за неделю
PHP разработчик - от 150 000 до 200 000 ₽ - удалёнка
Senior Fullstack PHP разработчик - от 250 000 до 350 000 ₽ - удалёнка
Lead PHP Developer - до 400 000 ₽ - удалёнка
➡️ Еще больше топовых вакансий — в нашем канале PHP Jobs
PHP разработчик - от 150 000 до 200 000 ₽ - удалёнка
Senior Fullstack PHP разработчик - от 250 000 до 350 000 ₽ - удалёнка
Lead PHP Developer - до 400 000 ₽ - удалёнка
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1😁1
✌🏻 У нас две новости — хорошая и плохая!
Хорошая: Ваших знаний, скорее всего, хватит, чтобы собрать рабочую демку AI-агента в Colab. 🫡
Плохая: Вы вряд ли выведете его в прод, не обанкротившись на токенах и не слив базу. 🤯
Для защиты от таких сценариев мы полностью пересобрали курс «Разработка AI-агентов». Теперь внутри плотная работа с экономикой ресурсов, дебаг через time-travel в
Программа курса, полный состав спикеров и другие подробности 👈🏻
ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.
Хорошая: Ваших знаний, скорее всего, хватит, чтобы собрать рабочую демку AI-агента в Colab. 🫡
Плохая: Вы вряд ли выведете его в прод, не обанкротившись на токенах и не слив базу. 🤯
Для защиты от таких сценариев мы полностью пересобрали курс «Разработка AI-агентов». Теперь внутри плотная работа с экономикой ресурсов, дебаг через time-travel в
LangGraph, извлечение данных из кривых сканов для RAG и комплаенс по 152-ФЗ.Если всё ещё сомневаетесь, послушайте голосовое от спикера курса Влада Прошинского, где он объясняет, как правильно тестировать агентов перед релизом.
Программа курса, полный состав спикеров и другие подробности 👈🏻
ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.
😢2🥱1
Оба инструмента про «выполнить что-то не прямо сейчас». Но они решают разные задачи, и смешивать их не стоит.
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