Ответ
Недавно мне понадобилось создать легковесный 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;
}
}
Главный фокус в чём? Читаем файл потоково, а не загружаем весь в память, а то будет тебе хиросима, если файл на гигабайт. И вставляем пачками по тысяче записей — это ж в разы быстрее, чем каждая строка отдельным запросом. Транзакции, конечно, на каждую пачку, чтобы при любой ошибке не получить базу, где половина данных есть, а половина — хуй с горы.
Вообще, иногда такие голые скрипты — самое то. Для каких-нибудь мелких интеграций или админских утилит на сервере тащить целый фреймворк — это как из пушки по воробьям, ебать мои старые костыли. А тут всё прозрачно, быстро, и доверия к коду — ебать ноль, потому что ты сам его написал и знаешь каждую строчку.