Что последнее ты писал на чистом PHP (без фреймворков)?

Ответ

Недавно мне понадобилось создать легковесный CLI-скрипт для обработки и импорта большого CSV-файла (около 100k записей) в базу данных. Использование чистого PHP без фреймворка было оправдано для максимальной скорости выполнения и минимального потребления памяти.

Задача: Преобразовать данные из CSV, провести валидацию, и пакетно вставить их в MySQL.

Ключевые моменты реализации:

#!/usr/bin/env php
<?php

// 1. Парсинг аргументов командной строки
$options = getopt('f:', ['file:']);
$csvFile = $options['f'] ?? $options['file'] ?? 'php://stdin';

// 2. Подключение к БД с PDO
$pdo = new PDO('mysql:host=localhost;dbname=import_db', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 3. Потоковое чтение CSV файла
$handle = fopen($csvFile, 'r');
if ($handle === false) {
    throw new RuntimeException('Cannot open file: ' . $csvFile);
}

$batchSize = 1000;
$batchData = [];
$header = fgetcsv($handle); // Первая строка - заголовки

// 4. Подготовленный запрос для пакетной вставки
$stmt = $pdo->prepare(
    'INSERT INTO products (sku, name, price, quantity) VALUES (?, ?, ?, ?)'
);

while (($row = fgetcsv($handle)) !== false) {
    // 5. Валидация и преобразование данных
    $sku = trim($row[0]);
    if (empty($sku)) {
        continue; // Пропуск записей без SKU
    }
    $price = (float) str_replace(',', '.', $row[2]);

    $batchData[] = [$sku, $row[1], $price, (int)$row[3]];

    // 6. Пакетная вставка при достижении лимита
    if (count($batchData) >= $batchSize) {
        executeBatch($pdo, $stmt, $batchData);
        $batchData = [];
    }
}

// Вставка оставшихся данных
if (!empty($batchData)) {
    executeBatch($pdo, $stmt, $batchData);
}

fclose($handle);
echo 'Import completed successfully.' . PHP_EOL;

/**
 * Выполняет пакетную вставку данных.
 */
function executeBatch(PDO $pdo, PDOStatement $stmt, array $batch): void
{
    $pdo->beginTransaction();
    try {
        foreach ($batch as $rowData) {
            $stmt->execute($rowData);
        }
        $pdo->commit();
    } catch (PDOException $e) {
        $pdo->rollBack();
        throw $e;
    }
}

Также я периодически пишу на чистом PHP простые REST-эндпоинты для интеграций или утилиты для администрирования сервера, где overhead фреймворка был бы излишним.

Ответ 18+ 🔞

Вот, представляешь, сижу я как-то, и надо мне запилить скрипт, который бы CSV-файл на сто тыщ записей в базу загонял. Фреймворк подключать — это ж овердохуища зависимостей, а тут нужна скорость и чтоб память не жрал как не в себя. Решил на голом PHP, по-старинке, чтоб всё летало.

Задача-то простая: взять CSV, пошаманить с данными, и чтоб всё аккуратненько в MySQL упало. Но ёпта, в деталях всегда собака зарыта.

Смотри, как я это сделал:

#!/usr/bin/env php
<?php

// 1. Парсинг аргументов командной строки
$options = getopt('f:', ['file:']);
$csvFile = $options['f'] ?? $options['file'] ?? 'php://stdin';

// 2. Подключение к БД с PDO
$pdo = new PDO('mysql:host=localhost;dbname=import_db', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 3. Потоковое чтение CSV файла
$handle = fopen($csvFile, 'r');
if ($handle === false) {
    throw new RuntimeException('Cannot open file: ' . $csvFile);
}

$batchSize = 1000;
$batchData = [];
$header = fgetcsv($handle); // Первая строка - заголовки

// 4. Подготовленный запрос для пакетной вставки
$stmt = $pdo->prepare(
    'INSERT INTO products (sku, name, price, quantity) VALUES (?, ?, ?, ?)'
);

while (($row = fgetcsv($handle)) !== false) {
    // 5. Валидация и преобразование данных
    $sku = trim($row[0]);
    if (empty($sku)) {
        continue; // Пропуск записей без SKU
    }
    $price = (float) str_replace(',', '.', $row[2]);

    $batchData[] = [$sku, $row[1], $price, (int)$row[3]];

    // 6. Пакетная вставка при достижении лимита
    if (count($batchData) >= $batchSize) {
        executeBatch($pdo, $stmt, $batchData);
        $batchData = [];
    }
}

// Вставка оставшихся данных
if (!empty($batchData)) {
    executeBatch($pdo, $stmt, $batchData);
}

fclose($handle);
echo 'Import completed successfully.' . PHP_EOL;

/**
 * Выполняет пакетную вставку данных.
 */
function executeBatch(PDO $pdo, PDOStatement $stmt, array $batch): void
{
    $pdo->beginTransaction();
    try {
        foreach ($batch as $rowData) {
            $stmt->execute($rowData);
        }
        $pdo->commit();
    } catch (PDOException $e) {
        $pdo->rollBack();
        throw $e;
    }
}

Главный фокус в чём? Читаем файл потоково, а не загружаем весь в память, а то будет тебе хиросима, если файл на гигабайт. И вставляем пачками по тысяче записей — это ж в разы быстрее, чем каждая строка отдельным запросом. Транзакции, конечно, на каждую пачку, чтобы при любой ошибке не получить базу, где половина данных есть, а половина — хуй с горы.

Вообще, иногда такие голые скрипты — самое то. Для каких-нибудь мелких интеграций или админских утилит на сервере тащить целый фреймворк — это как из пушки по воробьям, ебать мои старые костыли. А тут всё прозрачно, быстро, и доверия к коду — ебать ноль, потому что ты сам его написал и знаешь каждую строчку.