Писал ли собственные сервисы для работы с базой данных?

«Писал ли собственные сервисы для работы с базой данных?» — вопрос из категории Базы данных, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, создавал специализированные сервисы для работы с БД в нескольких сценариях:

  1. Сложная бизнес-логика, которую неудобно размещать в репозиториях
  2. Транзакционные операции, затрагивающие несколько сущностей
  3. Оптимизированные запросы, когда ORM генерирует неэффективный SQL
  4. Миграции данных и пакетная обработка

Пример сервиса для работы с финансовыми транзакциями:

// src/Service/TransactionManager.php
class TransactionManager
{
    private EntityManagerInterface $em;
    private Connection $connection;

    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
        $this->connection = $em->getConnection();
    }

    /**
     * Выполняет денежный перевод между счетами с проверкой баланса
     */
    public function transferFunds(int $fromAccountId, int $toAccountId, float $amount): bool
    {
        $this->em->beginTransaction();

        try {
            // Блокируем записи для избежания race condition
            $fromAccount = $this->connection->executeQuery(
                'SELECT * FROM accounts WHERE id = ? FOR UPDATE',
                [$fromAccountId]
            )->fetchAssociative();

            if (!$fromAccount || $fromAccount['balance'] < $amount) {
                throw new InsufficientFundsException();
            }

            // Выполняем перевод одним запросом для атомарности
            $this->connection->executeStatement(
                'UPDATE accounts SET balance = CASE 
                    WHEN id = ? THEN balance - ? 
                    WHEN id = ? THEN balance + ? 
                 END WHERE id IN (?, ?)',
                [$fromAccountId, $amount, $toAccountId, $amount, $fromAccountId, $toAccountId]
            );

            // Создаем запись о транзакции
            $this->createTransactionRecord($fromAccountId, $toAccountId, $amount);

            $this->em->commit();
            return true;

        } catch (Exception $e) {
            $this->em->rollback();
            throw $e;
        }
    }

    private function createTransactionRecord(int $fromId, int $toId, float $amount): void
    {
        $stmt = $this->connection->prepare(
            'INSERT INTO transactions (from_account_id, to_account_id, amount, created_at) 
             VALUES (?, ?, ?, NOW())'
        );
        $stmt->executeStatement([$fromId, $toId, $amount]);
    }
}

Преимущества такого подхода:

  • Инкапсуляция сложной логики в одном месте
  • Контроль транзакций на уровне бизнес-операции
  • Производительность за счет нативных SQL-запросов
  • Тестируемость — сервис можно мокать в unit-тестах