Библиотека собеса по PHP | вопросы с собеседований
3.15K subscribers
193 photos
6 videos
134 links
Вопросы с собеседований по PHP и ответы на них.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/9f3affba

Для обратной связи: @proglibrary_feeedback_bot
Download Telegram
Чем отличается Application Service от Domain Service в DDD?

Domain Service — содержит бизнес-логику, которая не принадлежит конкретной сущности или Value Object. Работает только с объектами домена, ничего не знает об инфраструктуре.

  class MoneyTransferService {
public function transfer(Account $from, Account $to, Money $amount): void {
if (!$from->hasSufficientFunds($amount)) {
throw new InsufficientFundsException();
}
$from->debit($amount);
$to->credit($amount);
}
}


Application Service — оркестрирует выполнение use case. Загружает агрегаты из репозиториев, вызывает доменные сервисы, сохраняет результат, диспатчит события. Не содержит бизнес-логики.

  class TransferMoneyHandler {
public function handle(TransferMoneyCommand $cmd): void {
$from = $this->accountRepo->findOrFail($cmd->fromId);
$to = $this->accountRepo->findOrFail($cmd->toId);

$this->transferService->transfer($from, $to, new Money($cmd->amount));

$this->accountRepo->save($from);
$this->accountRepo->save($to);
}
}


Правило разделения: если в методе есть бизнес-решение ("можно ли это сделать?") — это доменный сервис. Если только координация ("загрузи, вызови, сохрани") — Application Service.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31
Что такое Idempotency в контексте HTTP и очередей? Как обеспечить?

Идемпотентность — повторный вызов с теми же параметрами даёт тот же результат без дополнительных побочных эффектов.

HTTP методы: GET, HEAD, PUT, DELETE — идемпотентны по спецификации. POST — нет.

Для POST-запросов применяется Idempotency Key: клиент генерирует UUID и передаёт в заголовке. Сервер кэширует результат под этим ключом. При повторном запросе возвращает закэшированный ответ, не выполняя операцию повторно.

В очередях: воркер может упасть после обработки задачи, но до подтверждения (ack). Брокер переотправит задачу. Обработчик должен быть идемпотентным — повторная обработка одной и той же задачи не должна создавать дублей.

Техники обеспечения идемпотентности:

— Хранить processed_ids и проверять перед обработкой
— Использовать INSERT IGNORE / ON DUPLICATE KEY в MySQL
— Использовать upsert (INSERT ... ON CONFLICT DO NOTHING в PostgreSQL)
— Проверять состояние перед изменением ("уже оплачен — пропустить")

Идемпотентность — обязательное требование для любого обработчика в распределённой системе с at-least-once delivery.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
Что такое OPcache и как он ускоряет PHP?

PHP — интерпретируемый язык. На каждый запрос без OPcache:

1. Читается PHP-файл с диска
2. Парсится в AST
3. Компилируется в opcode
4. Opcode выполняется Zend Engine

OPcache кэширует скомпилированный opcode в shared memory. При следующем запросе шаги 1-3 пропускаются.

Результат: ускорение в 2-10x, снижение нагрузки на CPU.

JIT (Just-In-Time) — следующий уровень: компилирует opcode в машинный код. Даёт прирост для CPU-интенсивных задач.

Сброс кэша при деплое: opcache_reset() или перезапуск PHP-FPM.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3
✔️ PHP-тест: Generators + Memory leak + Batch-обработка

«Мы перешли на генераторы, чтобы не грузить память» — но память всё равно растёт 👇

📦 Задание

Команда переписала импорт 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
🔥21🌚31👍1
💬 Обратная связь

Текущий уровень сложности вопросов?

🔥 — Слишком просто, хочу сложнее
👍🏼 — В самый раз
❤️ — Иногда сложновато
😁 — Часто не понимаю
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍54😁4
Что происходит при использовании команды cache:clear и как это влияет на кэшированные данные в случае использования Redis с тэгами?

Команда php artisan cache:clear очищает весь кэш, включая данные в Redis, но если используется кэширование с тегами, то команда очистит только данные, связанные с общими ключами. Теги управляются отдельно, поэтому их нужно очищать вручную, используя cache:tags() для работы с конкретными группами данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥21
Что такое событийно-ориентированная архитектура?

Event-driven architecture — это когда компоненты общаются через события, не зная друг о друге напрямую.

// Событие:
class UserRegistered {
public function __construct(public readonly User $user) {}
}

// Listener:
class SendWelcomeEmail {
public function handle(UserRegistered $event): void {
$this->mailer->send($event->user->email, 'Welcome!');
}
}

// Диспетчер:
$dispatcher->dispatch(new UserRegistered($user));


Зачем

✔️ Слабая связанность (UserService не знает о SendWelcomeEmail)
✔️ Легко добавить новый listener без изменения существующего кода (Open/Closed)
✔️ Можно делать асинхронные listeners (через очередь)

В Laravel: Event / Listener, EventServiceProvider.
В Symfony: EventDispatcher, декларация через атрибуты.

Подводный камень: сложно трейсить цепочку — одно событие вызывает другое.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥21
Что такое N+1 проблема и как её решить?

N+1 — классическая проблема производительности ORM.

// N+1: 1 запрос за постами + N запросов за автором каждого поста:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // запрос на каждой итерации!
}

100 постов = 101 запрос к БД.


Решение — Eager Loading:

// Laravel:
$posts = Post::with('author')->get(); // 2 запроса: posts + authors IN (...)

// Doctrine:
$posts = $em->createQuery('SELECT p, a FROM Post p JOIN FETCH p.author a')->getResult();


Как обнаружить

• Laravel Debugbar / Telescope — показывает все запросы
• Логирование медленных запросов в MySQL
• Профилировщик (Blackfire, Xdebug)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍52🔥1
Что такое наследование?

Наследование — это механизм ООП, позволяющий создавать новый класс на основе уже существующего. Новый класс (подкласс) получает все свойства и методы родительского класса (суперкласса), что обеспечивает повторное использование кода и упрощает поддержку.

Наследование реализуется с помощью ключевого слова extends. Подкласс может расширять или переопределять поведение суперкласса, а также добавлять новые поля и методы. Важно помнить, что в PHP класс может наследоваться только от одного суперкласса.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥21
✔️ PHP-тест: Race condition

Код прошёл нагрузочное тестирование. На проде деньги задвоились 👇

📦 Задание


Фича: пользователь может переводить бонусные баллы другу. Логика простая — проверить баланс, списать, начислить. Тесты зелёные, нагрузочное прогнали — всё ок. Через три дня после релиза: у нескольких пользователей баланс ушёл в минус, у других — задвоился.

// 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
🔥17🤔3👍2
Какие существуют проблемы в многопоточной среде?

Основные проблемы многопоточности:

1️⃣ Race Conditionкогда результат работы зависит от порядка выполнения потоков. Например, два потока одновременно изменяют одну переменную без синхронизации.

2️⃣ Deadlockвзаимная блокировка, когда потоки ждут друг друга. Классика: поток А держит ресурс 1 и ждёт ресурс 2, а поток Б держит ресурс 2 и ждёт ресурс 1.

2️⃣ Livelockпотоки активны, но не могут продолжить работу, постоянно реагируя на действия друг друга. Например, как два человека в коридоре, которые одновременно пытаются уступить дорогу.

4️⃣ Starvationпоток никогда не получает доступ к ресурсу из-за того, что другие потоки постоянно его перехватывают.

5️⃣ Memory Visibility изменения, сделанные одним потоком, могут быть не видны другим из-за кэширования в CPU.
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍4🔥1
💬 Обратная связь

Стоит ли спойлерить часть ответа как тут, чтобы было время подумать самостоятельно?

🔥 — Да
😁 — Нет
🤔 — Без разницы
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥23😁4🤔3
Как работает Service Container в Laravel и чем bind() отличается от singleton()?

Service Container — это IoC-контейнер, который управляет зависимостями и их разрешением.

// bind() — каждый раз создаёт новый экземпляр
app()->bind(PaymentService::class, function ($app) {
return new PaymentService($app->make(HttpClient::class));
});

// singleton() — создаёт один раз, дальше отдаёт тот же объект
app()->singleton(CacheManager::class, function ($app) {
return new CacheManager(config('cache'));
});


🔹 Когда что использовать

→ bind() — если объект имеет состояние, которое должно быть свежим на каждый запрос. Например, корзина пользователя.

→ singleton() — если объект stateless или его инициализация дорогая. Например, коннект к внешнему API, логгер.
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1🔥1
Объясни разницу между Gate и Policy в Laravel?

Оба инструмента для авторизации, но с разной областью применения.

Gateпростые одиночные проверки, не привязанные к модели:

// Определяем в AuthServiceProvider
Gate::define('access-admin-panel', function (User $user) {
return $user->is_admin;
});

// Проверяем
if (Gate::allows('access-admin-panel')) { ... }
// или в контроллере
$this->authorize('access-admin-panel');


Policy класс с набором правил для конкретной модели:

// php artisan make:policy PostPolicy --model=Post
class PostPolicy
{
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}

public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->is_admin;
}
}

// Использование
$this->authorize('update', $post);


Правило выбора

— Нет модели → Gate
— Есть модель, несколько действий → Policy
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81🔥1
Что такое Job Chaining и Job Batching в Laravel?

Оба механизма для организации нескольких фоновых задач, но логика разная.

Job Chaining — задачи выполняются строго последовательно. Если одна упала, цепочка останавливается:

Bus::chain([
new ProcessPayment($order),
new SendInvoice($order),
new NotifyWarehouse($order),
])->dispatch();


Job Batching — задачи выполняются параллельно, можно отслеживать прогресс и реагировать на завершение всей группы:

$batch = Bus::batch([
new ImportRow($rows->chunk(100)[0]),
new ImportRow($rows->chunk(100)[1]),
new ImportRow($rows->chunk(100)[2]),
])
->then(fn (Batch $batch) => Log::info('Импорт завершён'))
->catch(fn (Batch $batch, Throwable $e) => Log::error('Ошибка'))
->finally(fn (Batch $batch) => Cache::forget('import-lock'))
->dispatch();

// Можно следить за прогрессом
$batch->progress(); // процент выполнения


Когда что

— Независимые задачи, нужна скорость и прогресс → Batch
— Зависимость между задачами, порядок важен → Chain
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍3🔥1
Как правильно протестировать?

У вас есть два класса:

class A {
public function __construct(private B $b) {}

public function doSomething(): string {
return $this->b->getValue();
}
}

class B {
public function getValue(): string {
return 'real';
}
}


Нужно написать юнит-тест для A::doSomething(), не трогая класс B. Как это сделать правильно?

Создаём мок через PHPUnit, он реализует интерфейс (или наследует класс) и позволяет изолировать зависимость:

$mockB = $this->createMock(B::class);
$mockB->method('getValue')->willReturn('mocked');

$a = new A($mockB);
$this->assertSame('mocked', $a->doSomething());


Почему работает: createMock() генерирует анонимный класс, расширяющий B. PHP позволяет передать его туда, где ожидается B.
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
PHP использует подсчёт ссылок для сборки мусора. В каком случае это может не сработать?

PHP уничтожает объект, когда его refcount падает до 0. Но есть исключение — циклические ссылки: объект A ссылается на B, B — на A. Каждый держит счётчик ≥ 1, хотя оба недостижимы из кода.

Для этого существует Cycle Collector (zval garbage collector). Он запускается не сразу, а когда буфер подозрительных zval заполняется (~10 000 узлов по умолчанию). До его запуска объекты в цикле живут в памяти, даже если логически мертвы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2🥱1
🏃‍♀️ Мы собрали бесплатный мега-гайд по ии-агентам 👇

В первой части постов навалили жесткой базы, чтобы вправить мозги на место. Во второй дали конкретные инструменты, фреймворки и пошаговые инструкции, что нужно кодить прямо сейчас.

Часть 1. Введение, юзкейсы и реальность
Разбираемся с терминами, снимаем розовые очки и смотрим, где ИИ реально приносит бабки, а где только жжет нервы:

1. «Так что вообще считается AI-агентом?»
2. «Где тут бот, а где уже AI-агент?»
3. «Не надо пихать AI-агента в каждую задачу»
4. «Что уже можно спокойно делать через AI-агентов?»
5. «А что через AI-агентов пока лучше не трогать?»

Часть 2. Изнанка, ошибки и архитектура
Как всё это устроено под капотом, чтобы не слить бюджет и не наломать дров на старте:

6. «Можно ли просто сесть вечером и собрать себе AI-агента?»
7. «С чего вообще начать, если хочется попробовать AI-агентов»
8. «Почему AI-агент может внезапно начать творить дичь»
9. «Где AI-агенты реально экономят время, а где только добавляют возни»
10. «Почему они жрут столько денег?»

Часть 3. Хардкорная практика (Что делать руками)
Хватит теории. Открываем ноут, запускаем Cursor и делаем нормальные, отказоустойчивые системы:

11. «Почему одного промпта мало?»
12. «Почему AI-агенту мало просто “дать доступ к данным”»
13. «Если не следить за AI-агентом, он быстро начинает жить своей жизнью»
14. «Собрать демку легко. Но как же сделать нормально»
15. «Как сделать, чтобы это не развалилось через неделю?»

👍 Сохраняйте пост в избранное, чтобы не потерять.

🤫 А завтра стартует наш курс по ии-агентам
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2
Чем SplDoublyLinkedList, SplMinHeap и SplFixedArray отличаются от обычного array?

PHP array — это хэш-таблица с упорядоченными ключами. Универсальна, но каждый элемент занимает ~400–500 байт в zval + hashtable bucket. SPL-структуры — специализированные контейнеры с фиксированной семантикой и меньшим оверхедом.

SplFixedArray — C-массив фиксированного размера. Занимает в 3–4 раза меньше памяти. Оправдан, когда размер известен заранее.

// array: ~400 MB
$arr = array_fill(0, 1_000_000, 0);

// SplFixedArray: ~90 MB
$fixed = new SplFixedArray(1_000_000);


SplDoublyLinkedList / SplStack / SplQueue — двусвязный список под капотом. Выигрывает при частых вставках и удалениях в середину. При случайном доступе по индексу — проигрывает array: O(n) против O(1).

SplMinHeap / SplMaxHeap — бинарная куча. Классический кейс — приоритетная очередь с извлечением минимума/максимума за O(log n).

$heap = new SplMinHeap();
$heap->insert(5);
$heap->insert(1);
$heap->insert(3);

// Всегда достаёт минимум за O(log n)
echo $heap->extract(); // 1
echo $heap->extract(); // 3

⚠️ На что обратить внимание на практике


→ SplFixedArray не поддерживает строковые ключи и array_* функции — только числовые индексы
→ В PHP 8.1+ SplFixedArray реализует IteratorAggregate — работает в foreach без обёрток
→ Для задач "top-K элементов" или Dijkstra — SplMinHeap бьёт usort по всем фронтам
→ В большинстве бизнес-задач обычный array быстрее за счёт CPU-кэша — SPL оправдан при сотнях тысяч элементов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2🤔1
Что такое Lazy Collections в Laravel?

Обычная Collection загружает всё в память сразу. При работе с большими объёмами данных — это проблема.

//  Загрузит ВСЕ записи в память
$users = User::all()->filter(...)->map(...);

// Lazy Collection — обрабатывает по одной записи через генератор
User::cursor()->filter(function (User $user) {
return $user->is_active;
})->each(function (User $user) {
ProcessUser::dispatch($user);
});


cursor() использует PDO fetchRow под капотом — в памяти одновременно одна запись.

Lazy Collection из файла:

// Обработка огромного CSV без OutOfMemoryError
$collection = LazyCollection::make(function () {
$handle = fopen('huge_file.csv', 'r');
while ($row = fgetcsv($handle)) {
yield $row;
}
});

$collection->skip(1)->chunk(100)->each(function ($rows) {
ImportBatch::dispatch($rows->toArray());
});


Когда использовать

10k+ записей в обработке → cursor() + LazyCollection
Файлы, стримы, внешние API с пагинацией → LazyCollection с генератором

Важно: методы типа count() и last() материализуют коллекцию. Их лучше избегать в lazy-контексте.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41🔥1