Symfony2 PHPWord response - symfony

I am trying to generate a docx document on Symfony2, using the PHPWord bundle.
In my controller, I succeed in returning a docx file, but it is empty, I think it comes from my faulty response format.
public function indexAction($id)
{
$PHPWord = new PHPWord();
$section = $PHPWord->addSection();
$section->addText(htmlspecialchars(
'"Learn from yesterday, live for today, hope for tomorrow. '
. 'The important thing is not to stop questioning." '
. '(Albert Einstein)'
));
// Saving the document
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($PHPWord, 'Word2007');
return new Response($objWriter->save('helloWorld.docx'), 200, array('Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'));
}

Try this class
<?php
use PhpOffice\PhpWord\IOFactory;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Settings;
use Symfony\Component\HttpFoundation\Response;
class WordResponse extends Response
{
/**
* WordResponse constructor.
* #param string $name The name of the word file
* #param PhpWord $word
*/
public function __construct($name, &$word)
{
parent::__construct();
// Set default zip library.
if( !class_exists('ZipArchive')){
Settings::setZipClass(Settings::PCLZIP);
}
$writer = IOFactory::createWriter($word, 'Word2007');
//Set headers.
$this->headers->set("Content-Disposition", 'attachment; filename="' . $name . '"');
$this->headers->set("Content-Type", 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
$this->headers->set("Content-Transfer-Encoding", 'binary');
$this->headers->set("Cache-Control", 'must-revalidate, post-check=0, pre-check=0');
$this->headers->set("Expires", '0');
$this->sendHeaders();
$writer->save('php://output');
}
}
Then in your controller do:
return new WordResponse($phpWord, "filename.docx");

Thanks a lot for your answer.
I achieve using the 2nd method, which is in my opinion the best.
I just have to return a response, otherwise the file was generated, but stuck in the web directory.
Using this response, everything was fine and a download prompt appeared, with the "full" file.
Here's my code :
$PHPWord = new PHPWord();
$section = $PHPWord->addSection();
$section->addText(htmlspecialchars(
'"Learn from yesterday, live for today, hope for tomorrow. '
. 'The important thing is not to stop questioning." '
. '(Albert Einstein)'
));
// Saving the document
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($PHPWord, 'Word2007');
$filename="MyAwesomeFile.docx";
$objWriter->save($filename, 'Word2007', true);
$path = $this->get('kernel')->getRootDir(). "/../web/" . $filename;
$content = file_get_contents($path);
$response = new Response();
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
$response->headers->set('Content-Disposition', 'attachment;filename="'.$filename);
$response->setContent($content);
return $response;

PHPWord->save() returns a true value so that would be why your file is not being downloaded. With your return new Response() you are setting the content of your response to true (the result of your save call) which is why your response is empty.
You have 2 (and probably more that I haven't thought of) options to generate and download this file..
1. Save your file to a temp folder and server from there
$filename = sprintf(
'%s%sDoc-Storage%s%s.%s',
sys_get_temp_dir(),
DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR,
uniqid(),
'docx'
);
$objWriter->save($filename);
$response = new BinaryFileResponse($filename);
For more info on the BinaryFileResponse see the docs.
2. Ignore Symfony and serve directly via the PHPWord action
$objWriter->save($filename, 'Word2007', true);
exit();
The ->save method provides all of the actions to download the generated file internally (see the code) so all you need to do is set the format and the third parameter to true and it will handle all of the headers for you. Granted it won't be returning a Symfony response but you will be exiting out before you get to that exception.

Related

Symfony3 add locale in deeplink

I create a new site in symfony3 following the getting started section in the official symfony documentation in https://symfony.com/doc/current/setup.html
Everything is working ok.. if I put mydomain.com as the URL, the framework add /en or the correct local.
My question is if there is a way that if the user do a deeplink to mydomain.com/blog the framework found that the local is not present so it can add and transform the url to mydomain.com/en/blog
I'm not adding the code as it is the default one. Let me know if you need it.
There are multiple ways to do this. Probably the easiest is to have an EventSubscriber or -Listener that catches request without a locale and then handles adding that information. Since you based your project on the demo application you might want to look at their solution: https://github.com/symfony/demo/blob/master/src/EventSubscriber/RedirectToPreferredLocaleSubscriber.php
The steps to perform in your event handler are roughly these:
Listen to kernel.request event
Return early based on some criteria, e.g. homepage, a cookie with the language is set, or something else
Detect the language either by getting the default locale or determining from your available locales and the browser header which language fits best (see: https://github.com/willdurand/Negotiation#language-negotiation)
Redirect, add the locale as attribute to request, write the currently set language to a cookie, or whatever else you need to do to change the route
Thanks to #dbrumann I get to this solution... For sure it can be improve to use less code but it just did the trick.
I updated the onKernelRequest method in RedirectToPreferredLocaleSubscriber class
public function onKernelRequest(GetResponseEvent $event): void
{
$request = $event->getRequest();
$path = explode('/',$request->getPathInfo());
$hasLocale = false;
foreach ($this->locales as $key => $l) {
if($l == $path[1]){
$hasLocale = true;
}
}
if(!$hasLocale){
// Ignore sub-requests and all URLs but the homepage
if (!$event->isMasterRequest() || '/' !== $request->getPathInfo()) {
$preferredLanguage = $request->getPreferredLanguage($this->locales);
if ($preferredLanguage !== $this->defaultLocale) {
$url = "";
foreach ($path as $key => $p) {
if($key > 0){
$url .= "/" . $p;
}
}
//print_r('/' . $preferredLanguage . $url);exit;
$response = new RedirectResponse('/' . $preferredLanguage . $url);
$event->setResponse($response);
}
}
else{
// Ignore requests from referrers with the same HTTP host in order to prevent
// changing language for users who possibly already selected it for this application.
if (0 === mb_stripos($request->headers->get('referer'), $request->getSchemeAndHttpHost())) {
return;
}
$preferredLanguage = $request->getPreferredLanguage($this->locales);
if ($preferredLanguage !== $this->defaultLocale) {
$response = new RedirectResponse($this->urlGenerator->generate('homepage', ['_locale' => $preferredLanguage]));
$event->setResponse($response);
}
}
}
}

HTTPful attach file and json-body in one request

I need to upload files via Rest and also send some configuration with it.
Here is my example code:
$this->login();
$files = array('file'=>'aTest1.jpg');
$data =
array(
'name'=>'first file',
'description'=>'first file description',
'author'=>'test user'
);
$response = Request::post($this->getRoute('test'))
->addHeader('Authorization', "Bearer " . $this->getToken())
->attach($files)
->body(json_encode($data))
->sendsJson()
->send();
I am able to send the file or able to send the body. But it is not working if I try with both...
Any Hint for me?
Regards
n00n
For those coming to this page via Google. Here's an approach that worked for me.
Don't use attach() and body() together. I found that one will clear out the other. Instead, just use the body() method. Use file_get_contents() to get binary data for your file, then base64_encode() that data and place it into the $data as a parameter.
It should work with JSON. The approach worked for me with application/x-www-form-urlencoded mime type, using $req->body(http_build_query($data));.
$this->login();
$filepath = 'aTest1.jpg';
$data =
array(
'name'=>'first file',
'description'=>'first file description',
'author'=>'test user'
);
$req = Request::post($this->getRoute('test'))
->addHeader('Authorization', "Bearer " . $this->getToken());
if (!empty($filepath) && file_exists($filepath)) {
$filedata = file_get_contents($filepath);
$data['file'] = base64_encode($filedata);
}
$response = $req
->body(json_encode($data))
->sendsJson();
->send();
the body() method erases payload content, so after calling attach(), you must fill payload yourself :
$request = Request::post($this->getRoute('test'))
->addHeader('Authorization', "Bearer " . $this->getToken())
->attach($files);
foreach ($parameters as $key => $value) {
$request->payload[$key] = $value;
}
$response = $request
->sendsJson();
->send();

Return an image from a controller action in symfony

I need to access an image by providing its name in the url path, i tried to use this code but the image is not showing
/**
*
* #Route("images/{imgname}",name="workflow_image")
*/
public function WorkflowImageAction(Request $request,$imgname){
$filepath = $this->get('kernel')->getRootDir().'/../web/images/workflow/'.$imgname;
$file = readfile($filepath);
$headers = array(
'Content-Type' => 'image/png',
'Content-Disposition' => 'inline; filename="'.$file.'"');
return $file;
}
if you are serving a static file, you can use a BinaryFileResponse:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = 'path/to/file.txt';
$response = new BinaryFileResponse($file);
return $response;
More info about Serving Files in Symfony2 in the doc.
Hope this help
Are you sure, it's a good idea to share image through php?
You can write some rules for folder web/image/workflow in your server (nginx or apache).
Share them through php is bad idea.
Nginx/apache can do it very fast, not using RAM (php read full image in RAM).
Also, nginx/apache can cache this image.
All the answers here are outdated.
I would suggest not using BinaryFileResponse or using file_get_contents since they would read the whole file and place it in your memory.
Please use StreamedResponse provided at Symfony\Component\HttpFoundation\StreamedResponse.
$imageFilePath = dirname(__FILE__)."/../../var/tmp/bean.jpg";
$streamedResponse = new StreamedResponse();
$streamedResponse->headers->set("Content-Type", 'image/png');
$streamedResponse->headers->set("Content-Length", filesize($imageFilePath));
$streamedResponse->setCallback(function() use ($imageFilePath) {
readfile($imageFilePath);
});
return $streamedResponse;

Symfony Audio Stream with Gaufrette

I am using KnpGaufretteBundle to store audio files. I am able to download a given file to the client like this:
$filename = "Somefilename.mp3";
$fs = $this->filesystemMap->get('media_fs');
$file = $fs->read($filename);
if($file){
//Create And Return Response
$response = new Response();
$disp = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$variant->getFileName()
);
$response->headers->set('Content-Length', $fs->size($filename));
$response->headers->set('Accept-Ranges', 'bytes');
$response->headers->set('Content-Transfer-Encoding', 'binary');
$response->headers->set('Content-Type', 'application/octet-stream');
$response->headers->set('Content-Disposition', $disp);
$response->setContent($file);
return $response;
}
But now I also want to stream the file to the client, instead of using the attachment content disposition. Basically I want to access it clientside as if I was pointing at an actual mp3 sitting on my server. Does anyone know how this can be done?
I solved this by using the streamwrapper... it was this easy.
$filepath = 'gaufrette://myFileSystemName/'.$filename;
$response = new \Symfony\Component\HttpFoundation\BinaryFileResponse($filepath);

How to retrieve a streamed response (e.g. download a file) with Symfony test client

I am writing functional tests with Symfony2.
I have a controller that calls a getImage() function which streams an image file as follows:
public function getImage($filePath)
$response = new StreamedResponse();
$response->headers->set('Content-Type', 'image/png');
$response->setCallback(function () use ($filePath) {
$bytes = #readfile(filePath);
if ($bytes === false || $bytes <= 0)
throw new NotFoundHttpException();
});
return $response;
}
In functional testing, I try to request the content with the Symfony test client as follows:
$client = static::createClient();
$client->request('GET', $url);
$content = $client->getResponse()->getContent();
The problem is that $content is empty, I guess because the response is generated as soon as the HTTP headers are received by the client, without waiting for a data stream to be delivered.
Is there a way to catch the content of the streamed response while still using $client->request() (or even some other function) to send the request to the server?
The return value of sendContent (rather than getContent) is the callback that you've set. getContent actually just returns false in Symfony2
Using sendContent you can enable the output buffer and assign the content to that for your tests, like so:
$client = static::createClient();
$client->request('GET', $url);
// Enable the output buffer
ob_start();
// Send the response to the output buffer
$client->getResponse()->sendContent();
// Get the contents of the output buffer
$content = ob_get_contents();
// Clean the output buffer and end it
ob_end_clean();
You can read more on the output buffer here
The API for StreamResponse is here
For me didn't work like that. Instead, I used ob_start() before making the request, and after the request i used $content = ob_get_clean() and made asserts on that content.
In test:
// Enable the output buffer
ob_start();
$this->client->request(
'GET',
'$url',
array(),
array(),
array('CONTENT_TYPE' => 'application/json')
);
// Get the output buffer and clean it
$content = ob_get_clean();
$this->assertEquals('my response content', $content);
Maybe this was because my response is a csv file.
In controller:
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
The current best answer used to work well for me for some time, but for some reason it isn't anymore. The response is parsed into a DOM crawler and the binary data is lost.
I could fix that by using the internal response. Here's the git patch of my changes[1]:
- ob_start();
$this->request('GET', $uri);
- $responseData = ob_get_clean();
+ $responseData = self::$client->getInternalResponse()->getContent();
I hope this can help someone.
[1]: you just need access to the client, which is a
Symfony\Bundle\FrameworkBundle\KernelBrowser

Resources