diff --git a/src/Response.php b/src/Response.php index fbe5158..9141995 100644 --- a/src/Response.php +++ b/src/Response.php @@ -3,6 +3,7 @@ namespace RREST; use League\JsonGuard; +use RREST\Response\FileConfiguration; use RREST\Validator\JsonValidator; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\XmlEncoder; @@ -18,6 +19,11 @@ class Response */ protected $content; + /** + * @var FileConfiguration + */ + protected $fileConfiguration; + /** * @var string */ @@ -62,6 +68,11 @@ public function __construct(RouterInterface $router, $format, $statusCode) $this->setStatusCode($statusCode); } + public function setFileConfiguration(FileConfiguration $fileConfiguration) + { + $this->fileConfiguration = $fileConfiguration; + } + /** * @return string */ @@ -220,6 +231,19 @@ public function getRouter() */ public function getRouterResponse($autoSerializeContent = true) { + if ($this->hasFileConfiguration()) { + $headers = array_merge( + $this->getConfiguredHeaders(), + $this->fileConfiguration->getHttpHeadersForFileResponse() + ); + return $this->router->getBinaryFileResponse( + $this->fileConfiguration->getFilePath(), + $this->getConfiguredHeaderstatusCode(), + $headers, + $this->fileConfiguration->getDeleteFileAfterSend() + ); + } + $content = $this->getContent(); if ($autoSerializeContent) { $content = $this->serialize($content, $this->getFormat()); @@ -230,6 +254,11 @@ public function getRouterResponse($autoSerializeContent = true) ); } + private function hasFileConfiguration(): bool + { + return !empty($this->fileConfiguration); + } + /** * @param mixed $data * @param string $format diff --git a/src/Response/FileConfiguration.php b/src/Response/FileConfiguration.php new file mode 100644 index 0000000..ba847a6 --- /dev/null +++ b/src/Response/FileConfiguration.php @@ -0,0 +1,77 @@ +buildHeaderContentDisposition(pathinfo($filePath, PATHINFO_BASENAME)); + $this->headerContentLength = filesize($filePath); + $this->deleteFileAfterSend = $deleteFileAfterSend; + $this->filePath = $filePath; + } + + private function buildHeaderContentDisposition(string $filename) + { + $symfonyResponse = new Response(); + $this->headerContentDisposition = $symfonyResponse->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + $filename + ); + } + + public function setFile(string $filePath) + { + if (!is_readable($filePath)) { + throw new \RuntimeException('Provided file is not readable or doesn\'t exists.'); + } + if (!is_file($filePath)) { + throw new \RuntimeException('Provided file is not a file'); + } + $this->filePath = $filePath; + } + + public function getFilePath(): string + { + return $this->filePath; + } + + public function getDeleteFileAfterSend(): bool + { + return $this->deleteFileAfterSend; + } + + public function getHttpHeadersForFileResponse(): array + { + return [ + 'Content-disposition' => $this->headerContentDisposition, + 'Content-Length' => $this->headerContentLength, + ]; + } +} diff --git a/src/Router/RouterInterface.php b/src/Router/RouterInterface.php index 6fe6511..a1a084f 100644 --- a/src/Router/RouterInterface.php +++ b/src/Router/RouterInterface.php @@ -59,4 +59,15 @@ public function setPayloadBodyValue($payloadBodyJSON); * @return mixed */ public function getResponse($content = '', $statusCode = 200, $headers = array()); + + /** + * The router response to serve a file to be downloaded. + * + * @param string $filepath + * @param int $statusCode + * @param string[] $headers + * + * @return mixed + */ + public function getBinaryFileResponse(string $filePath, $statusCode = 200, $headers = array(), bool $deleteFileAfterSend = false); } diff --git a/src/Router/Silex.php b/src/Router/Silex.php index c71a86a..6cc14a3 100644 --- a/src/Router/Silex.php +++ b/src/Router/Silex.php @@ -2,6 +2,7 @@ namespace RREST\Router; +use Symfony\Component\HttpFoundation\BinaryFileResponse as HttpFoundationBinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse; use RREST\Response; @@ -115,4 +116,14 @@ public function getResponse($content = '', $statusCode = 200, $headers = array() { return new HttpFoundationResponse($content, $statusCode, $headers); } + + /** + * {@inheritdoc} + */ + public function getBinaryFileResponse(string $filePath, $statusCode = 200, $headers = array(), bool $deleteFileAfterSend = false) + { + $response = new HttpFoundationBinaryFileResponse($filePath, $statusCode, $headers); + $response->deleteFileAfterSend($deleteFileAfterSend); + return $response; + } } diff --git a/tests/units/Response.php b/tests/units/Response.php index 7fec540..a9080aa 100644 --- a/tests/units/Response.php +++ b/tests/units/Response.php @@ -5,6 +5,7 @@ require_once __DIR__.'/boostrap.php'; use atoum; +use RREST\Response\FileConfiguration; use RREST\Router\Silex; use Silex\Application; @@ -48,6 +49,15 @@ function () { ; } + public function setFileConfiguration() + { + $this + ->given($this->testedInstance) + ->and($this->testedInstance->setFileConfiguration(new FileConfiguration('xxx'))) + ->object($this->testedInstance) + ->isInstanceOf('RREST\Response'); + } + public function testGetConfiguredHeaders() { $this->newTestedInstance($this->router, 'json', 200); @@ -214,5 +224,12 @@ public function testGetRouterResponse() ->string($this->testedInstance->getRouterResponse()->headers->get('Location')) ->isEqualTo('https://api.domain.com/items/uuid') ; + + $this->testedInstance->setFileConfiguration(new FileConfiguration(__DIR__.'/../fixture/song.xml')); + $this + ->given($this->testedInstance) + ->string($this->testedInstance->getRouterResponse()->headers->get('Content-disposition')) + ->isEqualTo('attachment; filename="song.xml"') + ; } } diff --git a/tests/units/Router/Silex.php b/tests/units/Router/Silex.php index 152769e..15b7a5b 100644 --- a/tests/units/Router/Silex.php +++ b/tests/units/Router/Silex.php @@ -54,6 +54,15 @@ public function testGetResponse() ->isInstanceOf('Symfony\Component\HttpFoundation\Response'); } + public function testGetBinaryFileResponse() + { + $this->newTestedInstance($this->app); + $this + ->given($this->testedInstance) + ->object($this->testedInstance->getBinaryFileResponse(__DIR__.'/../../fixture/song.xml', true)) + ->isInstanceOf('Symfony\Component\HttpFoundation\BinaryFileResponse'); + } + public function testGetPayloadBodyValue() { $this->newTestedInstance($this->app);