Занялся рефакторингом экспорта отзывов с помощью библиотеки PHPExcel, изначально метод экспорта имел такой вид:
public static function createReviewsXls($headers, $values)
{
$count = 1;
/** @var ExcelFile $file */
$file = Yii::createObject([
'class' => 'codemix\excelexport\ExcelFile',
'sheets' => [
Yii::t('app', 'Отчет ') => [
'data' => [],
],
],
'fileOptions' => [
'directory' => Yii::getAlias('@webroot'),
],
]);
/** @var PHPExcel $book */
$column = 1;
$book = $file->getWorkbook();
$sheet = $book->getSheet(0);
foreach ($headers as $header) {
$cell = $sheet->getCell(self::getColumnLabel($column).$count);
$cell->getStyle()->getFont()->setBold(true);
$cell->getStyle()->getFont()->setUnderline(true)->setSize(9)->setName('Arial')->setBold(true);
$cell->setValue($header);
++$column;
}
$count = 2;
foreach ($values as $value) {
$column = 1;
foreach ($value as $item) {
$cell = $sheet->getCell(self::getColumnLabel($column).$count);
$cell->setValue($item);
$cell->getStyle()->getFont()->setUnderline(true)->setSize(9)->setName('Arial')->setBold(true);
++$column;
}
++$count;
}
return $file;
}
protected static function getExcelColumns($numColumns)
{
$columns = [];
for ($i = 0; $i < $numColumns; ++$i) {
$columnName = '';
$num = $i + 1;
while ($num > 0) {
// 26 букв в английском алфавите
$modulo = ($num - 1) % 26;
// 65 буква в ASCII таблице - это буква A
$columnName = chr(65 + $modulo).$columnName;
$num = (int) (($num - $modulo) / 26);
}
$columns[] = $columnName;
}
return $columns;
}
protected static function getColumnLabel($columnNumber)
{
$columnLabel = '';
while ($columnNumber > 0) {
$remainder = ($columnNumber - 1) % 26;
$columnLabel = chr(65 + $remainder).$columnLabel;
$columnNumber = (int) (($columnNumber - $remainder) / 26);
}
return $columnLabel;
}
Убрав всё лишнее я получил такой код:
public static function createReviewsXls($headers, $values) {
/** @var PHPExcel $file */
$file = Yii::createObject([
'class' => 'codemix\excelexport\ExcelFile',
'sheets' => [
Yii::t('app', 'Отчет ') => [
'data' => [],
],
],
'fileOptions' => [
'directory' => Yii::getAlias('@webroot'),
],
]);
$book = $file->getWorkbook();
$sheet = $book->getSheet(0);
$defaultStyle = [
'font' => [
'name' => 'Arial',
'color' => ['rgb' => '000000'],
'size' => 9,
'underline' => true,
'bold' => true,
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_CENTER,
],
];
$book->getDefaultStyle()->applyFromArray($defaultStyle);
$reviews = array_merge([$headers], $values);
$sheet->fromArray($reviews);
return $file;
}
А вот сам экшн:
public function actionExel()
{
$searchModel = new ReviewSearch();
$lang = Yii::$app->user->identity->language ?? Yii::$app->language;
if (RoleHelper::isUserHasRole(Yii::$app->user->id, RoleHelper::ROLE_ADMIN)) {
$dataProvider = $searchModel->search(Yii::$app->request->get('params'));
} else {
$dataProvider = $searchModel->searchForUser(Yii::$app->user->id, Yii::$app->request->get('params'));
}
$totalCount = $dataProvider->query->count();
$batchSize = 50;
$totalBatches = ceil($totalCount / $batchSize);
$headers = [
self::COMPANY => Yii::t('app', 'Компания:'),
self::LOCATION => Yii::t('app', 'Локація:'),
self::FORM => Yii::t('app', 'Форма:'),
self::CSAT_GRADE => Yii::t('app', 'Оцінка (CSAT):'),
self::LIKERT_SCALE => Yii::t('app', 'Лайкерта (CES):'),
self::LOYALTY => Yii::t('app', 'Лояльность (NPS):'),
self::FIVE_STARS => Yii::t('app', 'Пять звезд:'),
self::PHONE => Yii::t('app', 'Телефон:'),
self::NAME => Yii::t('app', 'Имя:'),
self::EMAIL => Yii::t('app', 'Email:'),
self::REVIEW => Yii::t('app', 'Відгук:'),
self::ANSWER => Yii::t('app', 'Відповідь:'),
self::REVIEW_DATE => Yii::t('app', 'Дата відгуку:'),
];
$customHeaders = [];
$values = [];
for ($batch = 0; $batch < $totalBatches; $batch++) {
$offset = $batch * $batchSize;
$currentBatchData = $dataProvider->query
->offset($offset)
->limit($batchSize)
->orderBy(['created_at' => SORT_DESC])
->all();
foreach ($currentBatchData as $key => $data) {
$values[$offset + $key] = [
$headers[self::COMPANY] => $data->mainCompany ? $data->mainCompany->title : '',
$headers[self::LOCATION] => $data->company ? $data->company->title : '',
$headers[self::FORM] => $data->object ? $data->object : '',
$headers[self::CSAT_GRADE] => $data->ratingType ? Yii::t('app', $data->ratingType->title, [], $lang) : '',
$headers[self::LIKERT_SCALE] => $data->type_rating_likert ? Yii::t('app', strval($data->type_rating_likert), [], $lang) : '',
$headers[self::LOYALTY] => $data->type_rating_nps ? Yii::t('app', strval($data->type_rating_nps), [], $lang) : '',
$headers[self::FIVE_STARS] => $data->type_rating_stars ? strval($data->type_rating_stars) : '',
$headers[self::PHONE] => $data->user_phone ? $data->user_phone : Yii::t('app', 'Анонимный'),
$headers[self::NAME] => $data->user_name ? $data->user_name : '',
$headers[self::EMAIL] => $data->user_email ? $data->user_email : '',
$headers[self::REVIEW] => $data->content ? strval($data->content) : '',
$headers[self::ANSWER] => $data->lastResponse ? $data->lastResponse->content : '',
$headers[self::REVIEW_DATE] => Carbon::createFromTimestamp($data->created_at)->toDateTimeString(),
];
foreach ($data->customAnswers as $answer) {
$customQuestions = Translation::find()->andWhere(['pk' => $answer->field_id, 'lang' => Yii::$app->language, 'modelId' => 2])->one();
if (!$customQuestions) {
continue;
}
$val = '';
if ($answer->field) {
$val = BlockFactory::loadClientAnswer($answer, null)->buildAnswer()['answer_value'];
}
if (is_array($val)) {
$val = implode(', ', $val);
}
$val = Yii::t('app', $val, [], $lang);
if (!isset($customHeaders[$customQuestions->translation])) {
$idx = array_key_last($headers) + 1;
$customHeaders[$customQuestions->translation] = $idx;
$headers[$idx] = $customQuestions->translation;
}
$values[$offset + $key][$customHeaders[$customQuestions->translation]] = $val;
$toNull = array_diff($headers, array_keys($values[$offset + $key]));
foreach ($toNull as $nullKey => $nullValue) {
$values[$offset + $key][$nullValue] = '';
}
}
}
}
$values = array_values($values);
$file = ExcelGenerator::createReviewsXls($headers, $values);
$file->createSheets();
$file->getWriter()->save((string) $file->getTmpFile());
Yii::$app->response->sendFile($file->getTmpFile(), Carbon::now().'.xls')->send();
Yii::$app->end();
}
Он все равно падает в таймаут, а локально работает довольно быстро, как лучше оптимизировать код? Или что можно предпринять, чтобы экспорт был быстрее и оно не падало в 504?