From 201d97224f465301f06f833908de2c147ad4deb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Felix=20=C5=A0ulc?= Date: Wed, 15 Mar 2017 07:54:42 +0100 Subject: [PATCH] Added FlyResponses [closes #5] --- .docs/README.md | 138 ++++++++++++++++++ README.md | 3 +- src/Response/Fly/Adapter/Adapter.php | 18 +++ src/Response/Fly/Adapter/CallbackAdapter.php | 32 ++++ src/Response/Fly/Adapter/ProcessAdapter.php | 55 +++++++ src/Response/Fly/Adapter/StdoutAdapter.php | 40 +++++ src/Response/Fly/Buffer/Buffer.php | 32 ++++ src/Response/Fly/Buffer/FileBuffer.php | 69 +++++++++ src/Response/Fly/Buffer/ProcessBuffer.php | 71 +++++++++ src/Response/Fly/FlyFileResponse.php | 76 ++++++++++ src/Response/Fly/FlyResponse.php | 34 +++++ .../cases/Response/Fly/Buffer/FileBuffer.phpt | 41 ++++++ .../Response/Fly/Buffer/ProcessBuffer.phpt | 17 +++ 13 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 src/Response/Fly/Adapter/Adapter.php create mode 100644 src/Response/Fly/Adapter/CallbackAdapter.php create mode 100644 src/Response/Fly/Adapter/ProcessAdapter.php create mode 100644 src/Response/Fly/Adapter/StdoutAdapter.php create mode 100644 src/Response/Fly/Buffer/Buffer.php create mode 100644 src/Response/Fly/Buffer/FileBuffer.php create mode 100644 src/Response/Fly/Buffer/ProcessBuffer.php create mode 100644 src/Response/Fly/FlyFileResponse.php create mode 100644 src/Response/Fly/FlyResponse.php create mode 100644 tests/cases/Response/Fly/Buffer/FileBuffer.phpt create mode 100644 tests/cases/Response/Fly/Buffer/ProcessBuffer.phpt diff --git a/.docs/README.md b/.docs/README.md index bdd5cb5..e7a51ea 100644 --- a/.docs/README.md +++ b/.docs/README.md @@ -3,6 +3,7 @@ ## Content - [LinkGenerator (LinkGeneratorExtesion)](#link-generator) +- [FlyResponse - send file/buffer on-the-fly](#flyresponse) ## Link Generator @@ -13,3 +14,140 @@ URL addreses / links out of presenter scope. For example in mail templates. extensions: link: Contributte\Application\DI\LinkGeneratorExtesion ``` + +## FlyResponse + +### FlyResponse + +For common purpose and your custom solutions. + +### FlyFileResponse + +Special response for handling files on-the-fly. + +## Adapters + +### ProcessAdapter + +Execute command over [popen](http://php.net/manual/en/function.popen.php). + +```php +use Contributte\Application\Response\Fly\Adapter\ProcessAdapter; +use Contributte\Application\Response\Fly\FlyFileResponse; + +// Compress current folder and send to response +$adapter = new ProcessAdapter('tar cf - ./ | gzip -c -f'); +$response = new FlyFileResponse($adapter, 'folder.tgz'); + +$this->sendResponse($response); +``` + +### StdoutAdapter + +Write to `php://output`. + +```php +use Contributte\Application\Response\Fly\Adapter\StdoutAdapter; +use Contributte\Application\Response\Fly\Buffer\Buffer; +use Contributte\Application\Response\Fly\FlyFileResponse; +use Nette\Http\IRequest; +use Nette\Http\IResponse; + +// Write to stdout over buffer class +$adapter = new StdoutAdapter(function(Buffer $buffer, IRequest $request, IResponse $response) { + // Modify headers + $response->setHeader(..); + + // Write data + $buffer->write('Some data..'); +}); +$response = new FlyFileResponse($adapter, 'my.data'); + +$this->sendResponse($response); +``` + +### CallbackAdapter + +```php +use Contributte\Application\Response\Fly\Adapter\CallbackAdapter; +use Contributte\Application\Response\Fly\Buffer\Buffer; +use Contributte\Application\Response\Fly\FlyFileResponse; +use Nette\Http\IRequest; +use Nette\Http\IResponse; + +$adapter = new CallbackAdapter(function(IRequest $request, IResponse $response) use ($model) { + // Modify headers + $response->setHeader(..); + + // Fetch topsecret data + $data = $this->facade->getData(); + foreach ($data as $d) { + // Write or print data.. + } +}); +$response = new FlyFileResponse($adapter, 'my.data'); + +$this->sendResponse($response); +``` + +## Model + +```php +final class BigOperationHandler +{ + + /** @var Facade */ + private $facade; + + /** + * @param Facade $facade + */ + public function __construct(Facade $facade) + { + $this->facade = $facade; + } + + public function toFlyResponse() + { + $adapter = new CallbackAdapter(function (IRequest $request, IResponse $response) { + // Modify headers + $response->setHeader(..); + + // Fetch topsecret data + $data = $this->facade->getData(); + foreach ($data as $d) { + // Write or print data.. + } + }); + + return new FlyFileResponse($adapter, 'file.ext'); + + // or + return new FlyResponse($adapter); + } +} + +interface IBigOperationHandlerFactory +{ + + /** + * @return BigOperationHandler + */ + public function create(); + +} + +final class MyPresenter extends Nette\Application\UI\Presenter +{ + + /** @var IBigOperationHandlerFactory @inject */ + public $bigOperationHandlerFactory; + + public function handleMagic() + { + $this->sendResponse( + $this->bigOperationHandlerFactory->create()->toFlyResponse() + ); + } +} +``` diff --git a/README.md b/README.md index 2d5cb8c..91c45d8 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,12 @@ composer require contributte/application | State | Version | Branch | PHP | |-------------|---------|----------|----------| -| development | `^0.1` | `master` | `>= 5.6` | +| development | `^0.2` | `master` | `>= 5.6` | ## Overview - [LinkGenerator (LinkGeneratorExtension)](https://github.com/contributte/application/blob/master/.docs/README.md#link-generator) +- [FlyResponse - send file/buffer on-the-fly](https://github.com/contributte/application/blob/master/.docs/README.md#flyresponse) --- diff --git a/src/Response/Fly/Adapter/Adapter.php b/src/Response/Fly/Adapter/Adapter.php new file mode 100644 index 0000000..f2847a9 --- /dev/null +++ b/src/Response/Fly/Adapter/Adapter.php @@ -0,0 +1,18 @@ +callback = $callback; + } + + /** + * @param IRequest $request + * @param IResponse $response + * @return void + */ + public function send(IRequest $request, IResponse $response) + { + call_user_func_array($this->callback, [$request, $response]); + } + +} diff --git a/src/Response/Fly/Adapter/ProcessAdapter.php b/src/Response/Fly/Adapter/ProcessAdapter.php new file mode 100644 index 0000000..6069cc1 --- /dev/null +++ b/src/Response/Fly/Adapter/ProcessAdapter.php @@ -0,0 +1,55 @@ +command = $command; + $this->mode = $mode; + $this->buffersize = $buffersize; + } + + /** + * @param IRequest $request + * @param IResponse $response + * @return void + */ + public function send(IRequest $request, IResponse $response) + { + // Open file Buffer + $b = new ProcessBuffer($this->command, $this->mode); + + while (!$b->eof()) { + // Read from Buffer + $output = $b->read($this->buffersize); + + // Goes to ouput + echo $output; + } + + // Close file Buffer + $b->close(); + } + +} diff --git a/src/Response/Fly/Adapter/StdoutAdapter.php b/src/Response/Fly/Adapter/StdoutAdapter.php new file mode 100644 index 0000000..88684c6 --- /dev/null +++ b/src/Response/Fly/Adapter/StdoutAdapter.php @@ -0,0 +1,40 @@ +callback = $callback; + } + + /** + * @param IRequest $request + * @param IResponse $response + * @return void + */ + public function send(IRequest $request, IResponse $response) + { + // Open file pointer + $b = new FileBuffer('php://output', 'w'); + + // Fire callback with Buffer, request and response + call_user_func_array($this->callback, [$b, $request, $response]); + + // Close resource + $b->close(); + } + +} diff --git a/src/Response/Fly/Buffer/Buffer.php b/src/Response/Fly/Buffer/Buffer.php new file mode 100644 index 0000000..2373ba4 --- /dev/null +++ b/src/Response/Fly/Buffer/Buffer.php @@ -0,0 +1,32 @@ +pointer = fopen($file, $mode); + } + + /** + * Close and clean + */ + public function __destruct() + { + $this->close(); + } + + /** + * @param mixed $data + * @return void + */ + public function write($data) + { + fwrite($this->pointer, $data); + } + + /** + * @param int $size + * @return mixed + */ + public function read($size) + { + return fread($this->pointer, $size); + } + + /** + * @return bool + */ + public function eof() + { + return feof($this->pointer); + } + + /** + * @return bool + */ + public function close() + { + if (isset($this->pointer) && $this->pointer) { + $res = fclose($this->pointer); + unset($this->pointer); + + return $res; + } + + return 0; + } + +} diff --git a/src/Response/Fly/Buffer/ProcessBuffer.php b/src/Response/Fly/Buffer/ProcessBuffer.php new file mode 100644 index 0000000..36fc76a --- /dev/null +++ b/src/Response/Fly/Buffer/ProcessBuffer.php @@ -0,0 +1,71 @@ +pointer = popen($command, $mode); + } + + /** + * Close and clean + */ + public function __destruct() + { + $this->close(); + } + + /** + * @param mixed $data + * @return void + */ + public function write($data) + { + throw new Exception('Not implemented...'); + } + + /** + * @param int $size + * @return mixed + */ + public function read($size) + { + return fread($this->pointer, $size); + } + + /** + * @return bool + */ + public function eof() + { + return feof($this->pointer); + } + + /** + * @return int + */ + public function close() + { + if (isset($this->pointer) && $this->pointer) { + $res = fclose($this->pointer); + unset($this->pointer); + + return $res; + } + + return 0; + } + +} diff --git a/src/Response/Fly/FlyFileResponse.php b/src/Response/Fly/FlyFileResponse.php new file mode 100644 index 0000000..37c50c1 --- /dev/null +++ b/src/Response/Fly/FlyFileResponse.php @@ -0,0 +1,76 @@ +filename = $filename; + } + + /** + * @param string $contentType + * @return void + */ + public function setContentType($contentType) + { + $this->contentType = $contentType; + } + + /** + * @param string $filename + * @return void + */ + public function setFilename($filename) + { + $this->filename = $filename; + } + + /** + * @param boolean $force + * @return void + */ + public function setForceDownload($force = TRUE) + { + $this->forceDownload = $force; + } + + /** + * @param IRequest $request + * @param IResponse $response + * @return void + */ + public function send(IRequest $request, IResponse $response) + { + $response->setContentType($this->contentType); + $response->setHeader( + 'Content-Disposition', + ($this->forceDownload ? 'attachment' : 'inline') + . '; filename="' . $this->filename . '"' + . '; filename*=utf-8\'\'' . rawurlencode($this->filename) + ); + + $this->adapter->send($request, $response); + } + +} diff --git a/src/Response/Fly/FlyResponse.php b/src/Response/Fly/FlyResponse.php new file mode 100644 index 0000000..9588805 --- /dev/null +++ b/src/Response/Fly/FlyResponse.php @@ -0,0 +1,34 @@ +adapter = $adapter; + } + + /** + * @param IRequest $request + * @param IResponse $response + * @return void + */ + public function send(IRequest $request, IResponse $response) + { + $this->adapter->send($request, $response); + } + +} diff --git a/tests/cases/Response/Fly/Buffer/FileBuffer.phpt b/tests/cases/Response/Fly/Buffer/FileBuffer.phpt new file mode 100644 index 0000000..00ae9a2 --- /dev/null +++ b/tests/cases/Response/Fly/Buffer/FileBuffer.phpt @@ -0,0 +1,41 @@ +write('foobar'); + $b->close(); + + Assert::equal('foobar', file_get_contents($file)); +}); + +test(function () { + $file = TEMP_DIR . '/test2.file' . time(); + file_put_contents($file, 'foobar'); + + $b = new FileBuffer($file, 'r'); + $b->write('test'); + + Assert::equal('foobar', file_get_contents($file)); +}); + +test(function () { + $file = TEMP_DIR . '/test3.file' . time(); + file_put_contents($file, 'foobar'); + + $b = new FileBuffer($file, 'r'); + + Assert::equal('foo', $b->read(3)); + Assert::equal('bar', $b->read(3)); + Assert::equal('', $b->read(1)); + Assert::true($b->eof()); +}); diff --git a/tests/cases/Response/Fly/Buffer/ProcessBuffer.phpt b/tests/cases/Response/Fly/Buffer/ProcessBuffer.phpt new file mode 100644 index 0000000..4896ebe --- /dev/null +++ b/tests/cases/Response/Fly/Buffer/ProcessBuffer.phpt @@ -0,0 +1,17 @@ +read(128); + + Assert::equal(trim(@exec('date')), trim($data)); +});