Импорт прайс-листа из Excel: PHPExcel, проверка шапки и обновление цен
Задача
Старая заметка с pushorigin.ru показывала разбор XLS: читать лист, сверить «шапку», пройти строки с номенклатурой и обновить цены и остатки в таблице shop_items. Ниже — тот же каркас, переписанный для реальной эксплуатации рядом с «1С‑Битрикс»: аккуратная работа с формулами в ячейках, валидация строки и отказ от небезопасной сборки SQL через addslashes.
PHPExcel и преемник
В примерах эпохи PHP 5 часто стоит PHPExcel. В новых проектах разумнее перейти на PhpSpreadsheet (наследник), оставив ту же идею: IOFactory::load, активный лист, чтение значений. Для совместимости с «тяжёлыми» книгами имеет смысл setReadDataOnly(true), если не нужны стили.
Проверка формата
Импорт должен обрываться на «чужом» файле. В оригинале контрольная ячейка — A4 с текстом вроде Номенклатура, Ед. изм.. Такой маркер дешевле, чем разбирать все варианты заголовков поставщика, но его стоит вынести в константу или конфиг, а не дублировать по коду.
Парсинг строк
Цикл начинается с фиксированной строки (в источнике — с 8-й): имя в колонке A, артикул в E, опция в F, цена в G, штрихкод в H, остаток в I. Строка считается годной, если заполнены имя, SKU и цена — иначе её лучше пропустить и залогировать, чем портить данные.
Если в ячейке лежит формула, текст из getValue() начинается с =. Для выгрузки поставщика обычно нужно то, что Excel уже пересчитал: в PHPExcel это getOldCalculatedValue(), в PhpSpreadsheet — актуальный пересчёт через getCalculatedValue() (или сохранённое значение, если пересчёт недоступен).
Класс загрузчика (скелет)
Ниже — структура без привязки к автозагрузчику; пути к библиотеке подключите сами. Исправлена синтаксическая ошибка из исходника: между элементами массива обязательна запятая после price.
final class PriceImporter
{
private $sheet;
public function import(string $file): int|false
{
$this->loadXls($file);
if (!$this->isValidFormat()) {
return false;
}
return $this->processWorkbook();
}
private function loadXls(string $fileName): void
{
$type = \PHPExcel_IOFactory::identify($fileName);
$reader = \PHPExcel_IOFactory::createReader($type);
$reader->setReadDataOnly(true);
$book = $reader->load($fileName);
$book->setActiveSheetIndex(0);
$this->sheet = $book->getActiveSheet();
}
private function isValidFormat(): bool
{
return trim((string) $this->cell('A4')) === 'Номенклатура, Ед. изм.';
}
private function processWorkbook(): int
{
$line = 8;
$done = 0;
while (true) {
$row = $this->readRow($line);
if ($row === false) {
break;
}
if ($row['ok']) {
$item = new ShopItem($row, /* соединение с БД */);
$done += $item->store();
}
$line++;
}
return $done;
}
/** @return array{ok:bool,...}|false */
private function readRow(int $n)
{
if ($n > (int) $this->sheet->getHighestRow()) {
return false;
}
$name = $this->cell('A' . $n);
$sku = $this->cell('E' . $n);
$price = $this->cell('G' . $n);
return [
'ok' => ($name !== '' && $sku !== '' && $price !== ''),
'name' => $name,
'sku' => $sku,
'option' => $this->cell('F' . $n),
'price' => $price,
'bar' => $this->cell('H' . $n),
'balance' => $this->cell('I' . $n),
];
}
private function cell(string $coord): string
{
$c = $this->sheet->getCell($coord);
$v = $c->getValue();
if (is_string($v) && $v !== '' && $v[0] === '=') {
$v = $c->getOldCalculatedValue();
}
return trim((string) $v);
}
}Слой ShopItem: только обновление существующих
Исходная логика не создавала новые позиции — только находила id по SKU и обновляла прайс и остаток. Такой режим типичен, когда справочник номенклатуры заводится вручную, а Excel прилетает из учётной системы. Если нужно вставлять отсутствующие SKU, расширяют ветку «не найден» отдельным сценарием с валидацией и логированием.
Для «1С‑Битрикс» удобнее выполнять параметризованные запросы через \Bitrix\Main\Application::getConnection() и плейсхолдеры (или хотя бы SqlHelper::forSql), чем конкатенировать addslashes. Числовой id после выборки приводите к (int).
Где держать файл
В продакшене загрузку связывают с /upload/, проверкой расширения и размера, а запуск по cron — с тем же скриптом через пролог ядра (prolog_before.php), чтобы использовать те же константы подключения к БД, что и сайт.
Кратко
- Проверяйте «свой» Excel маркером в шапке перед массовым UPDATE.
- Для формул в ячейках берите вычисленное значение, а не строку с
=. - Исправляйте синтаксис массивов и разделяйте загрузку, валидацию и запись по классам.
- В Bitrix используйте слой БД ядра и безопасное экранирование вместо устаревшего слепого
addslashesв SQL.
Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии