【EC-CUBE4】大量データをCSVダウンロードしたらメモリオーバする時の対処法

EC-CUBE EC-CUBE

約12,000件の会員データを管理画面からCSVダウンロードしようとしたところ、なぜか3,000件ぐらいしかCSVデータが落ちてこない現象が起こりました。
調べてみるとメモリ不足で途中で落ちているとあったので、使えるメモリを増やしてみても結果変わらず。
(ちなみに、ID、氏名、メールアドレスぐらいにCSV出力設定で絞込んでやればダウンロードは可能でした)

同じようなこと(商品CSVダウンロードなども)で悩んでいる方が多いようですが、対処法を書いている方がいない…
それでも諦めずに調べてみると、対処法書いている方がいらっしゃった!

大量データによりCSVのダウンロードメモリオーバー

うわーやっぱりEC-CUBE側か~。
確かにこの設定をローカルでやってみると全件ダウンロード出来た。
ただ、出来るだけEC-CUBE本体は触りたくない。
どこに影響があるかわからないですしね。

ということで、別の対処で逃げることにしました。

<?php
namespace Customize\Controller\Admin\Customer;

use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\ORM\QueryBuilder;
use Eccube\Common\Constant;
use Eccube\Controller\AbstractController;
use Eccube\Entity\Master\CsvType;
use Eccube\Event\EccubeEvents;
use Eccube\Event\EventArgs;
use Eccube\Form\Type\Admin\SearchCustomerType;
use Eccube\Repository\CustomerRepository;
use Eccube\Repository\Master\PageMaxRepository;
use Eccube\Repository\Master\PrefRepository;
use Eccube\Repository\Master\SexRepository;
use Eccube\Service\CsvExportService;
use Eccube\Service\MailService;
use Eccube\Util\FormUtil;
use Knp\Component\Pager\Paginator;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Translation\TranslatorInterface;

class AdminCustomerController extends AbstractController
{
    /**
     * @var CsvExportService
     */
    protected $csvExportService;

    /**
     * @var MailService
     */
    protected $mailService;

    /**
     * @var PrefRepository
     */
    protected $prefRepository;

    /**
     * @var SexRepository
     */
    protected $sexRepository;

    /**
     * @var PageMaxRepository
     */
    protected $pageMaxRepository;

    /**
     * @var CustomerRepository
     */
    protected $customerRepository;

    public function __construct(
        PageMaxRepository $pageMaxRepository,
        CustomerRepository $customerRepository,
        SexRepository $sexRepository,
        PrefRepository $prefRepository,
        MailService $mailService,
        CsvExportService $csvExportService
    ) {
        $this->pageMaxRepository = $pageMaxRepository;
        $this->customerRepository = $customerRepository;
        $this->sexRepository = $sexRepository;
        $this->prefRepository = $prefRepository;
        $this->mailService = $mailService;
        $this->csvExportService = $csvExportService;
    }

    /**
     * 会員CSVの出力(全項目).
     *
     * @Route("/%eccube_admin_route%/customer/custom/export", name="admin_customer_custom_export")
     *
     * @param Request $request
     *
     * @return StreamedResponse
     */
    public function custom_export(Request $request)
    {

        // タイムアウトを無効にする.
        set_time_limit(0);

        // sql loggerを無効にする.
        $em = $this->entityManager;
        $em->getConfiguration()->setSQLLogger(null);

        $response = new StreamedResponse();
        $response->setCallback(function () use ($request) {

            // 会員データ検索用のクエリビルダを取得.
            $qb = $this->csvExportService
                ->getCustomerQueryBuilder($request);

            $Customers = $qb->getQuery()->getResult();

            // ヘッダー用
            $aryLists[] = [
                "会員ID",
                "お名前(姓)",
                "お名前(名)",
                "お名前(セイ)",
                "お名前(メイ)",
                "メールアドレス",
                "会社名",
                "郵便番号",
                "都道府県(ID)",
                "都道府県(名称)",
                "住所1",
                "住所2",
                "TEL",
                "性別(ID)",
                "性別(名称)",
                "職業(ID)",
                "職業(名称)",
                "誕生日",
                "初回購入日",
                "最終購入日",
                "購入回数",
                "ショップ用メモ欄",
                "会員ステータス(ID)",
                "会員ステータス(名称)",
                "登録日",
                "更新日",
            ];

            foreach($Customers as $Customer) {

                $aryLists[] = [
                    $Customer->getId(),
                    $Customer->getName01(),
                    $Customer->getName02(),
                    $Customer->getKana01(),
                    $Customer->getKana02(),
                    $Customer->getEmail(),
                    $Customer->getCompanyName(),
                    $Customer->getPostalCode(),
                    ($Customer->getPref()) ? $Customer->getPref()->getId()   : "",
                    ($Customer->getPref()) ? $Customer->getPref()->getName() : "",
                    $Customer->getAddr01(),
                    $Customer->getAddr02(),
                    $Customer->getPhoneNumber(),
                    ($Customer->getSex()) ? $Customer->getSex()->getId()   : "",
                    ($Customer->getSex()) ? $Customer->getSex()->getName() : "",
                    ($Customer->getJob()) ? $Customer->getJob()->getId()   : "",
                    ($Customer->getJob()) ? $Customer->getJob()->getName() : "",
                    ($Customer->getBirth()) ? $Customer->getBirth()->format('Y-m-d') : "",
                    ($Customer->getFirstBuyDate()) ? $Customer->getFirstBuyDate()->format('Y-m-d H:i:s') : "",
                    ($Customer->getLastBuyDate())  ? $Customer->getLastBuyDate()->format('Y-m-d H:i:s')  : "",
                    $Customer->getBuyTimes(),
                    $Customer->getNote(),
                    ($Customer->getStatus()) ? $Customer->getStatus()->getId() : "",
                    ($Customer->getStatus()) ? $Customer->getStatus()->getName() : "",
                    $Customer->getCreateDate()->format('Y-m-d H:i:s'),
                    $Customer->getUpdateDate()->format('Y-m-d H:i:s'),
                ];
            }

            // fとopenの間は本来くっついていますが、
            // くっつけて記事を投稿するとなぜかエラーになるのでスペース空けています。
            $this->csvExportService->f open();

            foreach ($aryLists as $fields) {

                $this->csvExportService->fputcsv($fields);
            }

            $this->csvExportService->fclose();

        });

        $now = new \DateTime();
        $filename = 'customer_'.$now->format('YmdHis').'.csv';
        $response->headers->set('Content-Type', 'application/octet-stream');
        $response->headers->set('Content-Disposition', 'attachment; filename='.$filename);

        $response->send();
        log_info('会員CSVファイル名', [$filename]);

        return $response;
    }
}
4行目から74行目辺りは
/eccube/src/Eccube/Controller/Admin/Customer/CustomerController.php
をコピーして使ってるだけですので、使用していないものも含まれています。

単純に直接検索したデータをCSVに出力しているだけです。
これだと現状は全件ダウンロード可能になりました。
ただ、この対処法だと管理画面でのCSV出力設定が意味がなくなります。
項目を追加したり、逆に減らしたりする時はわざわざソースを触らないといけなくなります。

EC-CUBEがバージョンアップで対応してくれることを祈ります…

タイトルとURLをコピーしました