fa
Feedback
Библиотека собеса по PHP | вопросы с собеседований

Библиотека собеса по PHP | вопросы с собеседований

رفتن به کانال در Telegram

Вопросы с собеседований по PHP и ответы на них. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/9f3affba Для обратной связи: @proglibrary_feeedback_bot

نمایش بیشتر
3 150
مشترکین
اطلاعاتی وجود ندارد24 ساعت
-17 روز
-530 روز
آرشیو پست ها
Как отревьюить большой PR? Большой PR — это архитектурная проблема, но раз он уже есть, вот стратегия: 🔹 Начинать не с первого файла в списке, а с точки входа — контроллера, команды или сервиса, где меняется логика. 🔹 До открытия первого файла — ответить на три вопроса: что должно измениться в поведении системы, что не должно, и где граница между старым и новым. 🔹 Тесты читать первыми, т.к. они документируют намерение автора лучше, чем любой комментарий. 🔹 Искать не ошибки, а допущения. Где код предполагает, что данные всегда валидны? Что очередь не упадёт? Что транзакция атомарна? Именно здесь прячутся баги, которые выстреливают через месяц. 🔹 Замечания по стилю без линтера — не тема для PR-комментариев. Если в проекте нет phpcs или php-cs-fixer, лучше их настроить, чем воевать в ревью.

Ты деплоишь новую версию. Половина серверов на старом коде, половина на новом. Как не сломать пользователей? Три зоны ответственности — три правила: 🔹 БД — только аддитивные миграции. Не удалять, не переименовывать колонки в том же деплое. Сначала добавь — потом убери старое отдельным деплоем. 🔹 API — не меняй формат ответа, только расширяй. Сломал контракт — версионируй (/v2/). 🔹 Очереди — не меняй свойства Job-классов, только добавляй. Старый воркер может достать джоб от нового кода. Главное: feature flags вместо big bang деплоя. Код едет отдельно от включения фичи.

Что такое WeakReference и WeakMap? WeakReference — ссылка на объект, не увеличивающая счётчик ссылок. Объект может быть уничтожен GC, тогда ->get() вернёт null. WeakMap (PHP 8.0) — map с объектами в роли ключей, тоже не удерживает объекты от GC. При уничтожении объекта-ключа запись автоматически исчезает.
$map = new WeakMap();
$obj = new stdClass();
$map[$obj] = 'data';
unset($obj);
// запись в $map исчезла сама
Можно применять: кэши метаданных об объектах (атрибуты, вычисленные значения), без риска утечки памяти. Активно используется в Symfony, Doctrine.

🔄Вы используете шаблоны, запросы и конфигурации каждый день. Но понимаете ли вы, как они устроены внутри? 📅На открытом урок
🔄Вы используете шаблоны, запросы и конфигурации каждый день. Но понимаете ли вы, как они устроены внутри? 📅На открытом уроке за 60 минут разберём, как работает любой язык — и соберём свой DSL на PHP. Покажем полный конвейер: от исходного текста до результата. Вы увидите, как писать лексер, строить синтаксическое дерево и реализовывать интерпретатор. Всё — на чистом PHP, без магии и скрытых механизмов. Это даёт не просто новый навык, а понимание, как работают инструменты, которыми вы уже пользуетесь: шаблонизаторы, запросы, правила. И как создавать собственные решения под задачи бизнеса — без хардкода и сложных обходных путей. 💡Открытый урок проходит в преддверии старта курса «PHP-разработчик. Продвинутый уровень» 29 апреля в 20:00 МСК. Регистрация: https://clc.to/IA9gbg Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

В чём разница между static и self в PHP? self — ссылается на класс, в котором метод определён (compile-time). static — ссылается на класс, из которого метод вызван (runtime, late static binding).
class A {
    public static function create(): static {
        return new static(); // вернёт экземпляр B, если вызвано из B
    }
}

class B extends A {}

B::create(); // → объект B, не A

Что такое 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-контексте.

Чем 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 оправдан при сотнях тысяч элементов

🏃‍♀️ Мы собрали бесплатный мега-гайд по ии-агентам 👇 В первой части постов навалили жесткой базы, чтобы вправить мозги на м
🏃‍♀️ Мы собрали бесплатный мега-гайд по ии-агентам 👇 В первой части постов навалили жесткой базы, чтобы вправить мозги на место. Во второй дали конкретные инструменты, фреймворки и пошаговые инструкции, что нужно кодить прямо сейчас. Часть 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. «Как сделать, чтобы это не развалилось через неделю?» 👍 Сохраняйте пост в избранное, чтобы не потерять. 🤫 А завтра стартует наш курс по ии-агентам

PHP использует подсчёт ссылок для сборки мусора. В каком случае это может не сработать? PHP уничтожает объект, когда его refcount падает до 0. Но есть исключение — циклические ссылки: объект A ссылается на B, B — на A. Каждый держит счётчик ≥ 1, хотя оба недостижимы из кода. Для этого существует Cycle Collector (zval garbage collector). Он запускается не сразу, а когда буфер подозрительных zval заполняется (~10 000 узлов по умолчанию). До его запуска объекты в цикле живут в памяти, даже если логически мертвы.

Как правильно протестировать? У вас есть два класса:
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.

Symfony Workflow: конечный автомат для реализации бизнес-логики. Бесплатный урок курса «Symfony Framework» Во многих приложен
Symfony Workflow: конечный автомат для реализации бизнес-логики. Бесплатный урок курса «Symfony Framework» Во многих приложениях бизнес-логика держится на статусах: заказ создан, оплачен, отправлен, доставлен. Пока таких состояний мало, всё кажется простым. Но как только процесс растёт, цепочки if/else начинают расползаться по проекту, логика дублируется, а добавление нового статуса превращается в риск для всей системы. 📅 На открытом уроке 22 апреля: — Разберём, как использовать Symfony Workflow для формализации бизнес-процессов через конечный автомат. — Покажем, чем конечный автомат отличается от рабочего процесса, когда применять каждый подход, как описывать состояния и переходы в YAML и как Symfony умеет автоматически визуализировать процесс. — На практическом примере рассмотрим сущность заказа со статусами new → paid → shipped → delivered, методы can() и apply(), а также построение схемы состояний через workflow:dump.
Урок не для тех, кто считает, что строковое поле status и набор if/else — это нормальная архитектура «на вырост», и не для тех, кто не работает со сложной бизнес-логикой в приложении.
👉 Записаться: https://clc.to/I2zW6A Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

Что такое 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

Объясни разницу между 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

Как работает 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, логгер.

💬 Обратная связь Стоит ли спойлерить часть ответа как тут, чтобы было время подумать самостоятельно? 🔥 — Да 😁 — Нет 🤔 — Без разницы

Какие существуют проблемы в многопоточной среде? Основные проблемы многопоточности: 1️⃣ Race Conditionкогда результат работы зависит от порядка выполнения потоков. Например, два потока одновременно изменяют одну переменную без синхронизации. 2️⃣ Deadlockвзаимная блокировка, когда потоки ждут друг друга. Классика: поток А держит ресурс 1 и ждёт ресурс 2, а поток Б держит ресурс 2 и ждёт ресурс 1. 2️⃣ Livelockпотоки активны, но не могут продолжить работу, постоянно реагируя на действия друг друга. Например, как два человека в коридоре, которые одновременно пытаются уступить дорогу. 4️⃣ Starvationпоток никогда не получает доступ к ресурсу из-за того, что другие потоки постоянно его перехватывают. 5️⃣ Memory Visibility изменения, сделанные одним потоком, могут быть не видны другим из-за кэширования в CPU.

✔️ 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 Ставьте → 🔥 если нравится формат. Если нет → 🌚 💬 Решения пишите в комменты под спойлер — сравним подходы.

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

Локализация текстов в Symfony: от статических переводов к динамическим данным из базы. Бесплатный урок курса «Symfony Framewo
Локализация текстов в Symfony: от статических переводов к динамическим данным из базы. Бесплатный урок курса «Symfony Framework» Перевести интерфейс через файлы — это только начало. Настоящие сложности начинаются тогда, когда переводить нужно не статичные строки, а содержимое из базы данных, которое живёт в административной панели, меняется редакторами и должно оставаться управляемым с точки зрения архитектуры. 📅 На открытом уроке 15 апреля в 20:00: — Разберём реальный сценарий локализации в Symfony — от стандартного подхода со статическими переводами до более сложной работы с динамическими текстами из базы данных. — Покажем возможности компонента symfony/translation, разберём подходы к хранению переводов, варианты моделей данных и практическую реализацию получения локализованного содержимого через Doctrine.
Урок не для тех, кто хочет решить многоязычность «одной таблицей на всё», не думает о поддержке архитектуры и считает, что локализация заканчивается на переводе кнопок и заголовков.
👉 Записаться: https://clc.to/G4mNWQ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

Что такое 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)