@Belib0v
Осторожно, новичок

Экспорт отзывов PHPExcel падает в Timeоut, как оптимизировать?

Занялся рефакторингом экспорта отзывов с помощью библиотеки 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?
  • Вопрос задан
  • 79 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы