Структура каталогов, artisan, .env и конфиги в Laravel

Структура каталогов — это соглашение о том, какой код где лежит в Laravel-проекте; artisan — консольный инструмент фреймворка, через который генерируют код, запускают миграции и управляют приложением; а .env и конфиги — два слоя настроек, между которыми новички чаще всего путаются. Тема выглядит скучной, но именно на ней ломаются проекты: конфиги зашивают в код, файлы пишут не туда, а окружение правят в проде руками.


Зачем это нужно

  • Ориентация в чужом коде. Первый день в проекте — и вы знаете, где живут контроллеры, где модели, где тесты. Без понимания структуры каждый файл ищется по всему репозиторию.
  • Правильные настройки. Ошибка «работает у меня, падает на проде» почти всегда живёт в разнице .env и конфигов. Понимание двух слоёв снимает её навсегда.
  • Экономия времени. Artisan генерирует скелеты классов, сбрасывает кеш, смотрит список роутов — то, что руками заняло бы час, делается одной командой.

Как это работает

Структура каталогов

Ключевые директории и что в них живёт:

КаталогЧто тамЗаметка
app/Http/ControllersКонтроллерыОсновная «прикладная» логика запросов
app/ModelsEloquent-моделиПо умолчанию User; свои кладём рядом
app/Http/MiddlewareMiddlewareСвои фильтры запросов
app/ProvidersСервис-провайдерыРегистрация привязок, событий
app/Console, app/Jobs, app/Events, app/Listeners, app/PoliciesКоманды, очереди, события, политикиПоявляются по мере использования artisan make:...
app/Services (сами создаём)Бизнес-логика вне контроллеровНе запрет фреймворка, а хорошее место по конвенции
bootstrapСборка приложенияapp.php — точка сборки контейнера; трогать редко
configВсе конфигиЕдинственный источник настроек (см. ниже)
databaseМиграции, фабрики, сидыСхема и тестовые данные
publicЕдинственная публичная папкаindex.php, стили, скрипты; сюда смотрит веб-сервер
resources/viewsBlade-шаблоныПредставления
routesРоутыweb.php, api.php, console.php
storageЛоги, скомпилированные шаблоны, сессии, файлы пользователейЗапись идёт сюда, а не куда попало
testsPHPUnit-тестыFeature/ и Unit/
vendorПакеты ComposerНе правим руками, в git не кладём

Два практических следствия. Первое: веб-сервер в проде направляем только на public — иначе наружу торчат .env и storage. Второе: app/ — не священная коробка «всё подряд». Классы вроде репозиториев и сервисов фреймворк разрешает класть куда угодно внутри app/, автозагрузка (PSR-4, префикс App\) подхватит.

Artisan

Artisan — это artisan в корне проекта, консольная утилита Laravel. Команды делятся на несколько групп.

Генерация кода (создают файлы со скелетом):

bash
php artisan make:model Post -mf          # модель + миграция + фабрика
php artisan make:controller PostController --resource
php artisan make:middleware CheckAge
php artisan make:request StorePostRequest
php artisan make:job SendEmail
php artisan make:seeder PostSeeder

Работа с базой:

bash
php artisan migrate              # накатить миграции
php artisan migrate:fresh --seed # пересоздать базу и залить сиды
php artisan db:seed              # только сиды

Инспекция и обслуживание:

bash
php artisan route:list           # все роуты с middleware
php artisan tinker               # REPL: живьём дергать Eloquent
php artisan config:clear         # сброс кеша конфигов
php artisan optimize:clear       # сброс всех кешей разом

tinker — недооценённая вещь: внутри него можно гонять любой PHP-код с загруженным приложением. Проверить связь, создать модель, посмотреть запрос — не пишуя тест.

.env и конфиги: два слоя

Схема простая, но её важно держать в голове:

  • .env — значения, которые меняются от окружения к окружению: пароль базы, ключ приложения, адрес почтового сервера. Файл не кладут в git.
  • config/*.php — то, как приложение пользуется этими значениями: структура настроек, значения по умолчанию, вся логика. Кладут в git.

.env читается только через функцию env(), и только внутри config/:

php
// config/database.php
'mysql' => [
    'host' => env('DB_HOST', '127.0.0.1'),   // вторая строка — значение по умолчанию
    'database' => env('DB_DATABASE', 'forge'),
    // ...
],

В коде приложения — только через config():

php
// Плохо
$db = env('DB_DATABASE');       // работает случайно, ломается после config:cache

// Хорошо
$db = config('database.connections.mysql.database');

Подключение к базе из кода тоже идёт через конфиг: DB_HOST и другие переменные попадают в config/database.php, а модель и соединения читают уже оттуда. То же с кешем (config/cache.php), почтой (config/mail.php), очередями (config/queue.php).

Взаимосвязь с lifecycle из прошлого конспекта: конфиги и .env загружаются на этапе bootstrap, до контейнера и middleware. Именно поэтому на проде конфиги кешируют одной командой — и именно поэтому после этого env() в обычном коде возвращает null.

Плохо / Хорошо

ПлохоХорошо
env('API_KEY') в контроллере или сервисеЗадать ключ в config/services.php и читать через config('services.api.key')
Добавить новый параметр в .env и использовать напрямуюДобавить строку в нужный конфиг со значением env(...) и дефолтом
Коммитить .env в gitКоммитить .env.example с названиями переменных без секретов
Править настройки на проде редактором в файлахМенять переменные окружения (панель хостинга, docker-compose) и кешировать конфиг
Заливать миграции правкой дампа рукамиПисать миграции и держать схему в коде
Кешировать конфиги в разработкеconfig:cache — только для прода; локально после смены .env просто перезапускаем

Подводный камень

  • env() возвращает null после config:cache. На проде конфиги кешируют в файл — и .env перестаёт читаться в рантайме. Любой env() вне config/ молча вернёт null. Это самая частая ловушка темы: локально код работает (кеша нет), на проде — падает.
  • Смена .env не даёт эффекта «просто так». Значения кешируются вместе с конфигами, а долгоживущие процессы (octane, очереди, демоны) держат старые значения в памяти. Поменяли переменную — сбросьте кеш и перезапустите воркеры очереди.
  • storage и bootstrap/cache должны быть доступны на запись. Логи, сессии, скомпилированные шаблоны и кеш конфигов пишутся именно туда. Типичный прод-фолбэк: «permission denied» после деплоя — забыли выставить права владельцу процесса php-fpm.
  • vendor — чужая территория. Правки в пакетах в vendor/ сотрутся при первом же composer install. Хотите изменить поведение пакета — переопределяйте конфиг, класс через контейнер или форкайте пакет, а не редактируйте файлы.
  • migrate:fresh стирает базу целиком. Название говорит честно: дроп всех таблиц и миграции с нуля. На проде это приговор данным, поэтому команда — только для локальной разработки и тестовых окружений.

Мини-шпаргалка

bash
# Ежедневный минимум
php artisan make:model Post -mf     # модель + миграция + фабрика
php artisan make:controller X --resource
php artisan migrate                 # накатить миграции
php artisan route:list              # посмотреть роуты и middleware
php artisan tinker                  # живая консоль с приложением

# Окружение
php artisan config:clear            # сбросить кеш конфигов
php artisan optimize:clear          # сбросить все кеши разом

# Прод-релиз
php artisan config:cache            # закешировать конфиги (после этого env() вне config — null)
php artisan route:cache             # закешировать роуты
php
// Правило чтения настроек:
env()      — только в файлах config/
config()   — везде в коде приложения

Три вывода

  1. Веб-сервер смотрит только в public, данные пишутся в storage, код приложения — в app. Это три границы, которые нельзя нарушать.
  2. .env — для окружения, конфиги — для приложения. env() живёт только в config/, везде дальше — config(). Это правило снимает половину проблем «на локалке работает, на проде нет».
  3. Artisan — не набор заклинаний, а рабочие руки фреймворка. route:list и tinker экономят больше времени, чем любые генераторы кода.

Коварный вопрос

Вопрос: «Почему после деплоя с закешированными конфигами значения из .env перестали читаться?»

Подвох: кажется, что кеш — это просто «быстрее достать те же данные». На деле config:cache переводит приложение в другой режим: конфиги собираются в один файл, а .env больше не загружается в рантайме вовсе — на то он и кеш. Спотыкаются на этом даже опытные разработчики: код с env() вне config/ выглядит рабочим, пока не окажется на проде.