Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[2.2] [output-stream] Refactor StreamingResponse implementation to use an OutputStream #4146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/HttpKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ public function render($controller, array $options = array())
return $response->getContent();
}

$outputStream = $this->container->get('kernel.output_stream');

$response->setOutputStream($outputStream);
$response->sendContent();
} catch (\Exception $e) {
if ($options['alt']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<parameter key="cache_warmer.class">Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate</parameter>
<parameter key="cache_clearer.class">Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer</parameter>
<parameter key="file_locator.class">Symfony\Component\HttpKernel\Config\FileLocator</parameter>
<parameter key="kernel.output_stream.class">Symfony\Component\HttpFoundation\OutputStream\StreamOutputStream</parameter>
</parameters>

<services>
Expand Down Expand Up @@ -51,5 +52,9 @@
<argument type="service" id="kernel" />
<argument>%kernel.root_dir%/Resources</argument>
</service>

<service id="kernel.output_stream" class="%kernel.output_stream.class%" factory-class="%kernel.output_stream.class%" factory-method="create">
<argument>php://output</argument>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

<service id="streamed_response_listener" class="%streamed_response_listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="kernel.output_stream" />
</service>

<service id="locale_listener" class="%locale_listener.class%">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpFoundation\OutputStream;

/**
* OutputStreamInterface defines how streaming responses
* send data to the client.
*
* @author Igor Wiedler <[email protected]>
*/
interface OutputStreamInterface
{
/**
* Write some data to the stream.
*
* @param string $data The data to write
*/
function write($data);

/**
* Close the stream.
*
* This should be called after the sending of data
* has been completed. In case of persistent connections,
* it is never called.
*/
function close();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpFoundation\OutputStream;

/**
* StreamOutputStream is an implementation of the OutputStreamInterface
* that uses any writable PHP stream as a target.
*
* Example usage:
*
* $stream = fopen('php://output');
* $output = new StreamOutputStream($stream, 'w');
* $output->write('foo');
* $output->write('bar');
* $output->close();
*
* @author Igor Wiedler <[email protected]>
*/
class StreamOutputStream implements OutputStreamInterface
{
private $stream;

/**
* @param resource $stream A valid stream resource, such as the return value of
* a fopen() call.
*/
public function __construct($stream)
{
if (!is_resource($stream)) {
throw new \InvalidArgumentException('The supplied stream is invalid.');
}

$this->stream = $stream;
}

/**
* @{inheritdoc}
*/
public function write($data)
{
fwrite($this->stream, $data);
}

/**
* @{inheritdoc}
*/
public function close()
{
fclose($this->stream);
}

/**
* Expose the stream resource, so that it can be used for more efficient
* operations, such as stream_copy_to_stream.
*
* @return The stream resource
*/
public function getStreamResource()
{
return $this->stream;
}

/**
* Static factory method
*/
static public function create($filename)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be public static

{
$stream = fopen($filename, 'w');

return new static($stream);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing empty line

}
}
21 changes: 19 additions & 2 deletions src/Symfony/Component/HttpFoundation/StreamedResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\HttpFoundation;

use Symfony\Component\HttpFoundation\OutputStream\OutputStreamInterface;

/**
* StreamedResponse represents a streamed HTTP response.
*
Expand All @@ -29,6 +31,7 @@
class StreamedResponse extends Response
{
protected $callback;
protected $outputStream;
protected $streamed;

/**
Expand Down Expand Up @@ -72,7 +75,17 @@ public function setCallback($callback)
}

/**
* {@inheritdoc}
* Sets the output stream used for streaming the response.
*
* @param OutputStreamInterface $outputStream stream
*/
public function setOutputStream(OutputStreamInterface $outputStream)
{
$this->outputStream = $outputStream;
}

/**
* @{inheritdoc}
*/
public function prepare(Request $request)
{
Expand Down Expand Up @@ -102,7 +115,11 @@ public function sendContent()
throw new \LogicException('The Response callback must not be null.');
}

call_user_func($this->callback);
if (null === $this->outputStream) {
throw new \LogicException('The Response output stream must not be null.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using stdout by default here instead of throwing an exception?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@igorw what do you think about this ?

}

call_user_func($this->callback, $this->outputStream);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpFoundation\Tests\OutputStream;

use Symfony\Component\HttpFoundation\OutputStream\StreamOutputStream;

class OutputStreamTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$fp = fopen('php://memory', 'rw');
$output = new StreamOutputStream($fp);
}

/**
* @expectedException InvalidArgumentException
*/
public function testConstructorWithInvalidStream()
{
$fp = 'foobar';
$output = new StreamOutputStream($fp);
}

public function testWrite()
{
$fp = fopen('php://memory', 'rw');
$output = new StreamOutputStream($fp);

$output->write('sample output');
rewind($fp);
$this->assertEquals('sample output', fread($fp, 50));
}

public function testClose()
{
$fp = fopen('php://memory', 'rw');
$output = new StreamOutputStream($fp);

$output->close();
try {
fread($fp, 1);
$this->fail();
} catch (\PHPUnit_Framework_Error_Warning $e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not @expectedException PHPUnit_Framework_Error_Warning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more explicit as it verifies exactly where the notice occurs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eriksencosta thus, I'm not sure we can use expectedException for PHPUnit internal exceptions

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\OutputStream\OutputStreamInterface;

class StreamedResponseTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -63,6 +64,7 @@ public function testSendContent()
$called = 0;

$response = new StreamedResponse(function () use (&$called) { ++$called; });
$response->setOutputStream($this->getOutputStreamMock());

$response->sendContent();
$this->assertEquals(1, $called);
Expand All @@ -71,10 +73,32 @@ public function testSendContent()
$this->assertEquals(1, $called);
}

public function testSendContentUsesOutputStream()
{
$output = $this->getOutputStreamMock();
$output->expects($this->once())
->method('write')
->with('foo');

$response = new StreamedResponse(function ($output) { $output->write('foo'); });
$response->setOutputStream($output);
$response->sendContent();
}

/**
* @expectedException \LogicException
*/
public function testSendContentWithNonCallable()
{
$response = new StreamedResponse('foobar');
$response->setOutputStream($this->getOutputStreamMock());
$response->sendContent();
}

/**
* @expectedException \LogicException
*/
public function testSendContentWithNoOutputStream()
{
$response = new StreamedResponse(null);
$response->sendContent();
Expand Down Expand Up @@ -111,4 +135,9 @@ public function testCreate()
$this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response);
$this->assertEquals(204, $response->getStatusCode());
}

private function getOutputStreamMock()
{
return $this->getMock('Symfony\Component\HttpFoundation\OutputStream\OutputStreamInterface');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\EventListener;

use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\OutputStream\OutputStreamInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
Expand All @@ -25,6 +26,11 @@
*/
class StreamedResponseListener implements EventSubscriberInterface
{
public function __construct(OutputStreamInterface $outputStream)
{
$this->outputStream = $outputStream;
}

/**
* Filters the Response.
*
Expand All @@ -39,7 +45,10 @@ public function onKernelResponse(FilterResponseEvent $event)
$response = $event->getResponse();

if ($response instanceof StreamedResponse) {
$response->setOutputStream($this->outputStream);
$response->send();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not close the stream here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to allow async execution where the callback closes the stream later on. But since that doesn't really work properly with HttpKernel it makes sense to close it.


$this->outputStream->close();
}
}

Expand Down