Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 42841b2

Browse files
committed
Create a CSV Response class
This class is almost identical to TextResponse, except that it sets the Content-Type header to text/csv.
1 parent 9a4b4a3 commit 42841b2

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

src/Response/CsvResponse.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-diactoros for the canonical source repository
4+
* @copyright Copyright (c) 2015-2018 Zend Technologies USA Inc. (https://www.zend.com)
5+
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Zend\Diactoros\Response;
11+
12+
use Psr\Http\Message\StreamInterface;
13+
use Zend\Diactoros\Exception;
14+
use Zend\Diactoros\Response;
15+
use Zend\Diactoros\Stream;
16+
17+
use function get_class;
18+
use function gettype;
19+
use function is_object;
20+
use function is_string;
21+
use function sprintf;
22+
23+
/**
24+
* CSV response.
25+
*
26+
* Allows creating a CSV response by passing a string to the constructor;
27+
* by default, sets a status code of 200 and sets the Content-Type header to
28+
* text/csv.
29+
*/
30+
class CsvResponse extends Response
31+
{
32+
use InjectContentTypeTrait;
33+
34+
/**
35+
* Create a CSV response.
36+
*
37+
* Produces a CSV response with a Content-Type of text/csv and a default
38+
* status of 200.
39+
*
40+
* @param string|StreamInterface $text String or stream for the message body.
41+
* @param int $status Integer status code for the response; 200 by default.
42+
* @param array $headers Array of headers to use at initialization.
43+
* @throws Exception\InvalidArgumentException if $text is neither a string or stream.
44+
*/
45+
public function __construct($text, int $status = 200, array $headers = [])
46+
{
47+
parent::__construct(
48+
$this->createBody($text),
49+
$status,
50+
$this->injectContentType('text/csv; charset=utf-8', $headers)
51+
);
52+
}
53+
54+
/**
55+
* Create the CSV message body.
56+
*
57+
* @param string|StreamInterface $text
58+
* @throws Exception\InvalidArgumentException if $text is neither a string or stream.
59+
*/
60+
private function createBody($text) : StreamInterface
61+
{
62+
if ($text instanceof StreamInterface) {
63+
return $text;
64+
}
65+
66+
if (! is_string($text)) {
67+
throw new Exception\InvalidArgumentException(sprintf(
68+
'Invalid CSV content (%s) provided to %s',
69+
(is_object($text) ? get_class($text) : gettype($text)),
70+
__CLASS__
71+
));
72+
}
73+
74+
$body = new Stream('php://temp', 'wb+');
75+
$body->write($text);
76+
$body->rewind();
77+
return $body;
78+
}
79+
}

test/Response/CsvResponseTest.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/**
3+
* @see https://github.com/zendframework/zend-diactoros for the canonical source repository
4+
* @copyright Copyright (c) 2015-2018 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace ZendTest\Diactoros\Response;
11+
12+
use InvalidArgumentException;
13+
use PHPUnit\Framework\TestCase;
14+
use Psr\Http\Message\StreamInterface;
15+
use Zend\Diactoros\Response\CsvResponse;
16+
17+
class CsvResponseTest extends TestCase
18+
{
19+
const VALID_CSV_BODY = <<<EOF
20+
"first","last","email","dob",
21+
"john","citizen","[email protected]","01/01/1970",
22+
EOF;
23+
24+
public function testConstructorAcceptsBodyAsString()
25+
{
26+
$response = new CsvResponse(self::VALID_CSV_BODY);
27+
$this->assertSame(self::VALID_CSV_BODY, (string) $response->getBody());
28+
$this->assertSame(200, $response->getStatusCode());
29+
}
30+
31+
public function testConstructorAllowsPassingStatus()
32+
{
33+
$status = 404;
34+
35+
$response = new CsvResponse(self::VALID_CSV_BODY, $status);
36+
$this->assertSame(404, $response->getStatusCode());
37+
$this->assertSame(self::VALID_CSV_BODY, (string) $response->getBody());
38+
}
39+
40+
public function testConstructorAllowsPassingHeaders()
41+
{
42+
$status = 404;
43+
$headers = [
44+
'x-custom' => [ 'foo-bar' ],
45+
];
46+
47+
$response = new CsvResponse(self::VALID_CSV_BODY, $status, $headers);
48+
$this->assertSame(['foo-bar'], $response->getHeader('x-custom'));
49+
$this->assertSame('text/csv; charset=utf-8', $response->getHeaderLine('content-type'));
50+
$this->assertSame(404, $response->getStatusCode());
51+
$this->assertSame(self::VALID_CSV_BODY, (string) $response->getBody());
52+
}
53+
54+
public function testAllowsStreamsForResponseBody()
55+
{
56+
$stream = $this->prophesize(StreamInterface::class);
57+
$body = $stream->reveal();
58+
$response = new CsvResponse($body);
59+
$this->assertSame($body, $response->getBody());
60+
}
61+
62+
public function invalidContent()
63+
{
64+
return [
65+
'null' => [null],
66+
'true' => [true],
67+
'false' => [false],
68+
'zero' => [0],
69+
'int' => [1],
70+
'zero-float' => [0.0],
71+
'float' => [1.1],
72+
'array' => [['php://temp']],
73+
'object' => [(object) ['php://temp']],
74+
];
75+
}
76+
77+
/**
78+
* @dataProvider invalidContent
79+
*/
80+
public function testRaisesExceptionforNonStringNonStreamBodyContent($body)
81+
{
82+
$this->expectException(InvalidArgumentException::class);
83+
84+
new CsvResponse($body);
85+
}
86+
87+
/**
88+
* @group 115
89+
*/
90+
public function testConstructorRewindsBodyStream()
91+
{
92+
$response = new CsvResponse(self::VALID_CSV_BODY);
93+
94+
$actual = $response->getBody()->getContents();
95+
$this->assertSame(self::VALID_CSV_BODY, $actual);
96+
}
97+
}

0 commit comments

Comments
 (0)