Начало работы

Структура проекта

Проект Winter плоский: четыре файла в корне и одна папка под ваш код. Нет config/, нет routes/, нет обязательного app/. Раскладку внутри своей папки вы выбираете сами — фреймворк находит классы обходом файлов, а не по списку в конфиге.

Ваш код main/Служебное storage/Не сканируется vendor · storage · resources

После установки

text
my-app/
├── bootstrap.php      класс приложения: из чего оно состоит
├── call               точка входа для всех команд
├── composer.json      автозагрузка и зависимости
├── .env               переменные окружения

├── main/              ваш код, namespace Main\
│   └── MainController.php

├── storage/           служебное, в репозиторий не идёт
│   ├── cache/
│   └── logs/

└── vendor/

Всё остальное появляется по необходимости и создаётся вами: resources/ под шаблоны и переводы, docker/ под контейнер, каталоги внутри main/ — под то, как вам удобнее разложить классы.

Файлы в корне

bootstrap.php

Класс приложения. Объявляет, из чего приложение состоит, и служит точкой входа.

bootstrap.php
#[EnableWeb]
final class Application extends WinterApplication
{
  public static function main(array $argv): never
  {
      parent::run($argv);
  }
}

Атрибуты #[Enable*] — единственное место, где перечислен состав приложения. См. Состав приложения.

call

Запускает всё: сервер, консольные команды, генераторы. Внутри — три строки: подключить автозагрузку, подключить bootstrap.php, передать управление классу приложения.

bash
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 — соответствие пространства имён каталогу:

composer.json
{
  "autoload": {
      "psr-4": {
          "Main\\": "main/"
      }
  }
}

.env

Значения, зависящие от окружения. Полный список переменных — на странице Конфигурация. В репозиторий не коммитится.

main/ — ваш код

Единственная папка, которую вы наполняете. Внутри неё структура ваша: фреймворк не требует ни Controllers/, ни Models/, ни какого-либо другого имени.

text
main/
├── MainController.php
├── User/
│   ├── UserController.php
│   ├── UserService.php
│   └── UserRepository.php
└── Order/
  ├── OrderController.php
  └── OrderProcess.php

Здесь классы сгруппированы по предметной области, а не по типу — но раскладка «по типу» (Controllers/, Services/, Repositories/) работает ровно так же. Разницы для фреймворка нет.

Несколько корней

Один префикс Main\ — не предел. Добавьте свои в composer.json, и они начнут работать после composer dump-autoload:

composer.json
"psr-4": {
  "Main\\": "main/",
  "Api\\":  "api/",
  "Admin\\": "admin/"
}

Отдельные корни удобны, когда части приложения живут своей жизнью: у публичного API и у админки разные маршруты, разные middleware и часто разные люди.

Куда кладёт файлы make

Генератор идёт от пространства имён, а не от типа: путь к файлу выводится из имени, которое вы указали.

bash
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 структура восстанавливалась.

bash
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/ Статика, если раздаёте её приложением

Раздача статики включается явно, в настройке веб-слоя:

php
public function configureServer(ServerSettings $server): void
{
  $server->staticPath('resources/static');
}

Такие запросы обслуживаются сервером напрямую и в PHP не попадают: middleware, CORS и логирование запросов к ним не применяются.

Файлы контейнера

Команда php call cfg docker добавляет в проект готовую сборку:

text
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():

bootstrap.php
protected static function configure(ApplicationArguments $args): void
{
  Kernel::init(
      pathRoot:     __DIR__,
      pathResource: __DIR__ . '/assets',
      pathStorage:  '/var/lib/my-app',
  );
}

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

Дальше