Потоковая обработка CSV в Laravel

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

И сервис (API) и фоновый обработчик, оба написаны на Laravel и находятся на разных серверах.

API-сервис получает данные из MySQL и отсылает их в ответ в JSON формате.

Проблема с которой мы столкнулись - объемы данных которые передавались от API. Это могли быть тысячи записей, и PHP падал из-за нехватки памяти. Увеличение memory_limit в php.ini было не самым лучшим решением в данной ситуации.

После изучения вопроса и поиска решения, мы нашли замечательную статью и решили использовать этот метод.

Мы решили изменить формат ответа от API с JSON на CSV, так как поточная обработка CSV намного проще, чем обработка JSON. Все что необходимо сделать - посылать данные построчно.

Так как мы используем Laravel, мы использовали "Laravel стиль" для стриминга CSV. Мы получали данные из базы данных с помощью модели и обрабатывали их в контроллере.

Пример того что у нас получилось:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use App\Models\User;

class StreamController extends Controller
{
  public function get(Request $request)
  {
    $response = new StreamedResponse();
    $fields = ['id', 'name', 'email'];
    $response->setCallback(function () use ($fields, $request) {
      $handle = fopen('php://output', 'w');
    // set CSV header
      fputcsv($handle, $fields);
// fill the data, using chunk by 1000
      User::select($fields)
        ->where($request->all())
        ->chunk(1000, function($users) use (&$handle) {
          $users->each(function($user) use (&$handle) {
            fputcsv($handle, $user->toArray());
          });
        });
    fclose($handle);
    });
    $response->headers->set('Content-Type', 'text/csv');
    $response->setStatusCode(Response::HTTP_OK);
return $response;
  }
}

Для фонового обработчика мы использовали Guzzle для запроса к API:

<?php

$token = 'jwt.token';

// get the stream using Guzzle
$guzzle = new GuzzleHttp\Client([
  'headers' => [
    'Accept' => 'text/csv',
    'Authorization' => 'Bearer '.$token,
  ],
  'stream' => true,
  'timeout' => 0,
  'base_uri' => 'http://api.server',
]);

$response = $guzzle->request('get', '/stream');
$data = $response->getBody()->getContents();

// process the $data
// exportToXlsx($data);

Результат нас вполне устроил. Думаю устроит и вас.

Это перевод статьи за авторством Muhammad Zamroni.

Опубликовано:

Категории: Статьи