Структура проекта
Проект Winter плоский: четыре файла в корне и одна папка под ваш код. Нет
config/, нет routes/, нет обязательного app/.
Раскладку внутри своей папки вы выбираете сами — фреймворк находит классы обходом
файлов, а не по списку в конфиге.
После установки
my-app/
├── bootstrap.php класс приложения: из чего оно состоит
├── call точка входа для всех команд
├── composer.json автозагрузка и зависимости
├── .env переменные окружения
│
├── main/ ваш код, namespace Main\
│ └── MainController.php
│
├── storage/ служебное, в репозиторий не идёт
│ ├── cache/
│ └── logs/
│
└── vendor/Всё остальное появляется по необходимости и создаётся вами: resources/ под
шаблоны и переводы, docker/ под контейнер, каталоги внутри main/ — под то, как
вам удобнее разложить классы.
Файлы в корне
bootstrap.php
Класс приложения. Объявляет, из чего приложение состоит, и служит точкой входа.
#[EnableWeb]
final class Application extends WinterApplication
{
public static function main(array $argv): never
{
parent::run($argv);
}
}Атрибуты #[Enable*] — единственное место, где перечислен состав приложения. См.
Состав приложения.
call
Запускает всё: сервер, консольные команды, генераторы. Внутри — три строки: подключить
автозагрузку, подключить bootstrap.php, передать управление классу приложения.
php call run dev # сервер разработки
php call mapping show # список маршрутов
php call make -c .User # создать контроллерФайл содержит chdir(__DIR__), поэтому команды работают из любого каталога:
php /path/to/my-app/call run поднимет сервер с правильными путями.
composer.json
Кроме зависимостей задаёт PSR-4 — соответствие пространства имён каталогу:
{
"autoload": {
"psr-4": {
"Main\\": "main/"
}
}
}.env
Значения, зависящие от окружения. Полный список переменных — на странице Конфигурация. В репозиторий не коммитится.
main/ — ваш код
Единственная папка, которую вы наполняете. Внутри неё структура ваша: фреймворк не
требует ни Controllers/, ни Models/, ни какого-либо другого имени.
main/
├── MainController.php
├── User/
│ ├── UserController.php
│ ├── UserService.php
│ └── UserRepository.php
└── Order/
├── OrderController.php
└── OrderProcess.phpЗдесь классы сгруппированы по предметной области, а не по типу — но раскладка «по
типу» (Controllers/, Services/, Repositories/) работает ровно так же.
Разницы для фреймворка нет.
Несколько корней
Один префикс Main\ — не предел. Добавьте свои в composer.json, и они начнут
работать после composer dump-autoload:
"psr-4": {
"Main\\": "main/",
"Api\\": "api/",
"Admin\\": "admin/"
}Отдельные корни удобны, когда части приложения живут своей жизнью: у публичного API и у админки разные маршруты, разные middleware и часто разные люди.
Куда кладёт файлы make
Генератор идёт от пространства имён, а не от типа: путь к файлу выводится из имени, которое вы указали.
php call make -c .User # main/UserController.php
php call make -c main.api.User # main/Api/UserController.phpЕсли каталог уже существует и назван привычно, генератор положит файл туда:
| Тип | Ищет каталог |
|---|---|
| Контроллер | Controllers/, Controller/ |
| Сервис | Services/, Service/ |
| Middleware | Middlewares/, Controllers/Middlewares/ |
| Репозиторий | Repositories/, Repository/ |
| Сущность, DTO | Entity/, Entity/Dto/ |
| Процесс, демон | Processes/, Daemons/ |
Нет такого каталога — файл ляжет рядом, по namespace. Создавать их заранее не нужно.
Что сканируется
При старте фреймворк обходит весь проект от корня, читает каждый .php и
подключает те, в которых объявлен класс. Так находятся контроллеры, конфигурации,
процессы — регистрировать их нигде не требуется.
Из обхода исключены три каталога:
| Каталог | Почему |
|---|---|
vendor/ |
Зависимости не участвуют в поиске классов приложения |
storage/ |
Там лежит сгенерированный код — обход читал бы результат прошлого обхода |
resources/ |
Там лежат шаблоны: PHP-файлы по природе, не классы |
Шаблоны исключены не для скорости
Сканер ищет объявление класса в каждом .php и подключает файл, где его нашёл. Для
шаблона это означает выполнение: он выведет свою разметку в вывод приложения и
запустит всё, что есть у него на верхнем уровне. Это проверено на живом проекте, а
не предположено.
Поэтому шаблоны держите под resources/. Каталог с шаблонами, положенный внутрь
main/, попадёт под обход.
Отсюда практическое правило: всё, что не является классом приложения, должно жить
в resources/ или storage/. Скрипты, дампы, миграционные однострочники в корне
проекта будут прочитаны и, если объявляют класс, подключены.
storage/ — служебное
| Каталог | Что там | Кто создаёт |
|---|---|---|
storage/cache/ |
Кеш приложения — данные, которые вы кладёте сами | storage init |
storage/logs/ |
Файлы логов при LOG_OUTPUT=file |
storage init |
storage/runnable/ |
Файлы блокировок процессов-одиночек | Появляется при первом запуске такого процесса |
Каталог целиком в .gitignore, но сам файл .gitignore коммитится — чтобы после
git clone структура восстанавливалась.
php call storage init # создать каталоги
php call storage clean # очистить, сохранив cache/ и logs/Кеш загрузки лежит не здесь
Список классов (di.php), прокси #[Async] и прочее, что фреймворк генерирует при
старте, по умолчанию пишется во временный каталог системы —
/tmp/flytachi.winter.volatile.<имя-проекта>, — а не в storage/.
Так сделано, чтобы сгенерированный код не попадал в образ и не переживал
перезагрузку машины. Практическое следствие: чтобы сбросить его, каталог storage/
чистить бесполезно — есть php call di clean.
Положить его внутрь проекта можно флагом isTmpVolatile: false в Kernel::init(),
но тогда каталог обязан быть доступен на запись пользователю, от которого работает
приложение.
resources/ — шаблоны и переводы
Каталога изначально нет; создайте, когда понадобится.
| Путь | Что там |
|---|---|
resources/views/ |
Шаблоны и макеты для ResponseView |
resources/lang/ |
Словари переводов: ru.php, en.php |
resources/static/ |
Статика, если раздаёте её приложением |
Раздача статики включается явно, в настройке веб-слоя:
public function configureServer(ServerSettings $server): void
{
$server->staticPath('resources/static');
}Такие запросы обслуживаются сервером напрямую и в PHP не попадают: middleware, CORS и логирование запросов к ним не применяются.
Файлы контейнера
Команда php call cfg docker добавляет в проект готовую сборку:
my-app/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
└── docker/
├── entrypoint.sh запуск: dev или прод
├── php-opcache.ini настройки opcache для прода
├── php-memory.ini
└── dependencies/ ваши скрипты установки пакетовРежим выбирается переменной: DEV=true docker compose up — разработка с
перезапуском по изменению файлов, docker compose up — рабочий режим с opcache.
Расширения PHP и драйверы БД ставятся скриптами из dependencies/: что идёт в
комплекте, как удалить лишнее и как написать свой — в
cfg docker.
Свои пути
Раскладка по умолчанию выводится из расположения класса приложения. Если она вам не
подходит — переопределите в configure():
protected static function configure(ApplicationArguments $args): void
{
Kernel::init(
pathRoot: __DIR__,
pathResource: __DIR__ . '/assets',
pathStorage: '/var/lib/my-app',
);
}Вынести storage за пределы проекта обычно требуется в контейнере, где код лежит на
слое только для чтения, а писать нужно в примонтированный том.
Дальше
- Конфигурация —
.envи классы-конфигураторы - Состав приложения — атрибуты
#[Enable*] - Быстрый старт — первый маршрут
- Консольные команды —
make,storage,cfg