Mastering the Art of File Management and Stream Handling
The content here is under the Attribution 4.0 International (CC BY 4.0) license
Using files is a common task for any application to write logs or save user data. We can read, write, move and so on. In this chapter, we will see how PHP behaves when interacting with files and directories of the operating system it is running on.
In addition to this interaction, we will understand how the input and output (I/O) operation works with PHP, and how we can use streams to make our lives easier.
Streams are delicate topics with PHP, as even its official documentation has flaws and even a lack of documentation.
Manipulating files
As usual, we have numerous functions for manipulating files in PHP, and the first thing to note is the function types. We have two types of function notation: those that start with the letter f, such as fopen, fstat, fwrite; and those that start with file, such as file_put_contents, file_get_contents etc. Let’s look at a practical example to understand this difference:
$file = fopen('/foo/bar/my_arquivo.txt', 'r');
We can see that it is simple to use the fopen function to manipulate a file. When using this function, we can pass as a second parameter which way we want to manipulate the file.
Mode | Description |
---|---|
r | Handles the file only in read mode |
w | Manipulate the file in writing mode |
r+ | Manipulates the file in read/write mode and places the pointer at the beginning of the file |
w+ | Manipulates the file in writing mode, resets the file contents, places the pointer at the beginning of the file and, if the file does not exist, it is created |
a | Manipulates the file only in writing mode, places the pointer at the end of the file and, if the file does not exist, it is created |
a+ | Manipulates the file in writing and reading mode, places the pointer at the end of the file and, if the file does not exist, it is created |
x | Handles the file for writing, places the pointer at the beginning of the file and is only written to the file if it does not exist |
x+ | Handles the file for reading only. If the file exists, false is returned (generating a WARNING ) and nothing is done to the file; otherwise, PHP tries to create the file |
We can use each item in this table to manipulate the file however we want. See the code below and try to answer what the result is.
$file = fopen('file.txt', 'r');
fwrite($file, 'Content to be inserted');
fclose($file);
What do you think we get as a result of running this code? If you thought that the Content string to be inserted will be written to the file, unfortunately you were wrong. Notice the way we are manipulating the file, r. In this mode, we can only read the contents of the file, not write.
In this example, we use the fwrite function to write to our file, which accepts as its first parameter a resource (a pointer to the file where we want to write) and the content to be written. Furthermore, we can specify the limit of bytes we want to be written to the file.
$myFile = fopen('/foo/bar/arquivo.txt', 'w');
fwrite($myFile, 'This is the content of my string', 4);
fclose($myFile);
This time, as we are specifying the maximum number of characters we want to write to the file, the fwrite function will only write the word Esse, which represents the 4 bytes entered in the third parameter.
If the third parameter is not provided, fwrite will write to the file until the content of the string runs out.
We could also have used the fputs function, which is just an alias for the fwrite function. In short: if you use fputs, be aware that it will call the fwrite function internally.
Now that we know how to write, we can also read files using the fgets function:
$myFile = fopen('/foo/bar/my_file.txt', 'r');
while(feof($myFile) !== true) {
print fgets($myFile);
}
fclose($myFile);
Unlike the fwrite function (which writes to a file), we use the fgets function to read the contents of a file. In our example, we created a pointer to the file meu_arquivo.txt and assigned it to the variable $meuArquivo. Then, we use the while loop to read the entire contents of the file until the end of the file is reached (we check if we have reached the end of the file using the feof function).
Now that we know the basics about reading/writing, let’s delve a little deeper into the ways PHP manipulates files. So far, we have used the most basic manipulation modes, which are r and w. The next one we’re going to look at is r+.
r+
The first thing we must pay attention to when using this mode is that it is not only used for reading, like r
, it is also used for writing. However, as described, when using r+
, we will start writing at the beginning of the file. Let’s see what this means in practice. To illustrate, I will use a file called arquivo.txt
and its content is a list:
Matheus
Marabesi
PHP
Let’s then change our file by adding one more item to this list:
$file = fopen('file.txt', 'r+');
fwrite($file, 'My new item');
fclose($file);
Can you guess what happened to the contents of our archive? When we run this code, our file will have the following content:
My new itemesi
PHP
Did you realize what happened? With the pointer at the beginning of the file indicated by fopen
, the fwrite
function wrote our string at the beginning of the file and, as it was longer than the existing one, a piece of Marabesi
was overwritten, thus leaving only the esi
at the end of the string. Also be careful when using this mode, because if the desired file does not exist, it will not be created and a WARNING
will be displayed:
PHP Warning: fopen(arquivo.txt): failed to open stream: No such file or directory in /my_dir/files/fopen_r+.php on line 3
PHP stack trace:
PHP 1. {main}() /my_dir/files/fopen_r+.php:0
PHP 2. fopen() /my_dir/files/fopen_r+.php:3
Warning: fopen(arquivo.txt): failed to open stream: No such file or directory in /my_dir/files/fopen_r+.php on line 3
Call Stack:
0.0002 230128 1. {main}() /my_dir/files/fopen_r+.php:0
0.0002 230272 2. fopen() /my_dir/files/fopen_r+.php:3
Stay tuned, as we will use the contents of
arquivo.txt
for the following examples about file manipulation.
w+
Using w+
, we manipulate the file for reading/writing, and the effect is the same as the example indicated previously, when using r+
. That is, let’s write at the beginning of the file:
$file = fopen('file.txt', 'w+');
fwrite($file, 'My new item');
fclose($file);
Taking into account the list presented in the previous example, using +r
, try to interpret what will happen to the file before moving on to the answer.
Basically, what will happen is that the w+
mode will reset the entire content of the file if it exists, position its pointer at its beginning and write the new content, in our case, the string My new item
.
In other words, we have the following in our file.txt
:
My new item
Unlike r+
, with w+
, if the file does not exist, it will be created and no WARNING
will be displayed.
a and a+
In PHP, both a
mode and a+
mode behave in the same way: both use the file only for writing (reading is not allowed) and the pointer is placed at the end of the file, before writing.
$file = fopen('file.txt', 'a');
fwrite($file, 'My new item');
fclose($file);
After running this script, we will have the following result:
Matheus
Marabesi
PHPMy new item
And just like in w+
mode, we do not receive any WARNING
, because, if the file does not exist, it is created.
x and x+
Once again, we have exactly the same behavior in both modes, both in x
and x+
. Through these modes, we are able to have greater control when reading a file if it already exists, as a WARNING
is launched if the file exists.
According to the previous examples, our file.txt
already exists, so let’s use the x
mode to see the result we get:
$file = fopen('file.txt', 'x');
fwrite($file, 'My new item');
fclose($file);
When we run the script, we have the following result:
PHP Warning: fopen(arquivo.txt): failed to open stream: File exists in /my_dir/files/arquivo.php on line 3
PHP stack trace:
PHP 1. {main}() /my_dir/files/fopen_x.php:0
PHP 2. fopen() /my_dir/files/fopen_x.php:3
Warning: fopen(arquivo.txt): failed to open stream: File exists in /my_dir/files/fopen_x.php on line 3
Call Stack:
0.0001 230128 1. {main}() /my_dir/files/fopen_x.php:0
0.0001 230272 2. fopen() /my_dir/files/fopen_x.php:3
If the file arquivo.txt
does not exist, when executing this script, PHP will create it and write "My new item"
in it. Remember that both x
and x+
mode have the same behavior.
If you are looking for more information about file manipulation modes in PHP, see the official documentation at http://php.net/manual/function.fopen.php.
fclose
Have you ever stopped to think about what will happen if I forget to close my resource? As we have seen, we use functions
from the f\*
family a lot and, to do so, we must open a pointer through the fopen
function. With this, we obtain
our resource to manipulate the file. After that, we must close the resource using the fclose
function, so as not to
leave our resource open.
But what happens when we forget to close the resouce? See our example below, which opens our file in read mode, but
never uses the fclose
function to close the pointer:
fopen('my_file.txt', 'w');
If you ran this script, you will notice that no errors occur and the PHP script ends normally.
Basically, when the script finishes executing, PHP will close it automatically. However, it is good practice and highly recommended to close manually after opening the file to avoid problems, such as PHP having its execution interrupted in an unexpected way, such as throwing an exception.
try {
$file = fopen('livro.txt', 'r+');
} catch (\Exception $error) {
print $error->getMessage();
}
Imagine that we open this file to update the content of book.txt
with the data that the user wants when we receive a POST
request on our server.
Receive POST request --> Open the file
|
$file = fopen("book.txt', 'r+');
|
\/
Performs data processing
|
|
\/
Unexpected error (exception) --> No --> Ends the execution
| |
| |
\/ |
Yes |
The pointer is still open, it is possible -----------
to make some modifications to the .txt file
Note that, in the flow, if an exception occurs (catch
block in our example), the pointer is still open and it is
possible to make any unwanted modifications to the file. You can find some user contributions in
the official documentation.
try {
$file = fopen('livro.txt', 'r+');
} catch (\Exception $error) {
fwrite($file, 'Pointer is still available');
}
filet_*
file_put_contents
is an alternative for manipulating the file, but, unlike fopen
, we only use two parameters: one with the file location, and the other with the content to be written. An advantage of functions that do not use resources is that it is not necessary to close them after using them with the fclose
function. Look at the example where we used fopen
and compare the use of the two functions.
Let’s take an example here: a text file with the following content, called my_file.txt
:
Contents of my file using file_get_contents
And now let’s display it in our PHP file:
$content = file_get_contents('/foo/bar/my_arquivo.txt');
print $content;
Streams
In computing, streams are defined as “a continuous flow of data” and, in PHP, they were added from version 4.3.0 to standardize the manipulation of files, sockets, HTTP protocol, among others. With streams, we have a series of facilities to manipulate any type of source for reading/writing today, and if your needs are not met, you can create your own stream. Amazon, for example, has its own for S3 services (https://aws.amazon.com/pt/s3/).
The first thing to note when using streams in PHP is its syntax. Many programmers probably already use streams to consume an API (Application Program Interface) from a social network, to read a file, or even to create a robot that consumes data from a website.
As we will see throughout this section, we can use functions such as fopen
, fgets
, fputs
and fclose
, with a series of wrappers, and not just for file manipulation, as we are used to seeing.
http://whatever.api.com
As incredible as it may seem, this is the syntax used. First we specify the wrapper (schema) we want and then our target (target). In this example, we are using the HTTP wrapper to consume a fictional API, in short we use the wrapper://target
syntax
You can refer to wrappers as protocols if you prefer, but don’t forget that in the test the word wrapper is the correct one to use.
The following table shows us which types of wrappers are natively supported by PHP.
Protocol | Description |
---|---|
file:// | Used to manipulate a local file |
http:// | Used to manipulate HTTP protocol content |
ftp:// | Used to manipulate files on an FTP |
php:// | Used to manipulate input/output (I/O). Using this wrapper, we can retrieve information from the global $_POST , $_GET , or even write to the standard output of PHP |
zlib:// | Used to manipulate compressed files, such as files with the extensions .gz , .bz2 or .zip
|
data:// | Used to manipulate explicit content, such as strings or base64-encoded strings |
glob:// | Used to search directory paths given a pattern |
phar:// | Used to manipulate .phar files |
rar:// | Used to manipulate files with the extension .rar
|
if no wrapper is specified, PHP will use the
file://
pattern implicitly.
Adding context
A very interesting part of using streams is the contexts we can create to manipulate them. Context is what brings our stream to life, what allows us to “decorate” our stream however we want. We can, for example, create a stream to access an FTP server, but to do so, I need to authenticate. Authentication is the part where we decorate our stream with the necessary username and password, that is, we end up creating an authentication context.
We use the stream_context_create
function passing two (optional) arrays as arguments to create a context in PHP to apply to a stream.
$options = [];
$parameters = [];
$context = stream_context_create($options, $parameters);
The first array must have an associative array with the wrapper we want to use. Let’s assume that we will create a context to use with FTP:
$options = [
'ftp' => [
'proxy' => 'http://proxy:3128'
]
];
$context = stream_context_create($options);
And to use our stream, we can use any PHP function that accepts a stream as a parameter (file_get_contents
, file
and fopen
are some of these functions):
print file_get_contents('ftp://my.ftp.server', false, $context);
Using streams
The simplest way to use a stream is to manipulate any file. Let’s suppose I have a file called random_file.txt
, with the content USING STREAMS
:
print file_get_contents('random_file.txt');
As we saw, we can specify or not a wrapper to be used and, if it is not specified, PHP will use file://
automatically. Thus, when executing the previous script, we will have the output USING STREAMS
.
print file_get_contents('file://random_file.txt');
Now, with this example, we get the same result, but we are explicitly using the file://
wrapper.
http://
I believe that today the http
wrapper is one of the most used in applications. Yes, it is a wrapper.
$site = file_get_contents('http://marabesi.com');
You’ve probably already done something similar to return the date from an API, or simply get content from a website like the one described with the curl
extension (http://php.net/manual/en/book.curl .php). As incredible as it may seem, we are using a stream and, internally, PHP manages all of this for us.
By default, when using http://
, the method used for the request is GET
. However, we can create a context so that it is possible to use other HTTP verbs, such as POST
, PUT
or DELETE
.
$context = stream_context_create(
'http' => [
'method' => 'POST'
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => 'book=php'
]
);
print file_get_contents('http://marabesi.com', false, $contexto);
For more information about HTTP verbs, you can consult the official W3C documentation at http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html.
ftp://
In the modern development environment, we rarely touch FTP files. Instead, we use version control, which provides us with numerous advantages. However, there are many hosting servers that only provide FTP access for files to be uploaded and made available on the web. Fortunately, PHP has a specific wrapper for this, in which we can read and create files freely.
The first thing we’re going to do is read the contents of a file called index.php
:
$fp = fopen('ftp://usuario:senha@meu_servidor.com/home/matheusmarabesi/index.php', 'r');
print fgets($fp);
One thing is clear: it is very easy to read the contents of the file on an FTP, all you need is the correct information and that’s it! However, as you may have noticed, the string we used to make the connection and read the desired file was a little complex, so let’s go in parts.
To access a file without any authentication on the FTP server, it is very simple, just enter the FTP host and the location of the file:
$ftp = fopen('ftp://my_server.com/var/www/arquivo.txt');
See that we have two distinct parts in the string. The first is the host my_server.com
and then the full path to where the file is located, /var/www/arquivo.txt
. But the reality is that FTP servers have authentication where it is necessary to enter the username and password. To do this, let’s change our string with this data:
$ftp = fopen('ftp://usuario:senha@meu_servidor.com/var/www/arquivo.txt');
Notice that now the first thing we see in our string is the username and password separated by a colon (user:password
). Right after that we use an @
sign to inform our server and the location where the file we want is located.
php://
Using the php://
wrapper, we can manipulate input and output in different ways that can be used both in applications developed for the web (which are accessed via browser) and for applications that use the command line.
We have some global variables, such as $_POST
and $_GET
, which we use to access the data that the user sent us. However, with some other methods, we need to deal directly with standard output/input, such as the PUT
method that is used to perform updates to records when implementing a REST architecture:
$put = fopen('php://input', 'r');
print fgets($put);
With just two lines, we can read any type of input sent to our script. See that we use the fopen
function in conjunction with fgets
, but we can use file_get_contests
, as shown in the following example:
print file_get_contents('php://input');
compress.zlib
Let’s assume that you are not going to read a simple file from the operating system, but now you will have to manipulate a file compressed with the tar.gz
extension. With streams in PHP, this becomes very easy. See our example:
$ArquivoComprimido = fopen('compress.zlib://arquivo.tar.gz', 'r');
while(feof($compressedfile) !== true) {
print fgets($compressedFile);
}
As usual, the first thing we will do is use the fopen
function in conjunction with the compress.zlib
wrapper to create a resource. After that, we just use the while
loop to display the entire contents of the file compressed by the fgets
function.
Just one item to take into consideration about the previous code is the use of the zlib
wrapper. Note that this wrapper doesn’t just use its name, we must use compress.zlib
, and the same applies to files compressed with bzip2
. When executing the previous code, we have the following output:
file.txt0000777000000000000000000000002112573322761011244 0ustar rootrootmatheus
marabesi
Let’s imagine now that, instead of reading existing files into a compressed file, we are going to add a file and compress it. Once again, PHP makes our lives very simple:
file_put_contents('compress.zlib:///var/www/arquivo.txt.gz', 'I will be compressed!');
After declaring the wrapper, we indicate the full path where the file should be stored after compression. In our example, we save the file arquivo.txt.gz
in the directory /var/www/
.
data://
The data://
wrapper is used when we need to include explicit content in our application as simple text. Furthermore, it makes it easier for us to use content encoded in base64. Let’s look at a simple example first, where we want to display the text Using the data:// wrapper
:
print file_get_contents('data://text/plain, Using the data://' wrapper);
Base64 is a way to encode data to transfer it on the internet. It is generally used to transform binaries (for example, images) to be able to be transmitted where only text is possible (such as HTTP protocol, for example). The 64 in its name exists because only 64-bit characters are used to transform the data.
Take a look at the official base64 documentation that PHP implements, at https://tools.ietf.org/html/rfc989.
So far, no surprises. This seems more complicated than simply concatenating the string, right? However, this wrapper becomes quite interesting when we use content encoded in base64. Let’s assume I’m going to display user-submitted content, but this content is sent in base64. Normally, with PHP, we would do it like this:
$dataEnviadoPeloUsuario = 'VXRpbGl6YW5kbyBzdHJlYW1zIGVtIFBIUCAh';
print base64_decode($dadosEnviadoPeloUsuario);
Very simple! We use the base64_decode
function to do the work for us. However, with the data://
wrapper, this becomes much simpler, see:
print file_get_contents('data://text/plain;base64,VXRpbGl6YW5kbyBzdHJlYW1zIGVtIFBIUCAh');
For more information about the
://data
wrapper, see the complete documentation that PHP implements at http://www.faqs.org/rfcs/rfc2397.html.
glob://
We have a specific wrapper to find files of a certain pattern. To do this, we use glob://
.
But in PHP, we can use SPL (Standard PHP Library) to find files. See our example below, in which we use SPL without using any wrapper to find files of type PHP:
$directory = new \RecursiveDirectoryIterator('/var/www');
$iterator = new \RecursiveIteratorIterator($directory);
$files = new \RegexIterator($iterator, '/^.+\.php/', \RecursiveRegexIterator::GET_MATCH);
foreach ($files->getInnerIterator() as $file) {
print $file->getFileName();
}
However, as you can see, we have to write a few lines. Fortunately, we can achieve the same result by writing much less code via the glob://
wrapper.
$directory = new \RecursiveDirectoryIterator('glob://var/www/*.php');
foreach ($directory as $files) {
print $files->getFilename();
}
We obtain the same result when using streams. Of course, at the end of the day, you decide which one to use. For each situation, we can use one of the two approaches shown here. Don’t worry too much about which approach is better than the other, just focus on understanding that, with glob://
, we can find certain files/paths based on a pattern.
You can check the official manual about the
glob://
wrapper at http://php.net/manual/wrappers.glob.php.
phar://
Currently, the .phar
extension for PHP libraries is widely used. Libraries such as PHPUnit (http://phpunit.de), Behat (http://docs.behat.org/en/v2.5/), Composer (https://getcomposer.org/), among countless others, use this extension to distribute their functionalities.
.phar
files came into existence similar in concept to .jar
files in Java. In other words, a .phar
file is used to distribute a complete PHP application, making it easy to use and not requiring any extra configuration. You simply need to run the .phar
file through PHP.
php phpunit.phar
When executing this command in your terminal, a list of all possible arguments to be used with PHPUnit will be displayed. As the list is large, it is not possible to place it here. But try it yourself: download PHPUnit via the link https://phar.phpunit.de/phpunit.phar, and run it.
Our next step is to create our own .phar
file. To do this, we first need to check some settings.
Check your php.ini
so that it is properly configured to allow file creation. The phar.readonly
directive must have the value Off
.
Let’s then create our file.phar
to understand how PHP packages everything into a single file. See below the structure we will use:
PHP basically takes our entire structure and compresses it into the file.phar
. For this to happen, we need to use the \Phar
class:
$phar = new \Phar('file.phar');
$phar->startBuffering();
$phar->buildFromDirectory('./src', '$(.*)\.php$');
$phar->stopBuffering();
echo 'File saved successfully';
Note that we give the name of the file that will be generated in the constructor of the \Phar
class. Another important detail is in the buildFromDirectory
method, where we define what goes inside our .phar
file. In our case, we are adding all the files with the .php
extension and, at the end of the script execution, we have our .phar
file generated with the PHP files inside it.
To make sure everything was generated successfully, we will use the phar://
wrapper to see if all the files we expect are in fact inside file.phar
.
print file_get_contents('phar://arquivo.phar/index.php');
The first one we are going to check is the index.php
file. If the file.phar
was generated correctly, when we run the previous script, the contents of the index.php
file will be displayed, and the same will occur for the other files we added.
print file_get_contents('phar://arquivo.phar/funcoes.php');
print file_get_contents('phar://arquivo.phar/mascaras.php');
Checking one by one is a lot of work, isn’t it? Imagine if there were 10 files? With this in mind, we can use some features of PHP itself that we used before to list all the files within our arquivo.phar
:
$phar = new \RecursiveTreeIterator(
new RecursiveDirectoryIterator('phar://file.phar')
);
foreach ($phar as $files) {
print $iterator->current();
}
With this script, we have a list of all existing files in our arquivo.phar
, and we can check if it was generated correctly:
|-phar://arquivo.phar/funcoes.php
|-phar://arquivo.phar/mascara.php
\-phar://file.phar/index.php
The use of
.phar
files is very well known among PHP developers, and ended up becoming a standard. I recommend that you take a look at the official documentation to delve even deeper into its use, at http://php.net/manual/en/book.phar.php.
rar://
To use rar://
, we must pay attention to its peculiarities. To manipulate .rar
files, we need to install the extension responsible for manipulating them. If you don’t do this, you will see the following message:
PHP Warning: file_get_contents(): Unable to find the wrapper "rar" - did you forget to enable it when you configured PHP? in /my_dir/streams/rar.php on line 5
PHP stack trace:
PHP 1. {main}() /my_dir/streams/rar.php:0
PHP 2. file_get_contents() /my_dir/streams/rar.php:5
What this message tells us is that PHP was unable to find the wrapper we want to use (Unable to find the wrapper "rar"
) and it even gives us a hint, asking if we forgot to enable this extension in the PHP configuration ( did you forget to enable it when you configured PHP?
).
Therefore, if this message is displayed, check if you have this extension correctly configured in your php.ini
, as without it it is not possible to use this wrapper.
If the error still persists, you will need to install the extension through PECL. Unfortunately, we will not cover how to install/configure the extension, as this depends on the environment you use. However, you can check the official PHP documentation for more information, at http://php.net/manual/install.pecl.intro.php.
Well, now that we have everything we need, let’s read the contents of an existing file within a .rar
archive:
$directory = '/foo/bar';
print file_get_contents('rar://' . rawurlencode( $arquivo ) . '/server.rar#server.log');
In our example, we are reading an existing file within the server.rar
file that contains the server’s access logs. But you must be thinking: “What a strange way to access the file!”, right? So, let’s understand the pattern used a little better:
rar://<path to file>#<file name>
We use an example that transforms the file path to a URL pattern using the rawurlencode
function. In the official PHP documentation, a recommendation is that you use this type of approach, however, this is not mandatory. See our example without encoding the file path:
print file_get_contents('rar:///foo/bar/server.rar#server.log');
Both examples produce the same result. We saw how to access the contents of a single file, but what if within the .rar
file there are files within subfolders? With our wrapper, this becomes an easy task. See the following example where we combine the use of some SPL classes with the use of the rar://
wrapper:
$directory = new \RecursiveTreeIterator(new RecursiveDirectoryIterator(
'rar:///foo/bar/my_arquivo.rar#'
));
foreach ($directory as $file) {
print $file;
}
To make the work easier, we created a RecursiveTreeIterator
(a class made available by PHP since version 5 that is part of the SPL set of classes) to access our files within the subfolders recursively. Note that, this time, we do not specify the file we want after the #
sign. With this, we were able to list all existing files in my_arquivo.rar
.
\-rar:///foo/bar/my_arquivo.rar#/my_folder
\-rar:///foo/bar/meu_arquivo.rar#/minha_pasta/acesso.log
See that in our example a folder was found with the name my_folder
and, within it, there is only one file, with the name acesso.log
.
You can check the official PHP documentation about
rar://
at http://php.net/manual/wrappers.rar.php.
SSH
With the ssh2://
wrapper, it is possible to connect to servers using the SSH protocol in a simple and elegant way. See an example of how to access the contents of a file:
$ssh = ssh2_connect('192.168.1.106');
ssh2_auth_password($ssh, 'zend', '123456');
$ftp = ssh2_sftp($ssh);
$file = fopen("ssh2.sftp://$ftp/foo/bar/teste.txt", 'r');
while($line = fgets($file)) {
print $line;
}
The first thing we need to do is create a connection, and we do this using the ssh2_connect
function. See that, in our example, we connect to a server that has the IP 192.168.1.106
.
If your server requires authentication (as ours does), use the ssh2_auth_password
function passing the created connection, username and password as parameters. In our example, we pass the variable $ssh
as a connection, and the credentials zend
for the user and 123456
for the password.
Now our ssh2.sftp://
wrapper comes into play, which makes everything easier. Just use the fopen
function to read the file you want from the server, passing the connection ($ssh
) created and the full path of the file (/foo/bar/teste.txt
). In the end, we have the following string: ssh2.sftp://$ftp/foo/bar/teste.txt
.
The
ssh2
wrapper has some variations such asssh2.shell://
, which is used to access the shell through SSH;ssh2.exec://
, which provides us with the possibility of executing commands on the server we are connected to; andssh2.tunnel://
, to connect to an existing SSH session. Try using these variations to fix the content you just read and, of course, access the official documentation at http://php.net/manual/en/wrappers.ssh2.php, as this is a very rich source of information.
Creating a wrapper
Although PHP offers a good range of wrappers, such as http://
, ftp://
, phar://
among others, in some cases you will want to create your own. In this topic, we will take a look at how we can do this and what precautions we should take.
To do this, the first thing we must pay attention to is the base class that the documentation offers us.
streamWrapper {
public resource $context;
__construct (void)
__destruct (void)
public bool dir_closedir (void)
public bool dir_opendir (string $path, int $options)
public string dir_readdir (void)
public bool dir_rewinddir (void)
public bool mkdir ( string $path , int $mode , int $options )
public bool rename ( string $path_from , string $path_to )
public bool rmdir (string $path, int $options)
public resource stream_cast ( int $cast_as )
public void stream_close (void)
public bool stream_eof (void)
public bool stream_flush (void)
public bool stream_lock (int $operation)
public bool stream_metadata ( string $path , int $option , mixed $value )
public bool stream_open ( string $path , string $mode , int $options , string &$opened_path )
public string stream_read ( int $count )
public bool stream_seek ( int $offset , int $whence = SEEK_SET )
public bool stream_set_option ( int $option , int $arg1 , int $arg2 )
public array stream_stat (void)
public int stream_tell (void)
public bool stream_truncate ( int $new_size )
public int stream_write ( string $data )
public bool unlink ( string $path )
public array url_stat ( string $path , int $flags )
}
The methods we should pay attention to are:
-
stream_open
– This method is invoked when our wrapper is initialized, using thefopen
,file_get_contents
,file
etc. functions, for example. -
stream_read
– When a function is used to read using streams, such asfgets
orfread
, this is the method invoked within our class. -
stream_eof
– This function is executed when we use thefeof
function on some resource. If your stream does not use a resource, you probably will not need this method, but it must be implemented.
Now that we know the basics of the methods needed to create our own wrapper, let’s create one that we will use to find out whether a user is PHP certified or not.
class My {
private $file;
public function stream_open($file, $mode)
{
if (!file_exists($file)) {
throw new \Exception('The specified file does not exist');
}
$this->file = fopen($file, $mode);
return true;
}
}
The first thing we must do when creating a wrapper is to implement the stream_open
method, as it is the first method to be executed when using the stream. Note that in our example we are using the fopen
function, which also works with streams.
Let’s now actually implement the method that will read the data from the pointer opened by the fopen
function and add our logic to check whether the person is certified or not:
public function stream_read()
{
}
The next method we will pay attention to is stream_eof
. This method has the simple task of informing us if the file has already been read to its end:
public function stream_eof()
{
}
Below, you can check the complete code of our new wrapper:
class My_Class
{
public $file;
public function stream_open($path, $mode)
{
$this->file = fopen(str_replace('my://', '', $path), $mode);
if (!$this->file) {
throw new \Exception('failed to open ' . $path);
}
return true;
}
public function stream_read($bytes)
{
return fread($this->file, $bytes);
}
public function stream_eof()
{
return feof($this->file);
}
}
The last step we must perform is to register our wrapper. PHP provides us with a very simple way with the stream_register_wrapper
function, in which we inform the name we are going to use to access the wrapper and the name of the class:
stream_register_wrapper('my', 'My');
The
stream_register_wrapper
function is just a shortcut for thestream_wrapper_register
function.
The first parameter is the name we will use to access the wrapper, just as we use file://
to access files, http://
to access web page content, or phar://
to access files .phar
.
In our case, we will create a wrapper named my
, which will allow us to access it by calling my://
, followed by the file name:
my://any_file.txt
To use our wrapper, it is very simple. Just call it with some function that manipulates files, such as file_get_contents
.
print file_get_contents('my://my_arquivo.txt');
PHP offers an example class to implement your own wrapper. Check out the complete class at http://php.net/manual/class.streamwrapper.php.
Filters
As the name suggests, as we use streams, we can also apply filters to the data that runs through the stream, such as transforming data from lowercase letters to uppercase or adding a filter to remove prohibited words. The possibilities are enormous.
We can use the stream_get_filters
function to find out which filters are available in the PHP environment we are running.
$filters = stream_get_filters();
print_r($filters);
With this, we obtain our list of filters. It depends on how PHP was installed, so if this list is not exactly the same as yours, don’t worry, as it may be affected by the extensions you have installed in your PHP.
If the filter you want to use is not in this list, look for the extension it belongs to, as by installing it, the filter will be available in your PHP installation.
Array
(
[0] => zlib.*
[1] => bzip2.*
[2] => convert.iconv.*
[3] => string.rot13
[4] => string.toupper
[5] => string.tolower
[6] => string.strip_tags
[7] => convert.*
[8] => consumed
[9] => dechunk
[10] => mcrypt.*
[11] => mdecrypt.*
)
To apply a specific filter across streams, we use the stream_filter_append
function. For our example, I will use the
string.toupper
filter to transform all the text in the livro.txt
file into uppercase letters:
// Contents of the book.txt file
php
elephant
zend
certified
engineer
Now that we have the file contents, let’s see how to apply the filter.
$fp = fopen('livro.txt', 'r');
stream_filter_append($fp, 'string.toupper');
print fread($fp, 1024);
After creating our pointer to the book.txt
file, we add a filter to make all letters in the file uppercase with the stream_filter_append
function.
The first parameter to this function is a resource (the file pointer created with the fopen
function), and the second parameter is the name of the desired filter (in our case, string.toupper
). To make sure that the filter was applied, we use the fread
function to display the entire contents of the file after applying the filter.
And the result we get is exactly what we expected:
PHP
ELEPHANT
ZEND
CERTIFIED
ENGINEER
Files, inputs/outputs and streams
In this chapter, you had the opportunity to learn more about working with files in your application. Opening, creating or manipulating files will be present in your daily life, whether for recording system logs or extracting data sent by the user through files. It is important for you to know the patterns that you can use and the PHP functions that will help in this vast world.
In this chapter, we demonstrate how PHP separates file manipulation into two categories: functions that work with pointers to files and functions that use the physical file. We also explored the use of streams to write to files, not only local, but on servers as well, using SSH, filters to apply to the file’s data, and even creating your own wrapper.
Although we learned a lot in this chapter, PHP goes further. PHP supports sockets, and you can check it in the official documentation, at http://php.net/manual/en/book.sockets.php.
As a last tip, see other file manipulation functions in PHP (http://php.net/manual/ref.filesystem.php) and, of course, the object-oriented side of file manipulation with the classes of SPL (http://php.net/manual/class.splfileinfo.php).
Resources
Table of contents
Got a question?
If you have question or feedback, don't think twice and click here to leave a comment. Just want to support me? Buy me a coffee!