Организация заказов в интернет-магазине на PHP

Раздел: Веб-разработка на PHP -> Электронная коммерция

Основные подходы к обработке заказов в PHP

Как создать универсальный обработчик заказов с поддержкой транзакций?

Цель: обеспечить атомарность операций при создании заказа, чтобы избежать частичного сохранения данных. Этот подход применяется в большинстве проектов электронной коммерции.

Пример базового класса OrderHandler:


class OrderHandler {
    private PDO $pdo;
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    public function create(array $data): int {
        $this->pdo->beginTransaction();
        try {
            $stmt = $this->pdo->prepare('INSERT INTO orders (user_id, total, status) VALUES (?, ?, ?)');
            $stmt->execute([$data['user_id'], $data['total'], 'new']);
            $orderId = (int) $this->pdo->lastInsertId();
            $stmt = $this->pdo->prepare('INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)');
            foreach ($data['items'] as $item) {
                $stmt->execute([$orderId, $item['product_id'], $item['quantity'], $item['price']]);
            }
            $this->pdo->commit();
            return $orderId;
        } catch (\Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
    public function updateStatus(int $orderId, string $status): void {
        $stmt = $this->pdo->prepare('UPDATE orders SET status = ? WHERE id = ?');
        $stmt->execute([$status, $orderId]);
    }
}
    

Booking php (система бронирования на php)

Пояснение: методы используют подготовленные запросы для безопасности от SQL-инъекций. Транзакция гарантирует, что вставка заказа и его позиций происходит полностью или откатывается при ошибке.

Типичная ошибка:

Пропуск обработки внешних ключей или блокировок таблиц. При создании заказа в высоконагруженной системе может возникнуть взаимоблокировка. Решение: использовать правильные индексы и уровни изоляции транзакций (например, READ COMMITTED).

Как реализовать обработку заказа через REST API с помощью Slim Framework?

Цель: отделить логику заказов от клиентской части и предоставить унифицированный интерфейс для мобильных приложений и веб-клиентов.

Пример маршрута для создания заказа:


$app->post('/orders', function ($request, $response) {
    $data = $request->getParsedBody();
    $handler = new OrderHandler($this->get(PDO::class));
    try {
        $orderId = $handler->create($data);
        return $response->withJson(['id' => $orderId], 201);
    } catch (\Exception $e) {
        return $response->withJson(['error' => $e->getMessage()], 400);
    }
});
    

Orders php orders action (заказы в php)

Пояснение: создается экземпляр OrderHandler, вызывается метод create. При ошибке возвращается HTTP 400. Для авторизации и валидации данных добавляются middleware.

Типичная ошибка:

Отсутствие проверки прав доступа. Любой неавторизованный пользователь может создать заказ от чужого имени. Решение: добавить middleware аутентификации (JWT, сессии).

Как обрабатывать заказы асинхронно с использованием очередей (RabbitMQ)?

Цель: снизить нагрузку на веб-сервер при создании заказов, особенно если требуется выполнение длительных операций (отправка email, генерация PDF, обновление инвентаря).

Пример продюсера (отправка сообщения в очередь):


$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('order_created', false, true, false, false);
$msg = new AMQPMessage(json_encode($orderData), ['delivery_mode' => 2]);
$channel->basic_publish($msg, '', 'order_created');
echo "Order message sent\n";
$channel->close();
$connection->close();
    

Product view php (просмотр товара php)

Пример консюмера:


$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('order_created', false, true, false, false);
$callback = function ($msg) {
    $order = json_decode($msg->body, true);
    // выполнение обработки: запись в БД, отправка email и т.д.
    echo "Order processed: " . $order['id'] . "\n";
    $msg->ack();
};
$channel->basic_qos(null, 1, null);
$channel->basic_consume('order_created', '', false, false, false, false, $callback);
while ($channel->is_consuming()) {
    $channel->wait();
}
$channel->close();
$connection->close();
    

Catalogue php page (каталог на php)

Пояснение: продюсер отправляет JSON с данными заказа, консюмер обрабатывает его. Параметр delivery_mode=2 делает сообщение persistent.

Типичная ошибка:

Потеря сообщения при падении консюмера. Решение: использовать подтверждения (ack) и dead letter exchanges для обработки неудачных попыток.

Как интегрировать обработку платежей через Stripe с вебхуками?

Цель: получать уведомления об успешных или неуспешных платежах и изменять статус заказа.

Пример обработки вебхука checkout.session.completed:


$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = \Stripe\Webhook::constructEvent(
    $payload, $sig_header, $endpoint_secret
);
if ($event->type === 'checkout.session.completed') {
    $session = $event->data->object;
    $orderId = $session->metadata->order_id;
    $handler = new OrderHandler($pdo);
    $handler->updateStatus($orderId, 'paid');
}
http_response_code(200);
    

Пояснение: верификация подписи обязательна для безопасности. Метаданные заказа передаются при создании сессии оплаты.

Типичная ошибка:

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

Как управлять статусами заказов с помощью конечного автомата (state machine)?

Цель: четко определить допустимые переходы и избежать некорректных изменений статуса (например, из 'cancelled' в 'paid').

Пример с использованием библиотеки PHP State Machine:


$stateMachine = new \StateMachine\StateMachine([
    'new' => ['pay' => 'paid', 'cancel' => 'cancelled'],
    'paid' => ['ship' => 'shipped', 'refund' => 'refunded'],
    'shipped' => ['deliver' => 'delivered'],
    'cancelled' => [],
    'refunded' => [],
]);
$order = ['status' => 'new'];
$transition = 'pay';
if ($stateMachine->can($order['status'], $transition)) {
    $order['status'] = $stateMachine->apply($order['status'], $transition);
    // обновить БД
}
    

Пояснение: подход предотвращает выполнение недопустимых операций и упрощает добавление новых статусов.

Типичная ошибка:

Сложность при интеграции с внешними системами (например, если статус изменяется извне). Решение: все изменения статуса только через state machine, внешние вызовы обрабатывать через API.

Как вести лог действий с заказами для аудита с использованием Monolog?

Цель: иметь возможность отследить, кто и когда изменил заказ, для решения спорных ситуаций и безопасности.

Пример логирования при изменении статуса:


$logger = new Logger('orders');
$logger->pushHandler(new StreamHandler(__DIR__.'/logs/orders.log', Logger::INFO));
$logger->info('Order status changed', [
    'order_id' => $orderId,
    'from' => $oldStatus,
    'to' => $newStatus,
    'user_id' => $currentUserId,
    'ip' => $_SERVER['REMOTE_ADDR']
]);
    

Пояснение: логируем не только событие, но и контекст. Журнал можно анализировать.

Типичная ошибка:

Запись больших объемов данных в лог без ротации. Решение: настроить ротацию логов (например, RotatingFileHandler).

Расширенные примеры кода для обработки заказов

Первый пример: полный класс OrderManager с методами получения заказов по ID, списка заказов пользователя и статистики.

Пример

class OrderManager {
    private PDO $pdo;
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }
    public function getOrder(int $id): ?array {
        $stmt = $this->pdo->prepare('SELECT * FROM orders WHERE id = ?');
        $stmt->execute([$id]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$order) return null;
        $stmt = $this->pdo->prepare('SELECT * FROM order_items WHERE order_id = ?');
        $stmt->execute([$id]);
        $order['items'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
        return $order;
    }
    public function getOrdersByUser(int $userId): array {
        $stmt = $this->pdo->prepare('SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC');
        $stmt->execute([$userId]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    public function getOrderStats(): array {
        $stmt = $this->pdo->query('SELECT status, COUNT(*) as count, SUM(total) as total FROM orders GROUP BY status');
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

Результат вызова getOrder(123):

Array
(
    [id] => 123
    [user_id] => 1
    [total] => 299.99
    [status] => paid
    [created_at] => 2024-03-15 10:30:00
    [items] => Array
        (
            [0] => Array
                (
                    [id] => 1
                    [order_id] => 123
                    [product_id] => 45
                    [quantity] => 2
                    [price] => 150.00
                )
        )
)

Второй пример: обработка вебхука Stripe с проверкой идемпотентности через базу данных.

Пример

$eventId = $event->id;
$stmt = $pdo->prepare('SELECT 1 FROM processed_events WHERE event_id = ?');
$stmt->execute([$eventId]);
if ($stmt->fetch()) {
    http_response_code(200);
    exit; // уже обработано
}
// обработка события
$stmt = $pdo->prepare('INSERT INTO processed_events (event_id, processed_at) VALUES (?, NOW())');
$stmt->execute([$eventId]);
$pdo->commit();

Результат: повторный вызов вебхука не приведет к двойной оплате.

Третий пример: асинхронная обработка с использованием Redis очереди (через list).

Пример

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$orderData = json_encode(['order_id' => 123, 'action' => 'send_confirmation_email']);
$redis->rPush('order_queue', $orderData); // продюсер
// консюмер:
while ($data = $redis->blPop('order_queue', 5)) {
    $process = json_decode($data[1], true);
    // выполнить действие
}

Результат: данные помещаются в очередь, консюмер извлекает и обрабатывает. Пример с использованием Supervisor для обеспечения постоянной работы.

Заказы в PHP - comments

En
Orders php orders action (php)