I'm trying to create a class that converts an array into plaintext and file. Plaintext works fine however when I try to save it as a tmpfile and share it I'm getting errors.
My controller looks like:
public method index() {
$props = ['foo'=>'bar']; //array of props;
return response()->download(MyClass::create($props);
// I've also tried using return response()->file(MyClass::create($props);
}
And my class looks like:
class MyClass
{
// transform array to plain text and save
public static function create($props)
{
// I've tried various read/write permissions here with no change.
$file = fopen(tempnam(sys_get_temp_dir(), 'prefix'), 'w');
fwrite($file, implode(PHP_EOL, $props));
return $file;
}
// I've also tried File::put('filename', implode(PHP_EOL, $props)) with the same results.
}
And I'm getting a file not found exception:
The file "Resource id #11" does not exist.
I've tried tmpfile, tempname and others and get the same exception. I've tried passing MyClass::create($props)['uri'] and I got
The file "" does not exist
Is this an error due to my env (valet) or am I doing this wrong?
Your code is mixing up usage of filenames and file handles:
tempnam() returns a string: the path to a newly created temporary file
fopen() accesses a file at a given path, and returns a "resource" - a special type in PHP used to refer to system resources; in this case, the resource is more specifically a "file handle"
if you use a resource where a string was expected, PHP will just give you a label describing the resource, such as "Resource id #11"; as far as I know, there is no way to get back the filename of an open file handle
In your create definition, $file is the result of fopen(), so is a "resource" value, the open file handle. Since you return $file, the result of MyClass::create($props) is also the file handle.
The Laravel response()->download(...) method is expecting a string, the filename to access; when given a resource, it silently converts it to string, resulting in the error seen.
To get the filename, you need to make two changes to your create function:
Put the result of tempnam(sys_get_temp_dir(), 'prefix') in a variable, e.g. $filename, before calling $file = fopen($filename, 'w');
Return $filename instead of $file
You should also add a call to fclose($file) before returning, to cleanly close the file after writing your data to it.
Related
(It's API only) I'm using JasperPHP to generate reports, the user can choose the extension (xls or pdf). After I process the data, then I save the file/report locally, it's in "storage/app/jasper/example1234.pdf".
So at the end of the function I do this:
// more code
$file = $path . $filename . '.' . $extension; // file path
// more code
return response()->file($file);
And it works, I can get the file using ReactJS. OK! But I wanted to delete the file before the "return", but if I do that the file will not be read on the "return". That's why I wanted to save it in a variable/memory first, delete the file locally and then return.
Note I tried to get the file using several methods, such as "Storage::get()", but they all generate errors on "return", stating that "string file" is expected but "resource" was given.
Response facade has deleteFileAfterSend() method. You can use it like the following:
return response()->file($file)->deleteFileAfterSend();
I have the contents of a file in a string. I need to pass this file to a function where the function is expecting the parameter to be the name of the file, not the contents. The obvious and probably simplest way to do this would be to write the contents to a temp file, then pass that file name to the function, and unlink the file once I'm finished.
However, I'm looking for a solution that doesn't involve writing the file out to the file system and then reading it back in. I've had a need for this in multiple cases, so I'm not looking for a work-around to a specific function, but more of a generic method that will work for any function expecting a file name (like file_get_contents(), for instance).
Here are some thoughts, but not sure how to pursue these yet:
Is it possible to write the contents somewhere in memory, and then
pass that to the function as a filename? Perhaps something using
php://memory.
Is it possible to write the contents to a pipe, then pass the name of the
pipe to the function?
I did a short proof-of-concept trying with php://memory as follows, but no luck:
$data = "This is some file data.\n";
file_put_contents( 'php://memory', $data );
echo file_get_contents( 'php://memory' );
Would be interested in knowing of good ways to address this. Googling hasn't come up with anything for me.
It mainly depends on what the target function does with the file name. If you're lucky, you can register your own stream wrapper:
stream_wrapper_register('demo', 'DemoStream');
$data = "This is some file data.\n";
$filename = 'demo://foo';
file_put_contents($filename, $data );
echo file_get_contents($filename);
Why not use a file in the /tmp/ directory? Like this:
<?php
$filename = '/tmp/mytmpfile';
$data = "This is some file.\n";
file_put_contents($filename, $data);
$result = file_get_contents($filename);
var_dump($result);
Well, as you say you don't want to use a file, you shouldn't use file_get_contents().
But you can achieve the same result by using stream_get_contents(), like this:
<?php
$data = "This is some file data.\n";
$handle = fopen('php://memory', 'r+'); // open an r/w handle to memory
fputs($handle, $data); // write the data
rewind($handle); // rewind the pointer
echo stream_get_contents($handle); // retrieve the contents
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$log = base_path()."/storage/logs/trade.log";
if ( !file_exists ($log) ) {
$data = fopen($log, "w");
}
file_put_contents($log, $data . PHP_EOL.PHP_EOL, FILE_APPEND);
I have a page use file_put_contents to record log. when my folder don't exist file, it will auto create the file and add log into it
My problem is when first time auto create file and place content into it. It comes out - Resource id #233
2nd time without crate file will be normal no any problem
anyone know how to fix this?
fopen() returns a resource.
file_put_contents() write your file and converts $data into a string (calls __toString() on the resource).
The second times, the file exists. So, the program doesn't pass into the if-condition and it writes $data that contains JSON.
To solve your problem, just remove your if block.
Documentation said :
If filename does not exist, the file is created. Otherwise, the existing file is overwritten, unless the FILE_APPEND flag is set.
Lets say we have a file named languages.txt which has the following content:
AJAX HTML CSS JQUERY
Here's the php code to read the above file:
<?php
function read ( $fh2, $length ) {
return ( fread($fh2,$length) );
}
$fh1 = fopen ( 'languages.txt', 'r' ) ;
echo read ( $fh1, 7 ) ;
echo read ( $fh1, 4 ) ;
?>
We know that in PHP local variables are local to functions and global variables are available outside of functions.
So, considering $fh1 as a global variable, $fh2 as a local variable and both being independent of each other I expected the output to be
AJAX HTAJAX
But, the output comes out to be
AJAX HTML C
Can anyone explain me what's happening? When a resource datatype is passed to a function as parameter, is it passed by reference unlike int datatype?
When you are using functions like fread() the file pointer moves forward in your file. This is why your input is not what you expect it to be.
If you want to return to the beginning of the file, you can use rewind().
What you could do is $line = fgets($fh) and get a whole line, then depending on the separator used you can split said line into an array like so $exploded = explode("\t", $line).
The resource returned by fopen() does not contain the content of the file. It encapsulates a file handle provided by the OS.
A file handle is used by the OS to identify a data structure that contains the information about the status of the open file. This status information includes a so-called file pointer that is the position inside the file where the next read or write operation will take place.
Your code passes the value returned by fopen() by value but, because it is just a pointer to the actual data structure, no matter how many (local or global) copies of $fh1 you create, they all point to the same structure that manages the same file in the background.
This means the following code:
$fh1 = fopen('languages.txt', 'r');
echo(read($fh1, 7)); // 'AJAX HT'
$fh2 = fh1;
echo(read($fh2, 4)); // 'ML C'
echo(read($fh1, 3)); // 'SS '
$fh3 = $fh2;
echo(read($fh3, 6)); // 'JQUERY'
will output the content of the file, even if three variables are used to read the content.
I have a blob resource from my db. I want to wrap temporaly this file into Symfony File object because I want to use specific methods like the extension guesser, and apply symfony file validators.
I want to store this temporary file into memory, because the blobs are small files and i dont want to create a file in disk in every request.
I tried to do this in that way:
$file = new File ('php://temp');
but symfony throws an error that says 'The file "php://temp" does not exist'. Looking at File source, the error is caused by a "is_file($path)" check that is made in the constructor, and I can invalidate this putting false in the second argument. But, if I do:
$file = new File ('php://temp', false);
the File is created, but then the error comes back later, e.g. when i use the guesser:
$file->guessExtension($file)
because in Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php:
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
(...)
Ok. Then my question is: There is a way to load a 'php://temp' or 'php://memory' within a File object?
Pretty sure php://temp writes to memory until it is full and then writes to a file, whereas php://memory ensures only in memory with no fall back.
This likely happens because php://temp and php://memory are non-reusable, so once you've written to it the content may not still be there when you next want it. From the PHP manual:
php://memory and php://temp are not reusable, i.e. after the streams have been closed there is no way to refer to them again.
file_put_contents('php://memory', 'PHP');
echo file_get_contents('php://memory'); // prints nothing
How are you writing to php://temp to begin with? That will be the more important issue rather than with Symfony's File class. I suspect that by the time you are creating a File instance that php://temp has already gone.
It's worth noting that using php://temp will create a file on disk in the temporary location, so you might as well use write to a tempnam() handle anyway. At least then you will have a reference to a physical (but temporary) file.
I suggested to allow passing the file contents (instead of the path) to Symfony's MIME type guesser, to enable guessing "on-the-fly": https://github.com/symfony/symfony/issues/40916
Here's how I do it right now:
use Symfony\Component\Mime\MimeTypes;
$tmpFilename = tempnam(sys_get_temp_dir(), 'guessMimeType_');
file_put_contents($tmpFilename, $content);
$mimeTypes = new MimeTypes();
$guessedMimeType = $mimeTypes->guessMimeType($tmpFilename);
unlink($tmpFilename);
The first line is taken from https://www.php.net/manual/en/function.tempnam.php#93256