Перед початком роботи: «порожній проєкт»
Базовий старт: Docker, Symfony і база даних для передбачуваних збірок.
2025-09-09
Вибір стеку
⚡️ Скорочений шлях для нетерплячих: Не хочете читати все це? Просто заберіть готовий архів проєкту: GitHub-репозиторій.
Далі йде те, що я називаю «порожнім проєктом» — добірка контейнерів, софта, конфігурацій та налаштувань, які створюють передбачуване середовище.
Важливо: цей «порожній проєкт» є оптимальним лише в межах цього блогу. Це не рекомендація або базовий шаблон для старту будь-якого нового проєкту у 2025 році.
Для фреймворку ми використаємо Symfony — досі №1 для корпоративної PHP-розробки (так навіть на Вікіпедії написано). І ми ж усі довіряємо Вікіпедії, чи не так?
Рекомендована версія Symfony станом на вересень 2025 року — Symfony v7.3 (потребує PHP 8.2).
Для бази даних обираємо MySQL.
Так, MariaDB у деяких аспектах краща, і багато хто їй надає перевагу. Але пам’ятайте, що MariaDB не є повністю сумісною із синтаксисом MySQL.
Оскільки це лише тестове середовище, немає сенсу гнатися за максимальною продуктивністю чи гратися з нестандартними функціями. Саме тому ми залишимось із «рідним» Oracle MySQL замість MariaDB.
До того ж Doctrine v4.3 відверто віддає перевагу MySQL 8.4, тому саме цю версію ми й використаємо.
Звісно, існує MySQL 9.4 LTS — остання LTS-гілка (випущена влітку 2024 року) з підтримкою до липня 2032-го. Doctrine може працювати з нею, але все одно вважатиме її 8.4 (через MySQL84Platform). Doctrine тестувався саме на 8.4.
Тож, для максимальної сумісності й передбачуваності наш вибір — MySQL 8.4 LTS.
Наш стек виглядає так:
- PHP 8.2
- MySQL 8.4
- Symfony 7.3
Ми запускатимемо все в контейнерах за допомогою Docker Compose, а не Kubernetes.
Оскільки це локальне тестове середовище, а не продакшн-кластер, Docker Compose більш ніж достатній і не буде надмірним рішенням. До речі, сам Docker прямо каже, що власне для цього він і створений. А ми поважаємо офіційну документацію.
Допоміжні файли
.dockerignore
.git
.gitignore
node_modules
vendor
var
.idea
.DS_Store
Скрипт ініціалізації проєкту
Якщо ви взяли «порожній проєкт» із мого репозиторію, у корені ви знайдете init.sh, який запускає середовище та створює Symfony «порожній проєкт» (див. нижче).
Налаштування для роботи з проєктом, а особливо з MySQL, зберігаються в кореневому файлі .env.
Скрипт призначений для роботи в системах Linux і macOS.
💡 Примітка для Windows: скрипт
init.shвимагає bash. У Windows запускайте його через WSL2 або інше bash-середовище.
compose.yml Фрагмент — MySQL 8.4
services:
db:
image: mysql:8.4
container_name: db
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
command: >
--character-set-server=utf8mb4
--collation-server=utf8mb4_0900_ai_ci
volumes:
- db_data:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d:ro
- ./mysql-conf/my.cnf:/etc/mysql/conf.d/zzz_my.cnf:ro
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"${MYSQL_ROOT_PASSWORD}\" --silent"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data:
.env (наступний за compose.yml)
MYSQL_ROOT_PASSWORD=secretroot
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=secretpass
TZ=Europe/Kyiv
MySQL скрипти ініціалізації
project-root/
mysql-init/
00_schema.sql
01_seed.sql
MySQL налаштування
[mysqld]
sql_mode=STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY
innodb_flush_log_at_trx_commit=1
character-set-server=utf8mb4
collation-server=utf8mb4_0900_ai_ci
Основні моменти
- Порт 3306 відкритий — це погана практика, окрім випадків домашньої лабораторії або якщо ви дійсно знаєте, що робите.
- UTF8MB4 — це справжній UTF-8 у MySQL (не стара урізана версія).
- Порівняння
utf8mb4_0900_ai_ci— нечутливе до регістру та діакритики. На практиці: café = cafe, Hello = hello. healthcheck— корисна річ для залежностей між сервісами.- Скрипти ініціалізації разом із власним my.cnf формують передбачувану початкову конфігурацію.
Образ PHP із Composer та pdo_mysql
Створіть docker/php/Dockerfile:
# docker/php/Dockerfile
FROM composer:2 AS composer_src
FROM dunglas/frankenphp:1-php8.2
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends git zip unzip; \
rm -rf /var/lib/apt/lists/*; \
docker-php-ext-install pdo_mysql
COPY --from=composer_src /usr/bin/composer /usr/local/bin/composer
WORKDIR /var/www/html
Фрагмент compose.yml — PHP 8.2 (FrankenPHP + Composer)
services:
php:
build: ./docker/php
container_name: php
restart: unless-stopped
ports:
- "8080:80"
working_dir: /var/www/html
volumes:
- ./app:/var/www/html:cached
- ./docker/php/Caddyfile:/etc/frankenphp/Caddyfile:ro
environment:
SYMFONY_CLI_VERSION: stable
COMPOSER_ALLOW_SUPERUSER: 1
TZ: Europe/Kyiv
depends_on:
db:
condition: service_healthy
Основні моменти
- FrankenPHP включає Caddy — для розробки це «все з коробки».
- Composer і розширення
pdo_mysqlвже вбудовані в образ. - Каталог
./appпотрібно створити вручну заздалегідь. - (Linux) Якщо важлива власність файлів, виконуйте команди з параметром
-u "$(id -u):$(id -g)"або задайте фіксоване значенняuser:уcompose.yml.
Caddyfile — власна конфігурація сервера
FrankenPHP постачається з Caddy, який за замовчуванням перенаправляє HTTP → HTTPS. Щоб уникнути плутанини під час локальної розробки, додаємо власний Caddyfile, щоб Symfony відповідав безпосередньо на порту 8080.
Створіть docker/php/Caddyfile:
:80 {
root * /var/www/html/public
php_server
file_server
}
Це вимикає автоматичний HTTPS і гарантує, що ваш застосунок буде доступний за адресою http://localhost:8080.
Очищення конфігурації Doctrine
У Linux рецепти Symfony можуть згенерувати файл config/packages/doctrine.yaml, який містить параметри, пов’язані з PostgreSQL, та закоментовані значення за замовчуванням. Неочікувано, але в macOS інсталятор взагалі не створює цей файл.
Наш скрипт init.sh нормалізує цей файл, приводячи його до чистої конфігурації MySQL.
Отже, ми або коригуємо app/config/packages/doctrine.yaml, якщо він був створений інсталятором, або створюємо його самостійно ось так:
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
server_version: '8.4'
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
mappings:
App:
is_bundle: false
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\\Entity'
alias: App
Таким чином, наша конфігурація Doctrine відповідатиме базі даних MySQL 8.4, що використовується в контейнері, без «залишків» від PostgreSQL — принаймні я на це сподіваюся, адже Doctrine постійно змінює формат цього файлу.
Підсумкова структура проєкту
docker-empty-project
├── compose.yml
├── .env
├── .dockerignore
├── init.sh
├── app/
├── docker/
│ └── php/
│ ├── Dockerfile
│ └── Caddyfile
├── mysql-init/
│ ├── 00_schema.sql
│ └── 01_seed.sql
└── mysql-conf/
└── my.cnf
Ініціалізація
Одна команда з кореня проєкту:
chmod +x ./init.sh
./init.sh # або `sudo ./init.sh`, залежно від вашої системи
Скрипт запустить контейнери, створить каталог ./app, встановить каркас Symfony, вимкне Docker-рецепти Flex, інсталює ORM і налаштує MySQL DATABASE_URL у app/.env. Також він запише app/config/packages/doctrine.yaml із параметром server_version: '8.4'.
Застосунок буде доступний за адресою: http://localhost:8080
Ось вміст цього скрипту — нічого незвичного, якщо ви вже працювали з Symfony:
# init.sh
#!/usr/bin/env bash
set -euo pipefail
if [[ ! -f .env ]]; then
echo "ERROR: .env not found next to compose.yml"
exit 1
fi
export $(grep -E '^(MYSQL_ROOT_PASSWORD|MYSQL_DATABASE|MYSQL_USER|MYSQL_PASSWORD|TZ)=' .env | xargs)
DB_URL="mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@db:3306/${MYSQL_DATABASE}?charset=utf8mb4"
GITKEEP_PRESENT=0
if [ -f app/.gitkeep ]; then
GITKEEP_PRESENT=1
rm -f app/.gitkeep
fi
if [ -n "$(ls -A app 2>/dev/null)" ]; then
echo "ERROR: ./app is not empty. Please clean it (even a single .gitignore will block create-project)."
exit 1
fi
docker compose up -d
docker compose run --rm php composer create-project symfony/skeleton .
docker compose run --rm php composer config extra.symfony.docker false
docker compose run --rm \
-e DATABASE_URL="$DB_URL" \
php composer require symfony/orm-pack
docker compose run --rm php composer require --dev symfony/maker-bundle
if grep -q '^###> doctrine/doctrine-bundle ###' app/.env; then
awk -v repl="###> doctrine/doctrine-bundle ###\nDATABASE_URL=\"${DB_URL}\"\n###< doctrine/doctrine-bundle ###" '
/^###> doctrine\/doctrine-bundle ###/ {print repl; skip=1; next}
skip && /^###< doctrine\/doctrine-bundle ###/ {skip=0; next}
!skip {print}
' app/.env > app/.env.tmp && mv app/.env.tmp app/.env
else
{
echo '###> doctrine/doctrine-bundle ###'
echo "DATABASE_URL=\"${DB_URL}\""
echo '###< doctrine/doctrine-bundle ###'
} >> app/.env
fi
DOCTRINE_YAML="app/config/packages/doctrine.yaml"
if [ -f "$DOCTRINE_YAML" ]; then
awk '
/^ *#server_version/ { print " server_version: '\''8.4'\''"; next }
/identity_generation_preferences:/ { skipblock=1; next }
skipblock && /^[^[:space:]]/ { skipblock=0 } # end of block
skipblock { next }
{ print }
' "$DOCTRINE_YAML" > "${DOCTRINE_YAML}.tmp" && mv "${DOCTRINE_YAML}.tmp" "$DOCTRINE_YAML"
echo "✓ Doctrine config normalized for MySQL (server_version=8.4, removed Postgres hints)."
else
mkdir -p app/config/packages
cat > "$DOCTRINE_YAML" <<'EOF'
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
server_version: '8.4'
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
mappings:
App:
is_bundle: false
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\\Entity'
alias: App
EOF
echo "✓ Doctrine config created fresh for MySQL (server_version=8.4)."
fi
if [ "$GITKEEP_PRESENT" -eq 1 ]; then
touch app/.gitkeep
fi
echo
echo "✔ Готово. Каркас Symfony встановлено у ./app"
echo " Застосунок працює за адресою: http://localhost:8080"
echo " Використано DATABASE_URL: $DB_URL"
Примітка для Linux
Якщо файли в ./app мають власника root, виконуйте команди з:
docker compose run --rm -u "$(id -u):$(id -g)" php …
Або задайте параметр user: у compose.yml (для середовищ розробки).
Ітоги
На прикінці маємо чистий каркас Symfony + база даних без зайвих пакетів.
⚡️ Нагадування: Ви все ще можете просто завантажити готовий архів проєкту: GitHub-репозиторій.
Перевірте:
- Усередині
app/ви маєте побачити стандартну структуру Symfony (bin/,config/,public/,src/,var/,vendor/,composer.jsonтощо). - Порожній проєкт відповідає за адресою http://localhost:8080.