Reading and Writing Files

Reading and Writing Files

===

Reading and Writing Files

The PHP file_get_contents() API function is one of the easiest and most common ways to read a file. It was added in PHP 4.3.0 and was immediately popular so only very old PHP 4 code does not use it. The following PHP code reads the file named data.txt from the same folder that the PHP file is in. The file is read as a long string that is assigned to the $contents variable:

$contents = file_get_contents ( 'data.txt' ); print $contents ;

If a file named data.txt does not exist, a false boolean value is assigned to the $con tents variable. So, if the data.txt file exists and is readable, a string is returned, but if the data.txt file does not exist or is not readable, a boolean is returned.

As discussed previously, PHP API functions, including file_get_contents(), block until they return. The PHP code after the PHP file_get_contents() API function call does not get executed until the PHP file_get_contents() API function call either completely succeeds or completely fails. There is no callback mechanism for this PHP API.

The previous PHP statement is converted in the following Node.js code. The readFile Sync() API function in the fs module is the closest Node.js equivalent to the PHP file_get_contents() API function:

try { contents = fs . readFileSync ( __dirname + '/' + 'data.txt' , 'utf8' ); } catch ( err ) { // do nothing } console . log ( contents );

Many Node.js API functions come in two forms: an asynchronous and a synchronous version. The asynchronous version is the default, unadorned name. The synchronous version has the word “Sync” appended to the end of the asynchronous name. In this case, the Node.js fs.readFile() API function is the asynchronous version and the Node.js fs.readFileSync() API function is the synchronous version.

The asynchronous version does not block and relies on callbacks to handle the results of the Node.js API function call. The synchronous version actually does block, just like PHP does, and Node.js code after the API function does not execute until the API function call either completely succeeds or completely fails. No callback mechanism is used for the synchronous version: it is not needed.

For the Node.js fs.readFileSync() API function, the first argument is the filename to read and the second argument is the encoding to use. By default, the second argument does no encoding, which causes the function to return a raw buffer of bytes instead of

a string. Instead, the 'utf8' string (for UTF-8 encoding) is passed as the second argu‐ ment to cause the function to return a string.

The first Node.js argument prepends the data.txt filename with the __dirname variable and a path separator, a forward slash (/). In Node.js, the __dirname variable contains the directory name of where the current Node.js source file, the .njs file, is located. Node.js can use both a forward slash (/) or a backward slash (\) as a directory name separator. In a string, a literal backward slash has to be delimited by another backward slash (\) to make a double backward slash (\\) because a backward slash is a delimiter for other things such as a newline (\n).

In PHP, a relative filename corresponds to the directory that the PHP file is in. For example, if the index.php file is in the common directory and the PHP file_get_con tents() API function is invoked in the index.php file with the argument data.txt, the data.txt file will be searched for in the common directory. However, in Node.js, a relative filename corresponds to the directory where the Node.js executable was run. So, re‐ gardless of where an index.njs file is located that calls the Node.js fs.readFileSync() API function, it will always look in the directory where the Node.js server was started. To access the data.txt file in the same directory as the .njs file, the __dirname variable and the forward slash (/) path separator must be prepended.

178 | Chapter 9: File Access

For the synchronous versions of the Node.js API functions, including the fs.read FileSync() API function, Node.js exceptions are used. The Node.js try keyword and the Node.js catch keyword are used to handle exceptions. The Node.js try keyword and the “try block” wrap Node.js code that will always be executed but where a Node.js exception might occur and be thrown. If a Node.js exception does occur while the Node.js “try block” is executing, the Node.js “try block” will immediately stop executing and the Node.js catch keyword and the “catch block” will start to be executed. If a Node.js exception does not occur, the Node.js catch keyword and the “catch block” will

be ignored and never executed. PHP 5 supports exceptions but they are rarely used. Very few of the PHP API functions

use PHP exceptions. PHP 4 does not support PHP exceptions. In the example that uses the Node.js fs.readFileSync() API function, the contents

variable is initialized to false and remains false when a Node.js exception is thrown. The PHP file_get_contents() API function returns false, so both the PHP and Node.js code perform the same when there is an error.

While replacing the PHP file_get_contents() API function with the Node.js fs.read FileSync() API function is convenient, the Node.js fs.readFile() API function is preferable. When a synchronous API is invoked in Node.js, the entire Node.js server blocks and waits for the synchronous API call to complete. During that time, the Node.js server is frozen when it could be handling new requests or doing some other useful work.

The following code uses the asynchronous Node.js fs.readFile() API function:

var fs = require ( 'fs' ); var contents = false ;

fs . readFile ( __dirname + '/' + 'data.txt' , 'utf8' , function ( err , data ) { if ( ! err ) { contents = data ; } console . log ( contents ); });

The synchronous Node.js fs.readFileSync() API function returns the data as the return value and uses the Node.js try keyword and the Node.js catch keyword to in‐ dicate an error. The asynchronous Node.js fs.readFile() API function forgoes those mechanisms and, instead, accepts a third argument, a callback function, to both return the data and indicate an error.

In earlier chapters, the concept of linearity was introduced to encourage PHP code to

be refactored to be more linear. If the PHP code is made linear, the asynchronous Node.js APIs, like the Node.js fs.readFile() API function, is nearly as easy to use as the syn‐ chronous Node.js APIs.

Reading and Writing Files | 179

The original PHP code is fully linear because only a single PHP print statement exists after the blocking PHP file_get_contents() API function call. When this PHP code is converted to Node.js, this PHP print statement can be moved into the callback func‐ tion of the Node.js fs.readFile() API function.

To convert the PHP file_get_contents() API function into Node.js, first insert a Node.js require() API function call near the top of the .njs file. The following Node.js code loads the fs built-in module, “fs” being short for “filesystem”:

var fs = require ( 'fs' );

Next, execute the following find-and-replace action to convert the PHP file_get_con tents() API function calls into Node.js fs.readFile() API function calls:

Operation: "Find/Replace" in Eclipse PDT Find: file_get_contents( Replace: fs.readFile( Options: None Action: Find, then Replace/Find

At each occurrence, apply the linearity concepts from previous chapters to correctly implement the Node.js callback function that replaces the PHP return value. For the previous example, the PHP $contents variable that is assigned the return value is con‐ verted to the Node.js contents variable that is used in the callback function passed as the third argument to the Node.js fs.readFile() API function call. Also, insert the __dirname constant before the first Node.js argument, if required.

There is a third way to read a file in Node.js, although it has no corresponding PHP feature. A Node.js stream can be created to read a file. Events, such as the data, close, and error events, are sent while the stream is read. Event handlers can be set up to handle these events.

The following code creates a Node.js stream for reading:

var fs = require ( 'fs' ); var stream = fs . createReadStream ( __dirname + '/' + 'data.txt' , {

'encoding' : 'utf8' });

The filename is passed as the first argument to the Node.js fs.createReadStream() API function. The second argument is a Node.js object that specifies the options, like encoding, flags, mode, and bufferSize.

Subsequent Node.js code creates a contents variable and the cb callback function. The default value of the contents variable is false, which will be the value returned if the file cannot be read. The callback function contains the Node.js code to run after the stream is read:

180 | Chapter 9: File Access 180 | Chapter 9: File Access

var cb = function () { console . log ( contents ); };

Next, the Node.js stream error event is attached to a function that calls the Node.js cb callback function. The Node.js contents variable will be reset to false in case it has been changed:

stream . on ( 'error' , function ( err ) {

contents = false ; cb (); });

Then, the following Node.js code attaches a function to the Node.js stream data event. Multiple Node.js stream data events may be sent. Each Node.js stream data event will contain the next portion or the reminder of the file in the chunk variable:

stream . on ( 'data' , function ( chunk ) { if ( contents === false ) {

contents = '' ; } contents += chunk ; });

Each “chunk” is added to the contents variable. When the last Node.js data event is received, the contents variable will contain the entire contents of the file.

Finally, the Node.js stream close event is caught and the cb callback function is called when the file is closed:

stream . on ( 'close' , function () {

cb (); });

The Node.js stream close event code can be rewritten to call the cb callback directly by passing the cb callback function as the second argument:

stream . on ( 'close' , cb );

PHP does not have any equivalent to the Node.js fs.createReadStream() API function. The PHP file_put_contents() API function complements the PHP file_get_con

tents() API function. It writes data to a file just as the PHP file_get_contents() API function reads data from a file. Even though the PHP file_get_contents() API func‐ tion was introduced in PHP 4.3.0, the PHP file_put_contents() API function was added in PHP 5:

$s = 'the quick brown fox jumped over the lazy dog' . PHP_EOL ;

file_put_contents ( 'fox.txt' , $s );

print 'File written.' ;

Reading and Writing Files | 181

The PHP_EOL constant is the end-of-line marker for the operating system that the web server is running on.

The Node.js fs.writeFileSync() API function is almost a drop-in conversion for the PHP file_put_contents() API function. However, as with the reading files, it is pref‐ erable to use the nonblocking version, the Node.js fs.writeFile() API function.

The following code uses the asynchronous Node.js fs.writeFile() API function:

var fs = require ( 'fs' ); var os = require ( 'os' );

var s = 'the quick brown fox jumped over the lazy dog' + os . EOL ;

fs . writeFile ( __dirname + '/' + 'fox.txt' , s , function ( err ) {

console . log ( 'File written.' ); });

The Node.js os built-in module contains an EOL constant that serves the same purpose as the PHP_EOL constant.

To convert PHP_EOL to Node.js, first insert the Node.js require() API function call to include the os built-in module, “os” being short for “operating system”: