На экзамене проверяют знание Symfony best practices, PSR-стандартов и backward compatibility promise. Это теоретические вопросы, которые нельзя угадать — нужно знать точно.
Symfony Best Practices
Контроллеры
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
// ✅ BEST PRACTICE: thin controllers, extend AbstractController
#[Route('/products', name: 'product_')]
class ProductController extends AbstractController
{
// ✅ Use constructor injection
public function __construct(
private readonly ProductService $productService,
) {
}
// ✅ Keep actions small — delegate to services
#[Route('', name: 'list')]
public function list(): Response
{
$products = $this->productService->getAll();
return $this->render('product/list.html.twig', [
'products' => $products,
]);
}
// ❌ BAD: business logic in controller
// ✅ GOOD: delegate to service
}
Конфигурация
| Best Practice | Описание |
|---|---|
| Атрибуты для routing | #[Route] вместо YAML |
| YAML для services | config/services.yaml |
| Env variables | Для секретов и инфраструктуры |
| Parameters | Для значений приложения |
| Constants | Для бизнес-логики |
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
Подвох экзамена: Symfony рекомендует использовать атрибуты для routing и YAML для сервисов (не наоборот). Env variables — для инфраструктурных настроек (DATABASE_URL), Parameters — для настроек приложения (items_per_page). Не путайте.
Сервисы
<?php
declare(strict_types=1);
// ✅ BEST PRACTICE: use constructor injection
class OrderService
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly MailerInterface $mailer,
private readonly LoggerInterface $logger,
) {
}
}
// ❌ BAD: service locator pattern
class BadService
{
public function doSomething(ContainerInterface $container): void
{
$em = $container->get('doctrine.orm.entity_manager');
}
}
PSR стандарты
PSR-4: Autoloading
// composer.json
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
| Namespace | Путь к файлу |
|---|---|
App\Controller\ProductController |
src/Controller/ProductController.php |
App\Entity\Product |
src/Entity/Product.php |
App\Service\OrderService |
src/Service/OrderService.php |
Правила PSR-4:
- Один класс = один файл
- Имя файла = имя класса +
.php - Namespace маппится на директорию
PSR-7: HTTP Message Interface
<?php
declare(strict_types=1);
// PSR-7 defines interfaces for HTTP messages
// Symfony does NOT use PSR-7 internally (uses HttpFoundation)
// But provides a bridge:
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
// Convert Symfony Request to PSR-7
$psr7Factory = new PsrHttpFactory(/* ... */);
$psrRequest = $psr7Factory->createRequest($symfonyRequest);
// Convert PSR-7 to Symfony Request
$httpFoundationFactory = new HttpFoundationFactory();
$symfonyRequest = $httpFoundationFactory->createRequest($psrRequest);
Подвох экзамена: Symfony НЕ использует PSR-7 напрямую. HttpFoundation — собственная реализация Symfony, появившаяся до PSR-7. Symfony предоставляет Bridge для конвертации между форматами. Если спросят "реализует ли Symfony PSR-7?" — ответ: нет, но предоставляет bridge.
PSR-11: Container Interface
<?php
declare(strict_types=1);
use Psr\Container\ContainerInterface;
// Symfony DI Container implements PSR-11
// ContainerInterface defines two methods:
interface ContainerInterface
{
public function get(string $id): mixed;
public function has(string $id): bool;
}
// In Symfony, service locator is a mini-container
// AbstractController uses it internally:
class AbstractController
{
// $this->container is a ServiceLocator (PSR-11)
public static function getSubscribedServices(): array
{
return [
'router' => RouterInterface::class,
'twig' => Environment::class,
// ...
];
}
}
PSR-12: Extended Coding Style
| Правило | Пример |
|---|---|
| Открывающая скобка класса | На новой строке |
| Открывающая скобка метода | На новой строке |
| Открывающая скобка if/for | На той же строке |
| Видимость | Обязательна для всех методов |
| Отступы | 4 пробела (не табы) |
| Длина строки | Мягкий лимит 120 символов |
<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\Product;
use App\Repository\ProductRepository;
// PSR-12 compliant code
class ProductService
{ // ← New line for class
public function __construct(
private readonly ProductRepository $repository,
) { // ← New line for method
}
public function findExpensive(float $minPrice): array
{ // ← New line for method
if ($minPrice < 0) { // ← Same line for if
throw new \InvalidArgumentException('Price must be positive');
}
return $this->repository->findByMinPrice($minPrice);
}
}
PSR-15: HTTP Server Request Handlers
<?php
declare(strict_types=1);
// PSR-15 defines middleware interfaces
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
// Symfony uses its own event system instead of PSR-15 middleware
// But the concepts are similar:
// PSR-15 Middleware ≈ Symfony Event Listener on kernel.request/kernel.response
Сводная таблица PSR
| PSR | Название | Symfony использует |
|---|---|---|
| PSR-1 | Basic Coding Standard | Да |
| PSR-3 | Logger Interface | Да (LoggerInterface) |
| PSR-4 | Autoloading | Да (через Composer) |
| PSR-6 | Caching Interface | Да (Cache component) |
| PSR-7 | HTTP Message | Через Bridge |
| PSR-11 | Container Interface | Да (DI Container) |
| PSR-12 | Extended Coding Style | Рекомендуется |
| PSR-14 | Event Dispatcher | Да (EventDispatcher) |
| PSR-15 | HTTP Handlers | Через Bridge |
| PSR-16 | Simple Cache | Да (Cache component) |
| PSR-17 | HTTP Factories | Через Bridge |
| PSR-18 | HTTP Client | Да (HttpClient) |
Подвох экзамена: Symfony реализует PSR-3 (Logger), PSR-6 и PSR-16 (Cache), PSR-11 (Container), PSR-14 (EventDispatcher), PSR-18 (HttpClient) нативно. PSR-7 и PSR-15 — только через Bridge. На экзамене часто спрашивают, какие PSR поддерживаются нативно.
Backward Compatibility Promise
Что гарантирует Symfony
Внутри major-версии (например 7.0 → 7.4):
<?php
declare(strict_types=1);
// ✅ Гарантируется стабильным:
// Public API — public methods и их сигнатуры
// Интерфейсы — для type-hinting (использования)
// Конфигурация — формат YAML/XML/PHP
// Console commands — их существование и аргументы
// ❌ НЕ гарантируется:
// @internal классы и методы
// Private/protected методы
// Добавление методов в интерфейсы
// Экспериментальные фичи (@experimental)
// Формат вывода (текст ошибок, debug output)
Deprecation Process
<?php
declare(strict_types=1);
// Symfony deprecation lifecycle:
// Version 7.0: Feature X available, Feature Y introduced
// Version 7.1-7.4: Feature X triggers deprecation notice
// Version 8.0: Feature X removed, Feature Y is the standard
// Example: trigger_deprecation()
trigger_deprecation(
'symfony/framework-bundle',
'7.2',
'Method "%s" is deprecated, use "%s" instead.',
'oldMethod',
'newMethod',
);
| Стадия | Что происходит |
|---|---|
| Версия N.0 | Фича доступна, работает |
| Версия N.x | Фича deprecated (warning) |
| Версия N+1.0 | Фича удалена |
Подвох экзамена: Deprecation в Symfony — это 2-step process. Сначала deprecation notice (можно обнаружить в тестах через
PHPUnit Bridge), потом удаление в следующей major-версии. Symfony НИКОГДА не удаляет фичу без предварительного deprecation.
Symfony 8.0: Все фичи, deprecated в Symfony 7.x, удалены в 8.0. При миграции с 7.4 на 8.0 нужно сначала исправить все deprecation notices. PHPUnit Bridge помогает обнаружить их в тестах.
Semantic Versioning
Symfony следует SemVer: MAJOR.MINOR.PATCH
| Тип | Когда | Пример | BC breaks |
|---|---|---|---|
| PATCH | Bug fixes | 7.4.1 → 7.4.2 | Нет |
| MINOR | New features | 7.3 → 7.4 | Нет |
| MAJOR | Breaking changes | 7.4 → 8.0 | Да |
Symfony выпускает minor-версию каждые 6 месяцев и major-версию каждые 2 года.