Разработка бэкенда сайта на PHP (Symfony)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бэкенда сайта на PHP (Symfony)
Сложная
от 1 недели до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка бэкенда сайта на PHP (Symfony)

Symfony — не быстрый старт. Это фундамент для проектов, которые должны жить долго, масштабироваться и поддерживаться командами разного состава. Высокий порог входа окупается предсказуемостью архитектуры, строгой типизацией и тем, что компоненты Symfony используются внутри Laravel, Drupal, Magento и десятков других систем — это индикатор их качества.

Symfony выбирают для: сложных монолитов с богатой доменной логикой, DDD-проектов, высоконагруженных API, enterprise-систем с долгосрочной поддержкой.

Архитектура компонентов

Symfony строится вокруг контейнера зависимостей (Service Container) и конфигурации через атрибуты PHP 8+. Всё — сервис, всё — внедряется автоматически:

namespace App\Service;

use App\Repository\ProductRepository;
use App\Event\ProductCreatedEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Psr\Cache\CacheItemPoolInterface;

final class ProductService
{
    public function __construct(
        private readonly ProductRepository $productRepository,
        private readonly EventDispatcherInterface $dispatcher,
        private readonly CacheItemPoolInterface $cache,
    ) {}

    public function create(CreateProductDto $dto): Product
    {
        $product = new Product(
            name: $dto->name,
            price: Money::of($dto->price, 'USD'),
            category: $dto->categoryId
                ? $this->productRepository->findCategoryOrFail($dto->categoryId)
                : null,
        );

        $this->productRepository->save($product, flush: true);
        $this->dispatcher->dispatch(new ProductCreatedEvent($product));

        // Инвалидация кеша
        $this->cache->deleteItem("product_{$product->getId()}");

        return $product;
    }
}

Контроллеры и маршруты

namespace App\Controller\Api\V1;

use App\Dto\CreateProductDto;
use App\Service\ProductService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/api/v1/products', name: 'api_products_')]
final class ProductController extends AbstractController
{
    public function __construct(private readonly ProductService $productService) {}

    #[Route('', name: 'list', methods: ['GET'])]
    public function list(ProductListQuery $query): JsonResponse
    {
        $result = $this->productService->getPaginated($query);
        return $this->json($result, context: ['groups' => ['product:list']]);
    }

    #[Route('', name: 'create', methods: ['POST'])]
    #[IsGranted('ROLE_ADMIN')]
    public function create(
        #[MapRequestPayload] CreateProductDto $dto
    ): JsonResponse {
        $product = $this->productService->create($dto);
        return $this->json($product, status: 201, context: ['groups' => ['product:detail']]);
    }

    #[Route('/{id}', name: 'show', methods: ['GET'])]
    public function show(Product $product): JsonResponse  // ParamConverter автоматически
    {
        return $this->json($product, context: ['groups' => ['product:detail']]);
    }
}

#[MapRequestPayload] — автоматическая десериализация + валидация через Symfony Validator:

namespace App\Dto;

use Symfony\Component\Validator\Constraints as Assert;

final class CreateProductDto
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Length(min: 2, max: 255)]
        public readonly string $name,

        #[Assert\Positive]
        public readonly float $price,

        #[Assert\Positive]
        #[Assert\IsNull]
        public readonly ?int $categoryId = null,

        #[Assert\Length(max: 5000)]
        public readonly ?string $description = null,
    ) {}
}

Doctrine ORM

Doctrine — полноценный ORM с Unit of Work паттерном. Принципиальное отличие от Active Record (Eloquent): Entity не знает о базе данных, репозиторий управляет persisting:

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;

#[ORM\Entity(repositoryClass: ProductRepository::class)]
#[ORM\Table(name: 'products')]
#[ORM\Index(columns: ['slug'], name: 'idx_products_slug')]
#[ORM\Index(columns: ['category_id', 'is_active'], name: 'idx_products_cat_active')]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['product:list', 'product:detail'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Groups(['product:list', 'product:detail'])]
    private string $name;

    #[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
    #[Groups(['product:list', 'product:detail'])]
    private string $price;

    #[ORM\ManyToOne(targetEntity: Category::class, inversedBy: 'products')]
    #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
    #[Groups(['product:detail'])]
    private ?Category $category = null;

    #[ORM\Column(type: 'json')]
    private array $attributes = [];

    // getters/setters...
}

Репозиторий с DQL:

namespace App\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;

class ProductRepository extends ServiceEntityRepository
{
    public function findActiveByCategory(int $categoryId, int $page = 1, int $limit = 20): array
    {
        return $this->createQueryBuilder('p')
            ->leftJoin('p.category', 'c')
            ->addSelect('c')
            ->where('p.isActive = :active')
            ->andWhere('p.category = :categoryId')
            ->setParameter('active', true)
            ->setParameter('categoryId', $categoryId)
            ->orderBy('p.createdAt', 'DESC')
            ->setFirstResult(($page - 1) * $limit)
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }
}

Безопасность и аутентификация

Symfony Security — один из самых гибких механизмов среди PHP-фреймворков:

# config/packages/security.yaml
security:
    password_hashers:
        App\Entity\User:
            algorithm: bcrypt
            cost: 12

    providers:
        app_provider:
            entity:
                class: App\Entity\User
                property: email

    firewalls:
        api:
            pattern: ^/api
            stateless: true
            jwt: ~

    access_control:
        - { path: ^/api/auth, roles: PUBLIC_ACCESS }
        - { path: ^/api/admin, roles: ROLE_ADMIN }
        - { path: ^/api, roles: ROLE_USER }

JWT через lexik/jwt-authentication-bundle:

#[Route('/api/auth/login', methods: ['POST'])]
public function login(
    #[MapRequestPayload] LoginDto $dto,
    UserRepository $users,
    UserPasswordHasherInterface $hasher,
    JWTTokenManagerInterface $jwtManager
): JsonResponse {
    $user = $users->findOneByEmail($dto->email);

    if (!$user || !$hasher->isPasswordValid($user, $dto->password)) {
        throw new UnauthorizedHttpException('', 'Invalid credentials');
    }

    return $this->json(['token' => $jwtManager->create($user)]);
}

Messenger и очереди

Symfony Messenger поддерживает: синхронный режим, AMQP (RabbitMQ), Redis Streams, SQS:

// Сообщение
final class SendEmailNotification
{
    public function __construct(
        public readonly int $userId,
        public readonly string $template,
        public readonly array $context = []
    ) {}
}

// Обработчик
#[AsMessageHandler]
final class SendEmailNotificationHandler
{
    public function __invoke(SendEmailNotification $message): void
    {
        $user = $this->userRepository->find($message->userId);
        $this->mailer->sendTemplate($user->getEmail(), $message->template, $message->context);
    }
}

// Диспетчеризация
$this->bus->dispatch(new SendEmailNotification($user->getId(), 'welcome'));

// Конфигурация транспорта
# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
        routing:
            'App\Message\SendEmailNotification': async

Кеширование

use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

final class ProductService
{
    public function getById(int $id): Product
    {
        return $this->cache->get("product_{$id}", function (ItemInterface $item) use ($id) {
            $item->expiresAfter(3600);
            $item->tag(["product_{$id}", 'products']);
            return $this->productRepository->find($id) ?? throw new NotFoundException();
        });
    }

    public function invalidate(int $id): void
    {
        $this->cache->invalidateTags(["product_{$id}"]);
    }
}

Сроки разработки

Symfony требует больше времени на настройку, но это инвестиция в поддержку:

  • Архитектура + DDD domain layer — 1–2 недели
  • Entities + Doctrine migrations — 1 неделя
  • API + Security + DTO — 2–3 недели
  • Messenger + интеграции — 1–2 недели
  • Тесты (PHPUnit + Foundry) — 1–2 недели

Сложный корпоративный сайт или портал: 8–16 недель. Symfony окупается на проектах с планируемым ростом, сложной доменной логикой и командой, которая в нём работает.