When a PHP programmer needs to create a temporary file, he finds the tmpfile()
function in the manual and, after studying the examples, begins to think how best to use it. So it was with me when I needed to upload the data immediately to a temporary file, and not to work with them through a variable. But it is inconvenient to work with a file created in this way because tmpfile()
returns a descriptor, and not a link to a local file. Let's delve a little into the anatomy of the temporary file and look at the pitfalls that I had to face.
The tmpfile()
function creates a resource, as fopen()
does, and works with STDIO I / O streams . This is equivalent to if we opened the php://temp
stream to follow up with the temporary file. In both cases, the file will appear in a temporary folder, which is registered in php.ini
, and will be automatically deleted upon completion of the script or early with the help of fclose()
.
When working with php: // temp, the file will be created in a temporary folder when the size of the data exceeds 2 MB. Prior to this, all recorded data will be stored in php: // memory. This restriction can be circumvented by immediately entering the php: // temp / maxmemory: 0 stream. - php
Since tmpfile()
and fopen()
when creating a temporary file, work with streams, we can use stream_get_meta_data()
to extract metadata and find out the actual path to the file for further manipulations:
<?php // $tmpfile = tmpfile(); // $data = stream_get_meta_data($tmpfile); /* ... */
What values returns stream_get_meta_data()
well described in the documentation , but we are more interested in the file name associated with the stream. It can be retrieved by the key uri
in the array.
Array ( [timed_out] => false [blocked] => true [eof] => false [wrapper_type] => plainfile [stream_type] => STDIO [mode] => r+b [unread_bytes] => 0 [seekable] => true [uri] => home\user\temp\phpDC08.tmp )
In the case of php://temp
we can’t get the URI from the metadata, although the file will actually be created in a temporary folder if its weight exceeds 2 MB. There is no other way to find out where the temporary file is physically stored and under what name when working with streams does not exist.
The actual path to the file may be needed when it becomes necessary to move the temporary file to another location on the disk, i.e., to save the data thus recorded in it. You cannot do this with rename()
, since tmpfile()
imposes a blocking mode on the stream and you cannot cancel it via stream_set_blocking()
, as practice has shown. Of course, you can solve the problem by copying, but then before the end of the script, we will have two copies of the data, if not to take care of the early removal of the temporary file.
Throwing a resource from one object to another is also not very convenient, because for this implementation you will need an interface. In my case, it was necessary to transfer the name of the temporary file with the File class from the Symfony HttpFoundation package to an object that has a strict dependency on the File class in the constructor. The business logic of the application involved a file validation at a different level, and it was important to take care of deleting the file at the very beginning of its path if the check fails. At this stage, it became clear that the tmpfile()
function is not suitable for creating a temporary file.
For an alternative solution, I wrote my own mechanism, which works as follows: creating a file in a temporary folder → any file manipulations → automatic deletion . Create file with a unique name in the temporary folder PHP allows using tempnam()
.
<?php // $tmpfile = tempnam(sys_get_temp_dir(), 'php'); /* ... */
The first argument specifies the location of the temporary folder via sys_get_temp_dir()
, and the second - the prefix in the file name. Such file is available for reading and writing only to the owner, since it is created with the rights 0600 (rw-). To implement automatic file deletion, I suggest transferring further logic to the class, where using __destruct()
we will try to delete the file.
<?php class tmpfile { public $filename; public function __construct() { $this->filename = tempnam(sys_get_temp_dir(), 'php'); } public function __destruct() { @unlink($this->filename); } public function __toString() { return $this->filename; } } // $tmpfile = new tmpfile; // file_put_contents($tmpfile, 'Hello, world!'); /* ... */
The object will return a link to the file that the tempnam()
function has created, tempnam()
__toString()
registered in the class. So we got rid of working with the resource. The file itself will be deleted when all references to the object are released or when the script is completed, but until such time as a fatal error is thrown or an exception is thrown .
The destructor is called when the object is destroyed. In case of critical errors, __destruct () may not be called in PHP7 and below. Destructor should not leave the object in an unstable state. Therefore, in PHP, handlers for the destruction and release of an object are separated from each other. The release handler is called when the engine is completely sure that the object is not used anywhere else. - Objects in PHP7
Delete the file through the destructor is not the best practice, which, by the way, is used in many of the available solutions . For guaranteed deletion of a file, we can register our function, which will be executed in any case after the completion of the script. This is done using register_shutdown_function()
in the constructor of our class:
<?php class tmpfile { public $filename; public function __construct() { $this->filename = tempnam(sys_get_temp_dir(), 'php'); register_shutdown_function(function () { @unlink($this->filename); }); } public function __toString() { return $this->filename; } } /* ... */
This approach allows you to create a temporary file without using tmpfile()
or php://temp
, which is very convenient in OOP. Standard methods are preferable for solving local problems where all logic is encapsulated in a single method or class.
The result was a class for working with a temporary file. I posted the source code in the repository on Gitkhab denisyukphp / tmpfile and added support for CRUD operations to the class. The methods for writing and reading are wrappers for
file_put_contents()
and file_get_contents()
. You can connect to your project through Composer.
<?php require __DIR__ . '/vendor/autoload.php'; // $tmpfile = new tmpfile; // $tmpfile->write('Hello, world!'); // $tmpfile->read(7, 5); // new SplFileInfo($tmpfile); // rename($tmpfile, __DIR__ . '/data.txt'); // $tmpfile->delete(); /* ... */
Source: https://habr.com/ru/post/320078/
All Articles