1

I currently output an image like this (inside a controller class):

    ob_start();
    header("Content-Type: image/gif");
    imagegif($image_resource);
    imagedestroy($image_resource);
    $content_string = ob_get_clean();
    $this->getResponse()->setContent($content_string);

Is there a better way without actually capturing the output buffer? since this is not very testable...

Artem Goutsoul
  • 733
  • 5
  • 17

1 Answers1

1

The Only way to capture the image without output-buffer is to write the image to a file:

$tmpFile = tmpfile();
$path = stream_get_meta_data($tmpFile)['uri'];
imagegif($image_resource, $tmpFile);
$content_string = file_get_contents($path);
imagedestroy($image_resource);
fclose($tmpFile);

$this->getResponse()->setContent($content_string);

I created a temporary file with the tmpfile() method, and got the filename via stream_get_metadata() function. Then I loaded the content of the file into the string variable and closed the filehandle to the tmpfile so it gets removed.

A more optimized way from the comments proposed writing to a php://memory file instead. The is a small explanation in another question on when to use php://memory and php://temp

// open in memory file
$handle = fopen('php://memory', 'r+');
imagegif($im, $handle);
// reset file handle
fseek($handle, 0);

// get filesize
$stat = fstat($handle);
$size = $stat['size'];

// read the created file into variable
$fileContent = fread($handle, $size);
fclose($handle);

// write filecontent to the response object
$this->getResponse()->setContent($content_string);

Personally i would use the output buffer but move the code into a separate function or class. That way you can isolate and mock its behavior for testing purposes:

/** @var GDImage|resource $image */
function getImage($image): string
{
   ob_start();
   imagegif($image);
   $returnData = ob_get_contents();
   ob_end_clean();
   imagedestroy($image);
   return $returnData;
}
$content_string = getImage($image_resource);
$this->getResponse()->setContent($content_string);

In any way i would advise you to set the header on the response object instead of writing it manually:

$this->getResponse()->getHeaders()->addHeaders([
    'Content-Type' => 'image/gif',
]);
$this->getResponse()->setContent($content_string);

This way the headers get output to the browser by the framework at the correct time and you will not have any problems with output buffering.

  • This can be optimized with the help of [php://memory](https://www.php.net/manual/en/wrappers.php.php#wrappers.php.memory), which avoids read/writes of files and pollution of the temp directory. – crash Jan 07 '22 at 13:54
  • @crash how would i use that in this example? And could that lead to race conditions, with several parralel requests? or is php://memory uniqe for each request? – Andre Lubian Jan 07 '22 at 14:15
  • Should be "unique" for each request, just use the resource returned by `fopen('php://memory')`. – crash Jan 07 '22 at 14:22
  • 1
    The reason why I don't want output buffering is because it prevents output of more headers, i.e. you cannot test outputting two images, since you'll get an error that the headers were already returned. But I like the idea with php://memory, so the solution with writing out a file would probably do the trick, thank you guys! – Artem Goutsoul Jan 07 '22 at 15:58
  • Ah, I understand, that is the reason I advised on setting the headers on the request object. It is a common practice nowadays to not output anything directly, but instead write to the request body and header so that the framework can handle sending the stuff to the client https://docs.laminas.dev/laminas-http/response/#quick-start – Andre Lubian Jan 10 '22 at 07:16