Basic CSV Output in Zend Framework 2

Creating a ZF2Skeleton Application and running it with PHP's built-in webserver

Synopsis

Today’s tutorial is a simple one. We’re going to look at a simple way of rendering CSV output in Zend Framework 2 using a combination of a View Template and Controller Action. We’re going to see just how easy it is to generate content and send it to the browser, instead of rendering a standard .pthml template.

Time Required: 20 Minutes

Difficulty: Basic

The Module Configuration

Ok, in the module config.php file update it with the following configuration:

'view_manager' => array(
    'template_map' => array(
        'download/download-csv' => 
            __DIR__ . 
            '/../view/application/download/download-csv.phtml',
    ),
),

Here, we’ve provided an alias, ‘download/download-csv‘, to a template which we’re about to create in our module view directory. Nothing too complex, so we’ll skip on to the actual view template itself.

The View Template

In your module, create a new template, download-csv.phtml, in a new directory ‘download‘, in the view directory for your module. In our case, it will be ‘/../view/application/download/‘. Then, open the file and add the following code, which we’ll work through.

<?php    
if (count($results) > 0) {
    $fh = @fopen( 'php://output', 'w' );
    foreach ($results as $result) {
        fputcsv($fh, $result);
    }
    fclose($fh);
}

What we’ve done here is use fputcsv to convert each record in $results to a CSV row then use PHP streams to store the CSV data in an output buffer. Like thoughtful developers, when we’ve finished iterating, we close the file handle on the stream we opened at the start.

The Controller Action

Finally, here we have the definition of the controller action. Add it to the controller in your module. Then let’s work through what’s going on.

protected function csvAction($filename, $resultset)
{
    $view = new ViewModel();
    $view->setTemplate('download/download-csv')
         ->setVariable('results', $resultset)
         ->setTerminal(true);

    if (!empty($columnHeaders)) {
        $view->setVariable(

            'columnHeaders', $columnHeaders
        );
    }

    $output = $this->getServiceLocator()
                   ->get('viewrenderer')
                   ->render($view);

    $response = $this->getResponse();

    $headers = $response->getHeaders();
    $headers->addHeaderLine('Content-Type', 'text/csv')
            ->addHeaderLine(

                'Content-Disposition', 
                sprintf("attachment; filename=\"%s\"", $filename)
            )
            ->addHeaderLine('Accept-Ranges', 'bytes')
            ->addHeaderLine('Content-Length', strlen($output));

    $response->setContent($output);

    return $response;
}

What we’ve done is to explicitly initialise a ViewModel object, instead of the implicit way, which normally occurs. Then, we’ve specified the template, and set $results as a template variable, followed by a call to ViewModel’s setTerminal method.

If you’re not familiar with this method, it changes the normal view rendering procedure. Normally the view model returned from a controller action would be injected into the layout view model. When setTerminal is called, the returned View Model replaces the layout view model.

It’s really handy for console output, modal dialogs and other situations where we just want the content at the action level and nothing else.

Note: If you’re familiar with Zend Framework 1, then you would have likely called the following two functions to achieve the same effect:

$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender(TRUE);

Then, we use the ServiceLocator to get direct access to the ViewRender object and retrieve the content of the rendered template directly into $output. Finally, we manipulate the headers which are sent to the browser to stipulate we’re sending a CSV file as a downloadable file.

The headers (with definition) are:

Content Header Definition
Content-Type Indicates the media type of the entity-body sent to the recipient
Content-Disposition A means for the origin server to suggest a default filename if the user requests that the content is saved to a file
Accept-Ranges Allows the server to indicate its acceptance of range requests for a resource
Content-Length Indicates the size of the entity-body, in decimal number of OCTETs, sent to the recipient

After we’ve done this, we set the body of the response to be the CSV content which we generated in the view and return the response object. The net result of doing this, is that a HTML view won’t be rendered. Instead, a CSV file will be downloaded via the browser.

Conclusion

Today we’ve looked at a simple way to generate CSV output in our controller actions. It takes a really simple path, looking directly at the components involved, without any special wrapper or helper functionality.

We could have gone in a number of directions, including creating and registering a custom rendering strategy. But it’s important to understand what the fundamentals are first, before progressing further. In next week’s tutorial, I’ll be creating a custom render strategy for CSV files (along with including the code on the Malt Blue Github repository.

Has this helped expand the types of content your application can produce? Do you know of a module which already does this? Either way, tell me in the comments.

Beginner Tutorial