Enterprise PHP: как пишут код в крупных компаниях

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему PHP до сих пор рулит в Enterprise, и как не ударить в грязь лицом

Когда слышишь «PHP», первая ассоциация — это лендинг за вечер или простенький блог на WordPress. Но откройте вакансии крупных банков, ритейлеров или телеком-компаний — там тонны вакансий для PHP-разработчиков. И это не про «быстро набросать код». Это — Enterprise PHP. Целая индустрия со своими законами, архитектурными паттернами и культурой код-ревью.

Чем отличается разработка на PHP в крупной компании от стартапа? Ты не просто пишешь «работающий код». Ты строишь систему, которая должна жить годы, пережить десятки релизов и выдержать нагрузку тысяч пользователей. В этой статье я разберу ключевые особенности Enterprise-разработки на PHP: от архитектуры до деплоя. Будет много кода, практических советов и минимум воды.

Архитектура: SOLID, слои и никакой магии

В Enterprise первое правило — код должен быть предсказуемым и легко тестируемым. Здесь не прокатит «вот тут я быстренько запилил запрос прямо в контроллере». В больших проектах ты обязан следовать принципам SOLID и четко разделять ответственность между слоями.

Типичная архитектура Enterprise-приложения на PHP выглядит так:

  • Presentation layer — контроллеры, роуты, Middleware.
  • Application layer — сервисы, DTO, команды/обработчики (CQRS).
  • Domain layer — сущности, Value Objects, репозитории (интерфейсы).
  • Infrastructure layer — ORM, внешние API, кэш, очереди.

Вот как это выглядит на практике. Вместо того чтобы писать SQL прямо в контроллере:

// ❌ Плохо: смешиваем слои
class OrderController {
    public function create(Request $request) {
        $db = new PDO('mysql:...');
        $db->exec("INSERT INTO orders (user_id, total) VALUES ({$request->get('user_id')}, {$request->get('total')})");
        return new JsonResponse(['status' => 'ok']);
    }
}

В Enterprise ты увидишь нечто подобное:

// ✅ Хорошо: разделение ответственности
class OrderController {
    public function __construct(
        private OrderService $orderService
    ) {}

    public function create(CreateOrderRequest $request): JsonResponse {
        $dto = new CreateOrderDTO(
            userId: $request->getUserId(),
            total: $request->getTotal()
        );

        $order = $this->orderService->createOrder($dto);

        return new JsonResponse(['id' => $order->getId()]);
    }
}

class OrderService {
    public function __construct(
        private OrderRepositoryInterface $orderRepository,
        private EventDispatcher $eventDispatcher
    ) {}

    public function createOrder(CreateOrderDTO $dto): Order {
        $order = new Order($dto->userId, $dto->total);
        $this->orderRepository->save($order);
        $this->eventDispatcher->dispatch(new OrderCreatedEvent($order));
        return $order;
    }
}

Видите разницу? Второй вариант можно протестировать, переиспользовать и легко изменить. Это и есть Enterprise PHP — когда код не просто работает, а спроектирован.

Value Objects и строгая типизация

В крупных проектах ты не увидишь string $email в параметрах. Вместо этого используются Value Objects. Это самодокументируемый код, который защищает от ошибок на этапе компиляции.

class Email {
    public function __construct(private string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('Invalid email');
        }
    }

    public function getValue(): string {
        return $this->value;
    }
}

class User {
    public function __construct(
        private UserId $id,
        private Email $email,
        private FullName $fullName
    ) {}
}

Теперь никто случайно не передаст в конструктор User строку вместо Email — компилятор или статический анализатор (например, PHPStan на уровне max) сразу укажет на ошибку.

Тестирование: без покрытия релиз не пройдет

В Enterprise-разработке на PHP тесты — это не опция, а требование. Крупные компании используют три уровня тестирования:

  • Unit-тесты — проверяют отдельные классы и методы. Используются моки и стабы. Покрытие часто требует >80%.
  • Integration-тесты — проверяют взаимодействие с базой, кэшем, внешними сервисами. Запускаются в изолированном окружении (тестовая БД, Docker).
  • Functional/E2E-тесты — симулируют действия пользователя через HTTP.

Пример unit-теста для нашего OrderService:

class OrderServiceTest extends TestCase {
    public function testCreateOrderSavesCorrectly(): void {
        // Arrange
        $repository = $this->createMock(OrderRepositoryInterface::class);
        $repository->expects($this->once())
            ->method('save')
            ->with($this->isInstanceOf(Order::class));

        $dispatcher = $this->createMock(EventDispatcher::class);
        $dispatcher->expects($this->once())
            ->method('dispatch')
            ->with($this->isInstanceOf(OrderCreatedEvent::class));

        $service = new OrderService($repository, $dispatcher);
        $dto = new CreateOrderDTO(userId: 1, total: 100.50);

        // Act
        $order = $service->createOrder($dto);

        // Assert
        $this->assertEquals($dto->userId, $order->getUserId());
        $this->assertEquals($dto->total, $order->getTotal());
    }
}

В Enterprise-проектах тесты запускаются автоматически при каждом пуше в репозиторий. Если тесты упали — код не мержится. Сурово, но справедливо.

Код-ревью: как не получить «минус» на ревью

Код-ревью в крупных компаниях — это священная корова. Каждый MR (Merge Request) проверяется минимум двумя разработчиками. Что ищут на ревью в Enterprise PHP?

  • Архитектурные нарушения — например, если сервис начал лезть в HTTP-запрос или контроллер делает запросы к БД.
  • Отсутствие тестов — новый метод без теста = причина отклонить MR.
  • Проблемы безопасности — экранирование, XSS, SQL-инъекции, валидация.
  • Стиль и нейминг — PSR-12 обязателен, названия классов и методов должны быть понятны без комментариев.

Вот типичный комментарий на ревью:

«This method violates Single Responsibility Principle. It both validates the input and sends an email. Consider extracting email sending to a separate service.»

Кстати, про безопасность. В Enterprise никогда не пишут:

$query = "SELECT * FROM users WHERE id = " . $_GET['id'];

Только подготовленные выражения через PDO или ORM:

$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $request->get('id')]);

Блоги

Рекомендаций по книгам