Как связать symfony 8 и Angular 22?

от автора

Это сложно, необходимо установить много утилит, команд для запуска и обслуживания такого проекта, и вообще, я не думаю, что держать всё в одном проекте — жизнеспособная вещь.

Именно так я думал, когда начинал разбираться в этой теме. Но разобравшись, я с уверенностью могу сказать, что это реально, такая идея имеет место быть, и обслуживать такую архитектуру легко.

В данной статье будет описан процесс реализации архитектуры веб‑приложения с удобной системой докер-контейнеров. Здесь не будет обсуждаться CI/CD, исключительно development‑side процесса разработки.

Prerequirements

Сперва хочется сказать, что я буду использовать в этом проекте. Я не хочу засорять свой образ Windows 11 какими‑либо лишними программами и папками. Поэтому для разработки я всегда использую WSL (не говорите мне ничего плохого о нем, я всё знаю). Это необязательно, можно использовать любой дистрибутив линукса или яблочной ОС. Да хоть native винду) Каждый это делает как хочет. Но лично мне нравится работа в WSL.

Проекты хранятся внутри WSL, не снаружи в Windows хосте, а именно внутри WSL. Это делается для скорости работы. Если проект будет лежать на хосте, то при работе команд доступ к файлам будет осуществляться по особенному и гипер НЕ-быстрому протоколу. Не рекомендую. Но если файлы лежат внутри WSL, то доступ к файлам будет мгновенным.

В WSL у меня стоит самая свежая Ubuntu 24.04.

Естественно, ещё нужно установить Docker.

Я использую самый свежий на момент написания PHPStorm 2026.1.4 от Jetbrains, но абсолютно не важно, какой IDE вы пользуетесь. В теории можно и в блокноте писать хороший код.

Можно писать код в блокноте?

Можно писать код в блокноте?

Вот именно, что незачем, тем более, когда есть множество классных IDE, которые предлагают очень хороший автокомплит кода, сэмплы кода, работу с Git и так далее.

Также для удобства надо поставить команду make, которая умеет работать с файлами Makefile, так как все команды я обычно оборачиваю в свои собственные, это очень удобно.

Project skeleton

Хватит воды, приступим сразу к делу.

Создаём проект, я буду хранить его в домашней папке юзера: cd ~ && mkdir angular‑symfony‑template && cd angular‑symfony‑template.

Все следующие команды я буду писать из корня проекта.

Я обычно создаю отдельную папку docker в корне проекта, где хранятся конфиги докера. `mkdir docker`.

Создаем главный файл конфига докера: touch./docker/docker‑compose.yml.

services:    server:        image: nginx:stable-alpine # Самый стабильный минимальный образ nginx        container_name: angular_symfony_template_server        restart: always        ports:            - ${SERVER_PORT:-8080}:80 # Проброс порта из файла ./docker/.env        volumes:            - ../.:/var/www/project            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf # Проброс кастомного конфига nginx        depends_on: # Не запускать этот контейнер пока не будут запущены следующие контейнеры            - php        networks: # Общая сетка            - angular_symfony_template    node:        build:            context: ..            dockerfile: ./docker/node/Dockerfile        container_name: angular_symfony_template_node        restart: always        # Монтируем весь проект, но Dockerfile сам зайдет в подпапку frontend        volumes:            - ../:/var/www/project        ports:            - ${NODE_PORT:-4200}:4200        networks:            - angular_symfony_template    php:        container_name: angular_symfony_template_php        restart: always        build:            context: .            dockerfile: ./php/Dockerfile # Будем юзать кастомную сборку PHP            args:                USER_ID: "${USER_ID}"                GROUP_ID: "${GROUP_ID}"                USER_NAME: "${USER_NAME}"        ports:            - ${PHP_PORT:-9000}:9000 # Проброс порта из файла ./docker/.env        volumes:            - ../.:/var/www/project        networks:            - angular_symfony_templatenetworks:    angular_symfony_template:

Делаем ./docker/.env файл и заполняем его:

USER_ID=1000 # ID вашего юзера в контейнереUSER_NAME=!ChangeMe! # имя вашего юзера в контейнереGROUP_ID=1000 # группа вашего юзера в конейнереSERVER_PORT=8088 # порт nginxPHP_PORT=9099    # порт PHPNODE_PORT=4200   # порт Node

Делаем кастомный конфига nginx — ./docker/nginx/default.conf:

server {    listen 80;    server_name localhost;    # Выносим root на самый верх, чтобы он работал везде одинаково    root /var/www/project/public;    error_log /var/log/nginx/project_error.log;    access_log /var/log/nginx/project_access.log;    # 1. Запросы к API (обрабатывает Symfony)    location /api {        # Если пришли на /api или /api/, перенаправляем на index.php        rewrite ^/api/?(.*)$ /index.php/$1 last;    }    # 2. Обработка фронт-контроллера Symfony    location ~ ^/index\.php(/|$) {        fastcgi_pass php:9000;        fastcgi_split_path_info ^(.+\.php)(/.*)$;        include fastcgi_params;        fastcgi_buffer_size 128k;        fastcgi_buffers 4 256k;        fastcgi_busy_buffers_size 256k;        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;        fastcgi_param DOCUMENT_ROOT $document_root;        internal;    }    # 3. Все остальные запросы отдаем на Angular Dev Server    location / {        proxy_pass http://node:4200;        proxy_http_version 1.1;        proxy_set_header Upgrade $http_upgrade;        proxy_set_header Connection 'upgrade';        proxy_set_header Host $host;        proxy_cache_bypass $http_upgrade;    }    # Запрещаем прямой вызов других PHP файлов    location ~ \.php$ {        return 404;    }}

Делаем Dockerfile для PHP — `./docker/php/Dockerfile`:

FROM php:8.4-fpmARG USER_IDARG GROUP_IDARG USER_NAME# Создаем пользователя для работы в DockerRUN groupadd -g ${GROUP_ID} ${USER_NAME} || true && \    useradd -l -u ${USER_ID} -g ${GROUP_ID} -m -s /bin/bash ${USER_NAME} || true# Настройка прав для папки проектаRUN mkdir -p /var/www/project && \    chown -R ${USER_ID}:${GROUP_ID} /var/www# Устанавливаем только необходимый минимум для расширенийRUN apt-get update && apt-get install -y --no-install-recommends \    git \    unzip \    libicu-dev \    libzip-dev \    && docker-php-ext-install intl opcache pdo pdo_mysql zip \    && rm -rf /var/lib/apt/lists/*# Установка ComposerRUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer# Установка Symfony CLIRUN curl -sS https://get.symfony.com/cli/installer | bash && \    mv /root/.symfony5/bin/symfony /usr/local/bin/symfonyWORKDIR /var/www/project# Переключаемся на созданного пользователяUSER ${USER_NAME}

Не забываем о кастомной сборке ноды, где у нас уже будет установлен Angular CLI для комфортной работы с Angular:

FROM node:26-alpine# Устанавливаем Angular CLI глобально в системуRUN npm install -g @angular/cli# Выставляем рабочую директорию сразу в папку с фронтендомWORKDIR /var/www/project/frontend# Открываем порт для Angular Dev ServerEXPOSE 4200CMD ["npm", "run", "start"]

И сделаем ./Makefile, в котором мы будем описывать основные команды проекта:

# VARIABLESDOCKER_COMPOSE := docker compose -f ./docker/docker-compose.ymlINTERACTIVE    := $(shell [ -t 0 ] && echo "-it" || echo "-T")COMPOSE_EXEC   := $(DOCKER_COMPOSE) exec $(INTERACTIVE)## --- DOCKER ---.PHONY: up down start stop rebuild rebuild-php rebuild-nodeup:@$(DOCKER_COMPOSE) up -ddown:@$(DOCKER_COMPOSE) down --remove-orphansstart:@$(DOCKER_COMPOSE) startstop:@$(DOCKER_COMPOSE) stoprebuild:@$(DOCKER_COMPOSE) up -d --buildrebuild-php:@$(DOCKER_COMPOSE) up -d --build phprebuild-node:@$(DOCKER_COMPOSE) up -d --build node## --- APPLICATION INIT ---.PHONY: app-initapp-init: up composer-install front-init@echo "Application initialization completed!"## --- PHP / SYMFONY ---.PHONY: shell composer-installshell:@$(DOCKER_COMPOSE) exec php bashcomposer-install:@echo "Installing composer dependencies..."@$(COMPOSE_EXEC) php composer install --no-interaction --optimize-autoloader## --- FRONTEND (Angular) ---.PHONY: front-shell front-init front-clear front-reinitfront-shell:@$(DOCKER_COMPOSE) exec node shfront-init:@echo "Installing npm dependencies..."@$(COMPOSE_EXEC) node npm installfront-clear:@echo "Cleaning frontend artifacts..."@$(COMPOSE_EXEC) node rm -rf ./node_modules ./.angular ./distfront-reinit: front-clear front-init## --- LOGS ---.PHONY: logs logs-nginx logs-php logs-nodelogs:@$(DOCKER_COMPOSE) logs -f --tail=100logs-nginx:@$(DOCKER_COMPOSE) logs -f --tail=100 serverlogs-php:@$(DOCKER_COMPOSE) logs -f --tail=100 phplogs-node:@$(DOCKER_COMPOSE) logs -f --tail=100 node

Все готово. Строим контейнеры! `make up`

Убедимся, что все запущено и работает командой docker ps. Если там есть наши контейнеры, то все отлично!

CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                         NAMES7a5b07e1818b   nginx:stable-alpine   "/docker-entrypoint.…"   33 seconds ago   Up 32 seconds   0.0.0.0:8088->80/tcp, [::]:8088->80/tcp       angular_symfony_template_server4dc9069c4fa1   docker-php            "docker-php-entrypoi…"   33 seconds ago   Up 32 seconds   0.0.0.0:9099->9000/tcp, [::]:9099->9000/tcp   angular_symfony_template_phpbc8ae9b37155   node:26-alpine        "docker-entrypoint.s…"   33 seconds ago   Up 32 seconds   0.0.0.0:4200->4200/tcp, [::]:4200->4200/tcp   angular_symfony_template_node

Скелет нашего проекта готов. С этого момента можно накатывать angular и symfony.

Symfony

Я приверженец всего нового, я не вижу смысла новые проекты заводить на старых инструментах, поэтому будем использовать Symfony 8 — самую свежую версию фреймворка на момент написания статьи.

Проваливаемся в контейнер php: make shell. Эта команда закинет нас внутрь работающего PHP‑контейнера, где уже есть всё необходимое: PHP, Composer и Symfony CLI.

Поскольку композер не даст нам развернуть фреймворк симфони внутри папки, где уже есть какие‑либо файлы, то развернем его во временной папке: composer create‑project symfony/skeleton tmp_symfony . После того, как все пакеты загрузились нам надо закинуть все файлы в корень нашего проекта: cp ‑rP tmp_symfony/... Не забываем удалить старую временнуую папку — она нам больше не нужна: rm ‑rf tmp_symfony .

По сути, после этого Symfony готова. В данной статье я не буду рассматривать внутреннюю кухню как работает фреймворк, что за файлы он создал. Возможно, когда‑то в будущем.

Кстати, не забываем выходить из контейнера PHP: exit.

Angular

Проваливаемся в контейнер node: make front‑shell. У нас уже все готово в этом контейнере для наката скелета Angular. Сделаем это командой: ng new frontend‑app ‑directory=. ‑routing ‑style=scss ‑skip‑git.

На вопрос делиться ли псевдоанонимной информацией с гуглом отвечайте сами. Я ничего такого плохого в этом не вижу, поэтому отвечаю Y (yes).

Дальше ангуляр спросит будем ли мы юзать SSR. У нас Single Page Application(SPA), который общается с бэкендом на Symfony через API, это только усложнит архитектуру. SSR (Server‑Side Rendering) заставил бы Node.js постоянно рендерить страницы на сервере, что нагрузит контейнер, а в связке с Symfony. Поэтому твердо и чётко — No.

Следующий вопрос от установщика — какие ИИ тулы вы ходите сконфигурировать в проекте. Я не юзаю такое. Выбираю none.

После этого начинают накатываться файлы фреймворка и подтягиваться зависимости. Все файлы будут положены в папке frontend (так мы настроили в конфиге докера).

Выходим из контейнера node: exit.

Мы настроили проксирование запросов для nginx. Осталось только допилить конфиг ангуляра под это.

В файле frontend/package.json нам надо поправить строку start, чтобы Nginx из своего контейнера мог достучаться до dev‑сервера Angular, Angular должен слушать внешние порты Docker: «start»: «ng serve ‑host 0.0.0.0».

Всё. Теперь нам просто последний раз надо перестроить проект: make rebuild.

После этого всего мы можем проверить наш ангуляр по ссылке: localhost:8088. Порт только свой укажите.

Angular построен

Angular построен

Работает hot reload — значит, если изменить файлы фронта и сохранить их, браузер перезагрузит элемент, который меняли. Очень удобная штука.

Также если сейчас зайти на localhost:8088/api, то бэк вернет стандартный ответ о том, что сейчас рут не найден. Тут не стоит пугаться, мы и вправду не сделали еще рута. Предлагаю быстренько его сделать. Для этого заходим в контейнер php: make shell. Так как симфони предлагает очень удобный бандл для создания сущностей, то я не смею не исспользовать его. Но для начала накатим его, так как симфони у нас сейчас голый: composer require symfony/maker‑bundle ‑dev. И после этого уже создаем рут api: php bin/console make:controller ApiHomeController.

Создался файл src/Controller/ApiHomeController, заполним его базовой заглушкой:

<?phpnamespace App\Controller;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Attribute\Route;final class ApiHomeController extends AbstractController{    #[Route('/api', name: 'app_api_home', methods: ['GET'])]    public function index(): Response    {        return new Response('// TODO: implement me');    }}

После этого, если перейти по ссылке localhost:8088/api, то нас будет приветствовать как раз наше сообщение, а не ошибка, как было раньше.

Ответ бэкэнда.

Ответ бэкэнда.

Итоги

Мы проделали отличную работу и развернули современный, отказоустойчивый стек для full‑stack разработки. Локальное окружение теперь настроено по всем правилам хорошего тона.

Nginx, склеил независимые контейнеры бэкенда и фронтенда на одном порту.

Фронтенд (http://localhost:8088/): Отдаёт чистый, работающий Angular 22 в режиме Watch‑mode с хот‑релоадом (изменения в коде сразу отображаются в браузере).

Бэкенд (http://localhost:8088/api): Успешно проксирует запросы на Symfony 8. Мы создали первый боевой контроллер ApiHomeController, который теперь вместо системной заглушки фреймворка отдаёт текстовый ответ: // TODO: implement me

Каркас приложения полностью готов к наполнению бизнес‑логикой. Всё работает как единый слаженный механизм.

Для удобства разработчиков я завернул все в гит репозиторий. Можете скачать его и юзать как основу. Распространяется на бесплатных условиях. Упоминать меня не обязательно:) Звездочку на гитхабе поставьте только, если не сложно.

Всем добра! Предлагайте свои доработки к данному скелету или подскажите где я ошибаюсь. Будет интересно почитать и пообщаться.

ссылка на оригинал статьи https://habr.com/ru/articles/1059392/