Removing file after delivering response with Silex/Symfony - php

I'm generating a pdf using Knp\Snappy\Pdf in my Silex application. the file name is random and saved to the tmp directory.
$filename = "/tmp/$random.pdf"
$snappy->generate('/tmp/body.html', $filename, array(), true);
I think return the pdf in the response,
$response = new Response(file_get_contents($filename));
$response->headers->set('Pragma', 'public');
$response->headers->set('Content-Type', 'application/pdf');
return $response;
The pdf is displayed in the web browser correctly. The file with the random filename still exists when the request is finished. I can't unlink the file before returning the response. I've tried registering a shutdown function with register_shutdown_function and unlinking the file from there. However that doesn't seem to work. Any ideas?

Even though this is old, figured if anyone googles this more recently like I did. This is the solution I found.
There is a deleteFileAfterSend() method on the BinaryFileResponse returned from sendFile in Silex.
So in your controller you can just do:
return $app ->sendFile($filepath)
->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $fileName)
->deleteFileAfterSend(true);

You can use the finish middleware for that:
A finish application middleware allows you to execute tasks after the Response has been sent to the client (like sending emails or logging)
This is how it could look:
$app->finish(function (Request $request, Response $response) use ($app) {
if (isset($app["file_to_remove"])) {
unlink($app["file_to_remove"];
}
});
//in your controller
$app["file_to_remove"] = $filename;

Maerlyn is right, but in this case you can also unlink the file before returning the response, since the content of the file is already in the $response.

Related

Download json without creating a file or delete file after return

i am trying to let an user download a collection as a json file, but i don't know how to do it, without storing the file.
option 1: download without storing at all
i tried something like this
return response()->download(WorkPersonalReport::all(), 'zapis_prac.json');
this does not work, because it is not a file. can i make it a "pseudo file"?
option 2: create file, let the download happen, delete file
public function jsonExport() {
$wprs = WorkPersonalReport::all();
file_put_contents('assets/workPersonalReport.json', $wprs);
return response()->download('assets/workPersonalReport.json', 'zapis_prac.json');
}
with
public function index() {
unlink('assets/workPersonalReport.json');
}
is either of the options possible with optimal code?
first - just to create a temporary file, which will be deleted after the download goes through?
second - unlink() the file after return statement, instead of everytime index() is called?
Consider the following snippet;
return response(WorkPersonalReport::all(), 200, [
'Content-Disposition' => 'attachment; filename="collection.json"'
]);

How Do I return a file for download in Laravel

I need to be able to give the user a file for download, and so I created a route (unless I can do the link for a specific user's CV in blade). My files are stored in storage/app/cv/ directory. I created a route as such:
Route::get('test', function() {
$destinationPath = config('app.CVDestinationPath') . "/static_file.pdf";
$uploaded = Storage::get($destinationPath);
return $uploaded;
}
However this route is giving me a weird output. I think it is the file but it is converting the file to html or something. Could anyone help me return the file (it could be pdf, doc, or docx, or plain text). My file is not stored in the public directory!
Because the file is not uploaded in the public directory, you are required to use a storage method for accessing it first before you return a response.
$destinationPath = config('app.CVDestinationPath') . "/static_file.pdf";
$uploaded = Storage::get($destinationPath);
return (new \Illuminate\Http\Response($uploaded))->header('Content-Type', 'application/pdf');
Make sure to return a header with the content type otherwise the file will not be in an acceptable format.
you can use "Response" to help you with the download headers:
<?php
use Illuminate\Support\Facades\Response;
Route::get('test', function() {
$destinationPath = config('app.CVDestinationPath') . "/static_file.pdf";
return response()->download($destinationPath);
}
?>
Checkout this link https://laravel.com/docs/5.5/responses#file-downloads to find out more e.g. response->file($destinationPath) forces the browser to display the file (.pdf in your case) to the user using its lokal plugins.

php - unlink throws error: Resource temporarily unavailable

Here is the piece of code:
public function uploadPhoto(){
$filename = '../storage/temp/image.jpg';
file_put_contents($filename,file_get_contents('http://example.com/image.jpg'));
$photoService->uploadPhoto($filename);
echo("If file exists: ".file_exists($filename));
unlink($filename);
}
I am trying to do the following things:
Get a photo from a URL and save it in a temp folder in my server. This works fine. The image file is created and echoes If file exists: 1 when echo("If file exists: ".file_exists('../storage/temp/image.jpg'));.
Pass that file to another function that hanldes uploading the file to Amazon s3 bucket. The file gets stored in my s3 bucket.
Delete the photo stored in the temp folder. This doesn't work! I get an error saying:
unlink(../storage/temp/image.jpg): Resource temporarily unavailable
If I use rename($filename,'../storage/temp/renimage.jpg'); instead of unlink($filename); i get an error:
rename(../storage/temp/image.jpg,../storage/temp/renimage.jpg): The process cannot access the file because it is being used by another process. (code: 32)
If I remove the function call $photoService->uploadPhoto($filename);, everything works perfectly fine.
If the file is being used by another process, how do I unlink it after the process has been completed and the file is no longer being used by any process? I do not want to use timers.
Please help! Thanks in advance.
Simplest solution:
gc_collect_cycles();
unlink($file);
Does it for me!
Straight after uploading a file to amazon S3 it allows me to delete the file on my server.
See here: https://github.com/aws/aws-sdk-php/issues/841
The GuzzleHttp\Stream object holds onto a resource handle until its
__destruct method is called. Normally, this means that resources are freed as soon as a stream falls out of scope, but sometimes, depending
on the PHP version and whether a script has yet filled the garbage
collector's buffer, garbage collection can be deferred.
gc_collect_cycles will force the collector to run and call __destruct
on all unreachable stream objects.
:)
Just had to deal with a similar Error.
It seems your $photoService is holding on to the image for some reason...
Since you didn't share the code of $photoService, my suggestion would be to do something like this (assuming you don't need $photoService anymore):
[...]
echo("If file exists: ".file_exists($filename));
unset($photoService);
unlink($filename);
}
The unset() method will destroy the given variable/object, so it can't "use" (or wharever it does) any files.
I sat over this problem for an hour or two, and finally realized that "temporarily unavailable" really means "temporarily".
In my case, concurrent PHP scripts access the file, either writing or reading. And when the unlink() process had a poor timing, then the whole thing failed.
The solution was quite simple: Use the (generally not very advisable) # to prevent the error being shown to the user (sure, one could also stop errors from beinf printed), and then have another try:
$gone = false;
for ($trial=0; $trial<10; $trial++) {
if ($gone = #unlink($filename)) {
break;
}
// Wait a short time
usleep(250000);
// Maybe a concurrent script has deleted the file in the meantime
clearstatcache();
if (!file_exists($filename)) {
$gone = true;
break;
}
}
if (!$gone) {
trigger_error('Warning: Could not delete file '.htmlspecialchars($filename), E_USER_WARNING);
}
After solving this issue and pushing my luck further, I could also trigger the "Resource temporarily unavailable" issue with file_put_contents(). Same solution, now everything works fine.
If I'm wise enough and/or unlinking fails in the future, I'll replace the # by ob_start(), so the error message could tell me the exact error.
I had the same problem. The S3 Client doesn't seem to want to unlock before unlink is being executed. If you extract the contents into a variable and set it as the 'body' in the putObject array:
$fileContent = file_get_contents($filepath);
$result = $s3->putObject(array(
'Bucket' => $bucket,
'Key' => $folderPath,
'Body' => $fileContent,
//'SourceFile' => $filepath,
'ContentType' => 'text/csv',
'ACL' => 'public-read'
));
See this answer: How to unlock the file after AWS S3 Helper uploading file?
The unlink method return bool value, so you can build a cycle, with some wait() and retries limit to wait for your processes to complete.
Additionally put "#" on the unlink, to hide the access error.
Throw another error/exception if retries count reached.

Flysystem S3 remote file download always corrupted

I recently started using Flysystem in an existing application with the intention of abstracting the local and remote (specifically, S3) filesystems. Everything was working ok on my development environment, on which I successfully configured the LocalAdapter. However, I cannot get S3 file downloads to work. I'd like to point out that file uploads are working perfectly, given that I can successfully download the file by manually browsing the S3 bucket in the AWS management console. That being said, I will skip the code that initializes the $filesystem variable.
My application is using a PSR-7 approach. That is, the code below is inside a function that is passed an object of type Psr\Http\Message\ServerRequestInterface as first argument and an object of type Psr\Http\Message\ResponseInterface as the second. Given that the local filesystem works fine, I think it is safe to assume that the problem doesn't lie there.
This is the code:
<?php
$stream = new \Zend\Diactoros\Stream($filesystem->readStream($filename));
$filesize = $stream->getSize();
return $response
->withHeader('Content-Type', 'application/pdf')
->withHeader('Content-Transfer-Encoding', 'Binary')
->withHeader('Content-Description', 'File Transfer')
->withHeader('Pragma', 'public')
->withHeader('Expires', '0')
->withHeader('Cache-Control', 'must-revalidate')
->withHeader('Content-Length', "{$filesize}")
->withBody($stream);
When I dump the $stream variable and the $filesize variable the results are as expected. The remote file contents are successfully printed. However, the file download is always corrupted and the file size is always of 0 bytes.
I am assuming that Flysystem takes care of everything behind the scenes and that I don't have to manually download the file to a temp folder first, before serving it to the client.
Any clue to what could be the problem?
Update 1
I have also tried with the following code, without any luck. However, it continues to work locally:
use Zend\Diactoros\CallbackStream;
$stream = new CallbackStream(function() use ($filesystem, $filename) {
$resource = $filesystem->readStream($filename);
while (!feof($resource)) {
echo fread($resource, 1024);
}
fclose($resource);
return '';
});
and
use Zend\Diactoros\CallbackStream;
$stream = new CallbackStream(function() use ($filesystem, $filename) {
$resource = $filesystem->readStream($filename);
fpassthru($resource);
return '';
});
Removing the Content-Length header seems to solve the problem.
See https://github.com/thephpleague/flysystem/issues/543 for more details.

CakePHP 3, how to render without CTP

How do I render without my controller attempting to load a non-existent .ctp file.
This is my code:
//without this, I get an error trying to load a non-existent .ctp file. When I include it, the browser does not render the PNG file.
$this->autoRender = false;
//... get avatar code
if (!file_exists($avatarPath))
throw new Exception('Could not find file: '.$avatarPath);
$file = file_get_contents($avatarPath);
header('Content-Type: image/png');
if ($file === false)
throw new Exception('file_get_contents failed on avatarPath');
else
echo $file;
When I use $this->autoRender = false;, the header call appears to be ignored. Any ideas?
Read about how to send files with CakePHP. Let me quote the documentation for you:
There are times when you want to send files as responses for your requests. You can accomplish that by using
public function sendFile($id)
{
$file = $this->Attachments->getFile($id);
$this->response->file($file['path']);
// Return response object to prevent controller from trying to render
// a view.
return $this->response;
}
As shown in the above example, you must pass the file path to the method. CakePHP will send a proper content type header if it’s a known file type listed in Cake\Network\Reponse::$_mimeTypes. You can add new types prior to calling Cake\Network\Response::file() by using the Cake\Network\Response::type() method.
If you want, you can also force a file to be downloaded instead of displayed in the browser by specifying the options:
$this->response->file(
$file['path'],
['download' => true, 'name' => 'foo']
);

Categories