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

в 10:31, , рубрики: angular, symfony, архитектура, окружение, окружение разработки, разработка

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

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

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

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

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_template

networks:
    angular_symfony_template:

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

USER_ID=1000 # ID вашего юзера в контейнере
USER_NAME=!ChangeMe! # имя вашего юзера в контейнере
GROUP_ID=1000 # группа вашего юзера в конейнере

SERVER_PORT=8088 # порт nginx
PHP_PORT=9099    # порт PHP
NODE_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-fpm

ARG USER_ID
ARG GROUP_ID
ARG USER_NAME

# Создаем пользователя для работы в Docker
RUN 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/*

# Установка Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Установка Symfony CLI
RUN curl -sS https://get.symfony.com/cli/installer | bash && 
    mv /root/.symfony5/bin/symfony /usr/local/bin/symfony

WORKDIR /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 Server
EXPOSE 4200

CMD ["npm", "run", "start"]

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

# VARIABLES
DOCKER_COMPOSE := docker compose -f ./docker/docker-compose.yml
INTERACTIVE    := $(shell [ -t 0 ] && echo "-it" || echo "-T")
COMPOSE_EXEC   := $(DOCKER_COMPOSE) exec $(INTERACTIVE)

## --- DOCKER ---
.PHONY: up down start stop rebuild rebuild-php rebuild-node

up:
	@$(DOCKER_COMPOSE) up -d

down:
	@$(DOCKER_COMPOSE) down --remove-orphans

start:
	@$(DOCKER_COMPOSE) start

stop:
	@$(DOCKER_COMPOSE) stop

rebuild:
	@$(DOCKER_COMPOSE) up -d --build

rebuild-php:
	@$(DOCKER_COMPOSE) up -d --build php

rebuild-node:
	@$(DOCKER_COMPOSE) up -d --build node

## --- APPLICATION INIT ---
.PHONY: app-init
app-init: up composer-install front-init
	@echo "Application initialization completed!"

## --- PHP / SYMFONY ---
.PHONY: shell composer-install

shell:
	@$(DOCKER_COMPOSE) exec php bash

composer-install:
	@echo "Installing composer dependencies..."
	@$(COMPOSE_EXEC) php composer install --no-interaction --optimize-autoloader

## --- FRONTEND (Angular) ---
.PHONY: front-shell front-init front-clear front-reinit

front-shell:
	@$(DOCKER_COMPOSE) exec node sh

front-init:
	@echo "Installing npm dependencies..."
	@$(COMPOSE_EXEC) node npm install

front-clear:
	@echo "Cleaning frontend artifacts..."
	@$(COMPOSE_EXEC) node rm -rf ./node_modules ./.angular ./dist

front-reinit: front-clear front-init

## --- LOGS ---
.PHONY: logs logs-nginx logs-php logs-node

logs:
	@$(DOCKER_COMPOSE) logs -f --tail=100

logs-nginx:
	@$(DOCKER_COMPOSE) logs -f --tail=100 server

logs-php:
	@$(DOCKER_COMPOSE) logs -f --tail=100 php

logs-node:
	@$(DOCKER_COMPOSE) logs -f --tail=100 node

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

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

CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                         NAMES
7a5b07e1818b   nginx:stable-alpine   "/docker-entrypoint.…"   33 seconds ago   Up 32 seconds   0.0.0.0:8088->80/tcp, [::]:8088->80/tcp       angular_symfony_template_server
4dc9069c4fa1   docker-php            "docker-php-entrypoi…"   33 seconds ago   Up 32 seconds   0.0.0.0:9099->9000/tcp, [::]:9099->9000/tcp   angular_symfony_template_php
bc8ae9b37155   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, заполним его базовой заглушкой:

<?php

namespace AppController;

use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAttributeRoute;

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

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

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

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

Автор: antoxabobylev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js