4

我的代码看起来不错,我得到了状态 200,我得到了正确的标题,......但是我创建的 CSV 文件不会下载......

没有错误,所以我不明白为什么它会失败。

这是我的代码:

namespace Rac\CaraBundle\Manager;

/* Imports */
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Validator\ValidatorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpFoundation\StreamedResponse;

/* Local Imports */
use Rac\CaraBundle\Entity\Contact;

/**
 * Class CSV Contact Importer
 */
class CSVContactImporterManager {

    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * @var ValidatorInterface
     */
    private $validator;

    /**
     * @var ContactManager
     */
    private $contactManager;


    /**
     * @param EventDispatcherInterface $eventDispatcher
     * @param ObjectManager            $om
     * @param Contact                  $contactManager
     *
     */
    public function __construct(
    EventDispatcherInterface $eventDispatcher, ObjectManager $om, ValidatorInterface $validator, ContactManager $contactManager
    ) {
        $this->eventDispatcher = $eventDispatcher;
        $this->om = $om;
        $this->validator = $validator;
        $this->contactManager = $contactManager;
    }
    public function getExportToCSVResponse() {
        // get the service container to pass to the closure
        $contactList = $this->contactManager->findAll();
        $response = new StreamedResponse();
        $response->setCallback(
            function () use ($contactList) {
            //Import all contacts
            $handle = fopen('php://output', 'r+');
            // Add a row with the names of the columns for the CSV file
            fputcsv($handle, array('Nom', 'Prénom', 'Société', 'Position', 'Email', 'Adresse', 'Téléphone', 'Téléphone mobile'), "\t");
            $header = array();
            //print_r($contactList);
            foreach ($contactList as $row) {
                fputcsv($handle, array(
                    $row->getFirstName(),
                    $row->getLastName(),
                    $row->getCompany(),
                    $row->getPosition(),
                    $row->getEmail(),
                    $row->getAddress(),
                    $row->getPhone(),
                    $row->getMobile(),
                    ), "\t");
            }
            fclose($handle);
        }
        );
        $response->headers->set('Content-Type', 'application/force-download');
        $response->headers->set('Content-Disposition', 'attachment; filename="export.csv"');

        return $response;
    }

我的控制器:

    use Rac\CaraBundle\Entity\Contact;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Component\HttpFoundation\Request;
    use UCS\Bundle\RichUIBundle\Controller\BaseController;
    use UCS\Bundle\RichUIBundle\Serializer\AbstractListSerializer;
    
    /**
     * Contact BackOffice Environment Controller.
     *
     *
     *
     * @Route("/contact_environment")
     */
    class ContactEnvironmentController extends BaseController{
        /* My code here..*/
    
    
       /**
         * @Route("/export", name="contact_environment_export",options={"expose"=true})
         * @Method("GET")
         *
         * @return type
         */
        public function exort(){
            $manager = $this->get("cara.csv_contact_importer_manager");
           return $manager->getExportToCSVResponse();
    
        

}
}

我的回复标题:

Cache-Control:no-cache, private
Connection:close
Content-Disposition:attachment; filename="export.csv"
Content-Type:application/force-download
4

5 回答 5

2

这是我多次使用的简单实现,实际上是StreamRepsonse按要求使用的。

这是一个新的响应类,扩展StreamResponse并具有类似的签名。还接受$separator$enclosure参数,例如需要使用分号 ( ;) 而不是逗号等。

如果需要创建更大的文件,它会创建 CSVphp://temp以尝试节省内存,并用于stream_get_contents一次检索一点。

class StreamedCsvResponse extends StreamedResponse
{
    private string $filename;

    public function __construct(
        private array $data,
        ?string $filename = null,
        private string $separator = ',',
        private string $enclosure = '"',
        $status = 200,
        $headers = []
    ) {
        if (null === $filename) {
            $filename = uniqid() . '.csv';
        }

        if (!str_ends_with($filename, '.csv')) {
            $filename .= '.csv';
        }

        $this->filename = $filename;
        
        parent::__construct([$this, 'stream'], $status, $headers);
        $this->setHeaders();
    }

    private function setHeaders(): void
    {
        $this->headers->set(
            'Content-disposition',
            HeaderUtils::makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $this->filename)
        );

        if (!$this->headers->has('Content-Type')) {
            $this->headers->set('Content-Type', 'text/csv; charset=UTF-8');
        }

        if (!$this->headers->has('Content-Encoding')) {
            $this->headers->set('Content-Encoding', 'UTF-8');
        }
    }

    public function stream(): void
    {
        $handle = fopen('php://temp', 'r+b');

        $this->encode($this->data, $handle);

        if (!is_resource($handle)) {
            return;
        }

        rewind($handle);

        while ($t = stream_get_contents($handle, 1024)) {
            echo $t;
        }

        fclose($handle);
    }

    private function encode(array $data, $handle): void
    {
        if (!is_resource($handle)) {
            return;
        }

        foreach ($data as $row) {
            fputcsv($handle, $row, $this->separator, $this->enclosure);
        }
    }
}
于 2021-07-21T15:05:56.190 回答
2

这是一个运行良好的 Streamed Symfony 响应。该类创建一个文件以下载其中包含导出的数据。

class ExportManagerService {

    protected $filename;
    protected $repdata;


    public function publishToCSVReportData(){

        $repdata  = $this->repdata;
// array check
        if (is_array($repdata)){

            $response = new StreamedResponse();
            $response->setCallback(
                function () use ($repdata) {
                    $handle = fopen('php://output', 'r+');
                    foreach ($repdata as $row) {

                        $values = $row['values'];
                        $position = $row['position'];

                        $fileData = $this->structureDataInFile($values, $position);
                        fputcsv($handle, $fileData);
                    }
                    fclose($handle);
                }
            );
        } else{
            throw new Exception('The report data to be exported should be an array!');
        }

        $compstring = substr($this->filename,-4);
        if($compstring === '.csv'){
// csv file type check
            $response->headers->set('Content-Type', 'application/force-download');
            $response->headers->set('Content-Disposition', 'attachment; filename='.$this->filename);
        } else { throw new Exception('Incorrect file name!');}


        return $response;

    }

    public function structureDataInFile(array $values, $position){

        switch ($position){
            case 'TopMain':
                for ($i = 0; $i < 4; $i++){
                    array_unshift($values, ' ');
                }
                return $values;
                break;
            case 'Top':
                $space = array(' ', ' ', ' ');
                array_splice($values,1,0,$space);
                return $values;
                break;
            case 'TopFirst':
                for ($i = 0; $i < 1; $i++){
                    array_unshift($values, ' ');
                }
                $space = array(' ', ' ');
                array_splice($values,2,0,$space);
                return $values;
                break;
            case 'TopSecond':
                for ($i = 0; $i < 2; $i++){
                    array_unshift($values, ' ');
                }
                $space = array(' ');
                array_splice($values,3,0,$space);
                return $values;
                break;
            case 'TopThird':
                for ($i = 0; $i < 3; $i++){
                    array_unshift($values, ' ');
                }
                return $values;
                break;
            default:
                return $values;
        }
    }

    /*
    * @var array
    */
    public function setRepdata($repdata){
        $this->repdata = $repdata;
    }

    /*
    * @var string
    */
    public function setFilename($filename){
        $this->filename = $filename;
    }
}
于 2015-08-24T14:56:08.913 回答
1

这是作者要求的基于响应的解决方案。在此设计中,csv 服务仅返回 csv 文本。响应在控制器中生成。

csv 生成器:

class ScheduleGameUtilDumpCSV
{
public function getFileExtension() { return 'csv'; }
public function getContentType()   { return 'text/csv'; }

public function dump($games)
{
    $fp = fopen('php://temp','r+');

    // Header
    $row = array(
        "Game","Date","DOW","Time","Venue","Field",
        "Group","HT Slot","AT Slot",
        "Home Team Name",'Away Team Name',
    );
    fputcsv($fp,$row);

    // Games is passed in
    foreach($games as $game)
    {
        // Date/Time
        $dt   = $game->getDtBeg();
        $dow  = $dt->format('D');
        $date = $dt->format('m/d/Y');
        $time = $dt->format('g:i A');

        // Build up row
        $row = array();
        $row[] = $game->getNum();
        $row[] = $date;
        $row[] = $dow;
        $row[] = $time;
        $row[] = $game->getVenueName();
        $row[] = $game->getFieldName();

        $row[] = $game->getGroupKey();

        $row[] = $game->getHomeTeam()->getGroupSlot();
        $row[] = $game->getAwayTeam()->getGroupSlot();
        $row[] = $game->getHomeTeam()->getName();
        $row[] = $game->getAwayTeam()->getName();

        fputcsv($fp,$row);
    }
    // Return the content
    rewind($fp);
    $csv = stream_get_contents($fp);
    fclose($fp);
    return $csv;
}

控制器:

public function renderResponse(Request $request)
{   
    // Model is passed via request
    $model = $request->attributes->get('model');
    $games = $model->loadGames();

    // The csv service
    $dumper = $this->get('csv_dumper_service');

    // Response with content
    $response = new Response($dumper->dump($games);

    // file prefix was injected
    $outFileName = $this->prefix . date('Ymd-Hi') . '.' . $dumper->getFileExtension();

    $response->headers->set('Content-Type', $dumper->getContentType());
    $response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"',$outFileName));

    return $response;
}
于 2014-12-10T03:12:04.937 回答
1

这是一个较短的:

/**
 * Class CsvResponse
 */
class CsvResponse extends StreamedResponse
{
    /**
     * CsvResponse constructor.
     *
     * @param array  $rows
     * @param string $fileName
     */
    public function __construct(array $rows, $fileName)
    {
        parent::__construct(
            function () use ($rows) {
                $this->convertArrayToCsv($rows);
            },
            self::HTTP_OK,
            [
                'Content-Disposition' => sprintf('attachment; filename="%s"', $fileName),
                'Content-Type' => 'text/csv',
            ]
        );
    }

    /**
     * @param array $rows
     *
     */
    private function convertArrayToCsv(array $rows)
    {
        $tempFile = fopen('php://output', 'r+b');
        foreach ($rows as $row) {
            fputcsv($tempFile, $row);
        }
        fclose($tempFile);
    }
}
于 2017-01-24T09:54:39.830 回答
0

如果您不使用迭代器进行数据库查询,则通过有限或无限查找所有数据学说或其他 ORM 工具。

如果您想流式传输此大数据(假设),则在等待它结束此查找所有查询之前。这可能需要很长时间并且可能会超时。

解决方案:在流响应中查询迭代器;)

注意:我使用 Symfony Serializer 的 CSV 格式

例子:

public function export(): Response
{
    $query = $this->getQuery(); // Doctrine query

    $serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);

    $response = new StreamedResponse();

    $response->setCallback(function () use ($serializer, $query) {
        $data = $query->toIterable(); // iterate query, not find all, one by one

        $csv = fopen('php://output', 'wb+');

        $headTitle = array_keys($data->current()->toArray());

        $serializer->encode(
            $headTitle,
            CsvEncoder::FORMAT
        );

        fputcsv($csv, $headTitle, ';');

        while (null !== $data->current()) {
            $line = $data->current()->toArray(); // object to array convert on iterate

            $serializer->encode($line, CsvEncoder::FORMAT);

            fputcsv($csv, $line, ';');

            $data->next();
        }

        fclose($csv);
    });

    $response->headers->set('Content-Type', 'text/csv; charset=utf-8; application/octet-stream');
    $response->headers->set('Content-Disposition', 'attachment; filename="example.csv"');

    return $response;
}

此解决方案直接启动下载文件并在下载时流式传输。像这样,您可以轻松下载您的小数据或大数据。

于 2022-01-12T09:13:01.180 回答