Symfony построен на принципах объектно-ориентированного программирования. На экзамене проверяют глубокое понимание ООП в PHP, включая тонкие различия между абстрактными классами и интерфейсами, правила наследования и использование трейтов.
Классы и объекты
Базовое объявление класса
<?php
declare(strict_types=1);
namespace App\Entity;
final class Product
{
public function __construct(
private readonly string $name,
private readonly float $price,
private string $description = '',
) {
}
public function getName(): string
{
return $this->name;
}
public function getPrice(): float
{
return $this->price;
}
public function getDescription(): string
{
return $this->description;
}
public function withDescription(string $description): self
{
$clone = clone $this;
$clone->description = $description;
return $clone;
}
}
Ключевые модификаторы классов
| Модификатор | Описание | Symfony-контекст |
|---|---|---|
final |
Запрет наследования | Рекомендуется Symfony для сервисов |
abstract |
Нельзя создать экземпляр | Базовые классы (AbstractController) |
readonly |
Все свойства readonly (PHP 8.2+) | DTO, Value Objects |
<?php
declare(strict_types=1);
// Readonly class — PHP 8.2+
readonly class OrderDTO
{
public function __construct(
public string $customerName,
public float $total,
public \DateTimeImmutable $createdAt,
) {
}
}
Symfony 8.0: Symfony 8.0 требует PHP 8.2+ и активно использует
readonlyклассы для DTO. В Symfony 7.4 поддержка PHP 8.1+ —readonlyклассы доступны, но не обязательны.
Интерфейсы
Интерфейсы определяют контракт — набор методов без реализации. Symfony использует интерфейсы повсюду: EventSubscriberInterface, FormTypeInterface, VoterInterface.
<?php
declare(strict_types=1);
namespace App\Contract;
interface PaymentGatewayInterface
{
public function charge(float $amount, string $currency): PaymentResult;
public function refund(string $transactionId): bool;
public function getBalance(): float;
}
Интерфейсы с константами
<?php
declare(strict_types=1);
interface StatusInterface
{
// Constants in interfaces — allowed since PHP 8.1 with enum backing
public const STATUS_ACTIVE = 'active';
public const STATUS_INACTIVE = 'inactive';
public function getStatus(): string;
}
Множественная реализация
<?php
declare(strict_types=1);
// A class can implement MULTIPLE interfaces
final class StripeGateway implements
PaymentGatewayInterface,
LoggerAwareInterface,
\Stringable
{
public function charge(float $amount, string $currency): PaymentResult
{
// Implementation
}
public function refund(string $transactionId): bool
{
// Implementation
}
public function getBalance(): float
{
// Implementation
}
public function __toString(): string
{
return 'Stripe Payment Gateway';
}
}
Абстрактные классы
Абстрактный класс может содержать как абстрактные методы (без реализации), так и конкретные методы с реализацией. Ключевое отличие от интерфейса.
<?php
declare(strict_types=1);
namespace App\Service;
abstract class AbstractNotifier
{
// Concrete method — shared implementation
public function notify(string $recipient, string $message): void
{
$formatted = $this->formatMessage($message);
$this->send($recipient, $formatted);
$this->log($recipient, $formatted);
}
// Abstract method — MUST be implemented by child
abstract protected function send(string $recipient, string $message): void;
// Concrete method with default behavior
protected function formatMessage(string $message): string
{
return trim($message);
}
private function log(string $recipient, string $message): void
{
// Logging logic
}
}
final class EmailNotifier extends AbstractNotifier
{
protected function send(string $recipient, string $message): void
{
// Send email
}
}
final class SmsNotifier extends AbstractNotifier
{
protected function send(string $recipient, string $message): void
{
// Send SMS
}
// Override default formatting
protected function formatMessage(string $message): string
{
return mb_substr($message, 0, 160);
}
}
Подвох экзамена: На экзамене часто спрашивают разницу между abstract class и interface. Запомните таблицу ниже — это одна из самых частых тем.
Abstract class vs Interface — полное сравнение
| Критерий | Interface | Abstract class |
|---|---|---|
| Реализация методов | Нет (до PHP 8.0) | Да |
| Свойства | Нет (только константы) | Да |
| Конструктор | Нет | Да |
| Множественное наследование | Да (implements) | Нет (extends) |
| Модификаторы доступа | Только public | Любые |
| Тип-хинтинг | Да | Да |
final методы |
Нет | Да |
| Состояние (state) | Нет | Да |
Подвох экзамена: С PHP 8.0 интерфейсы НЕ могут содержать реализацию методов по умолчанию (в отличие от Java). Если на экзамене предложат вариант с default method в интерфейсе — это неправильный ответ.
Когда использовать что
<?php
declare(strict_types=1);
// USE INTERFACE when:
// - Defining a contract (what object CAN DO)
// - Multiple unrelated classes share the same contract
// - You need multiple implementation
interface CacheInterface // Symfony: PSR-6/16 caching
{
public function get(string $key): mixed;
public function set(string $key, mixed $value, int $ttl = 0): void;
}
// USE ABSTRACT CLASS when:
// - Sharing code between related classes
// - You need constructor / state / protected methods
// - Template Method pattern
abstract class AbstractCommand // Symfony: console commands base
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
abstract protected function execute(): int;
}
Трейты
Трейты — механизм повторного использования кода в PHP. Symfony использует трейты для сервисов, которые нуждаются в общей функциональности без наследования.
<?php
declare(strict_types=1);
namespace App\Trait;
trait TimestampableTrait
{
private \DateTimeImmutable $createdAt;
private ?\DateTimeImmutable $updatedAt = null;
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function markAsUpdated(): void
{
$this->updatedAt = new \DateTimeImmutable();
}
private function initTimestamps(): void
{
$this->createdAt = new \DateTimeImmutable();
}
}
trait SoftDeletableTrait
{
private ?\DateTimeImmutable $deletedAt = null;
public function softDelete(): void
{
$this->deletedAt = new \DateTimeImmutable();
}
public function isDeleted(): bool
{
return $this->deletedAt !== null;
}
public function restore(): void
{
$this->deletedAt = null;
}
}
// Using multiple traits
final class Article
{
use TimestampableTrait;
use SoftDeletableTrait;
public function __construct(
private string $title,
private string $content,
) {
$this->initTimestamps();
}
}
Конфликт трейтов
<?php
declare(strict_types=1);
trait A
{
public function hello(): string
{
return 'Hello from A';
}
}
trait B
{
public function hello(): string
{
return 'Hello from B';
}
}
class MyClass
{
use A, B {
A::hello insteadof B; // Use A's version
B::hello as helloB; // Alias B's version
}
}
Подвох экзамена: Конфликт трейтов без
insteadof— это fatal error. На экзамене могут показать код с двумя трейтами, имеющими одинаковый метод, без разрешения конфликта и спросить результат. Ответ: Fatal error.
Перечисление приоритета при конфликтах
При конфликте методов в PHP действует строгий порядок приоритетов:
- Текущий класс — методы класса всегда побеждают
- Трейт — методы трейта переопределяют унаследованные
- Родительский класс — методы родителя имеют наименьший приоритет
<?php
declare(strict_types=1);
class Base
{
public function method(): string
{
return 'Base';
}
}
trait MyTrait
{
public function method(): string
{
return 'Trait';
}
}
class Child extends Base
{
use MyTrait;
// Without own method: returns 'Trait' (trait > parent)
// With own method: returns own (class > trait > parent)
}
$child = new Child();
echo $child->method(); // 'Trait'
Ковариантность и контравариантность (PHP 7.4+)
Важная тема для Symfony — правила замещения типов при наследовании.
<?php
declare(strict_types=1);
// Covariance — return type can be MORE specific in child
interface AnimalShelter
{
public function adopt(): Animal;
}
class CatShelter implements AnimalShelter
{
public function adopt(): Cat // Cat extends Animal — VALID
{
return new Cat();
}
}
// Contravariance — parameter type can be LESS specific in child
interface EventHandler
{
public function handle(ClickEvent $event): void;
}
class WideHandler implements EventHandler
{
public function handle(Event $event): void // Event is parent of ClickEvent — VALID
{
}
}
Подвох экзамена: Ковариантность работает для возвращаемых типов (можно сужать), контравариантность — для параметров (можно расширять). Если перепутать направление — код не скомпилируется. На экзамене проверяют именно понимание направления.