Ядро фреймворка
Фреймворк не «ускоряет разработку» абстрактно — он берёт на себя один конкретный цикл: превращение HTTP-запроса в HTTP-ответ. Между байтами на сокете и отданным ответом стоит конвейер, и он один и тот же в Laravel и в Symfony. Ядро собирает объект запроса и прогоняет его через слои middleware. Маршрутизатор по паре «глагол + путь» выбирает действие контроллера. Контейнер читает тайп-хинты конструктора и рекурсивно собирает и сам контроллер, и всё, что тому нужно. Действие координирует работу — валидирует ввод, спрашивает у моделей данные, проверяет права — и отдаёт результат шаблонизатору. Знать фреймворк — значит уметь назвать, что происходит на каждом из этих шагов, и понимать, куда именно вы можете в конвейер вклиниться.
Почти каждая ловушка темы растёт из того, что какой-то шаг конвейера считают магией. Контейнер ничего не «угадывает»: он резолвит тайп-хинт рефлексией, а на интерфейсе с двумя реализациями падает громко — и это правильное поведение, а не баг. Cache::get() не статический метод: за ним стоит __callStatic и контейнер, а платите вы тем, что настоящие зависимости класса исчезают из конструктора. Middleware — не набор хуков, а конвейер декораторов, где порядок слоёв несёт смысл. Скомпилированный Blade-шаблон — обычный PHP без всякой песочницы, поэтому {!! !!} вокруг пользовательского текста — это готовая XSS. JWT подписан, но не зашифрован, и отозвать его до истечения нечем. А залогиненный пользователь — ещё не пользователь, которому что-то разрешено. Разбор по слоям — ниже.
Карта темы
- MVC на запросе — как маршрутизатор, контроллер, модель и представление делят между собой один запрос и почему контроллер обязан остаться тонким.
- Маршрутизация и контроллеры — как пара «глагол + путь» выбирает действие, что генерирует ресурсный контроллер и почему атрибуты
#[Route]компилируются в таблицу маршрутов. - Сервис-контейнер и автовайринг — как контейнер собирает граф зависимостей рефлексией, почему он громко падает на неоднозначном интерфейсе и что на самом деле прячет фасад.
- Middleware и события — конвейер декораторов с
$next, семантика порядка слоёв, слушатели ядра Symfony и цена неявного потока управления. - Шаблонизаторы Blade и Twig — почему скомпилированный
Blade— это обычный PHP, чем{{ }}отличается от{!! !!}и какTwigнавязывает дисциплину экранирования. - Валидация входных данных — как
FormRequestотрабатывает до тела действия, почему провал бросает исключение и чем от этого отличается валидатор Symfony. - Аутентификация API — состояние на сервере против самодостаточного
JWT, проблема отзыва и выбор междуSanctumиPassport. - Авторизация — политики и голосующие — чем «кто вы» отличается от «можно ли вам», почему
authorize()бросает исключение и как стратегия голосования решает исход в Symfony.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Держать бизнес-правила в действии контроллера | Действие разрастается в god object; логику нельзя переиспользовать и невозможно тестировать без HTTP |
Забыть methods: в атрибуте #[Route] | Одно действие начинает отвечать на любой глагол, включая DELETE |
| Ждать, что контейнер сам выберет одну из двух реализаций интерфейса | Он не выбирает молча — он падает; чинится только явной привязкой (alias в Symfony, contextual binding в Laravel) |
Резолвить сервис внутри register() сервис-провайдера | Провайдер нужной зависимости мог ещё не отработать — ошибка зависит от порядка загрузки |
Считать Cache::get() статическим методом | Это __callStatic поверх контейнера; настоящие зависимости класса исчезают из конструктора, а тесты уезжают в статические моки |
Не вызвать $next($request) в middleware | Запрос молча проглочен: конвейер оборван, контроллер не выполнится вообще |
| Считать порядок middleware косметикой | auth перед throttle снимает лимит с неаутентифицированного перебора паролей |
Ждать, что упавший очередной (ShouldQueue) слушатель откатит действие | Заказ уже сохранён — провалился только побочный эффект, компенсировать придётся вручную |
Выводить пользовательский HTML через {!! !!} | Экранирования нет — это готовая XSS |
Считать, что скомпилированный Blade исполняется в песочнице | Шаблон компилируется в обычный PHP и может вызвать что угодно |
Читать $request->all() в действии вместо validated() | В домен приезжают непроверенные ключи — mass assignment и мусор в базе |
Ждать от rules() списка ошибок вместо исключения | Провал бросает ValidationException (422 для API, redirect back для формы), и тело действия не выполняется |
Думать, что payload JWT зашифрован | Он только подписан — payload читается обычным base64-декодом, а отозвать токен до exp нечем |
| Считать залогиненного пользователя разрешённым — и «прятать кнопку» вместо серверной проверки | Auth::check() вместо политики позволяет править чужие объекты: запрос уходит напрямую, минуя ваш UI |
Значение для собеседований
Ядро фреймворка спрашивают на любой позиции, но проверяют не знание API. Проверяют, можете ли вы провести запрос через весь конвейер вслух: кто собрал объект запроса, в каком порядке отработали слои, кто и по какому признаку выбрал действие, откуда у контроллера взялись зависимости, где произошла валидация и где — проверка прав. Кандидат, который говорит «контейнер читает тайп-хинты конструктора рефлексией и рекурсивно строит граф, а на интерфейсе с двумя реализациями бросает исключение», сразу отделяется от того, кто отвечает «Laravel сам всё подставляет».
Что обычно проверяют:
- Роли
MVCна конкретном запросе и почему бизнес-логике не место в действии. - Что именно выбирает действие — и что генерирует
Route::resource/apiResource. - Как автовайринг резолвит зависимость и что происходит при двух реализациях интерфейса.
- Разницу
register()иboot()в сервис-провайдере. - Что такое фасад под капотом и в чём его честная критика.
- Механику middleware —
$next, короткое замыкание, семантику порядка — и чем от него отличается слушатель ядра Symfony. - Чем
Bladeотличается отTwigв том, что вообще позволено шаблону. - Что делает
FormRequestдо тела действия и что происходит при провале валидации. - Отзыв доступа: сессия против
JWT; когдаSanctum, а когдаPassport. - Разницу аутентификации и авторизации; политики против голосующих и стратегию решения.
Типичный неверный ответ: «Контейнер сам находит нужную реализацию, а если их несколько — берёт первую». Отсюда обычно и разворачивается разговор: контейнер не выбирает за вас никогда, он бросает исключение, и в Symfony оно прилетает на прогреве кэша, то есть на деплое, а не на живом трафике. Второй классический провал — «authorize() вернёт false, я проверю его в if»: он не возвращает false, он бросает AuthorizationException, которая превращается в 403, и тело действия после отказа не выполняется вовсе.