WP_Filesystem vs Bare PHP File- Functions

Should we use WP_Filesystem or the build-in file- functions in PHP?

1702 views
d

By. Jacob

Edited: 2019-10-09 00:52

The bare PHP file- functions, file_put_contents() and file_get_contents(), are actually fine to use in many circumstances. This also applies when it comes to Wordpress plugin development. But, it really depends on the circumstances.

There are typically two ways to store data in web applications, one is the filesystem, and the other is in a database. You do not need to store files in the filesystem, you could also store them in a database. But, the standard approach is to store files in the filesystem rather than saving them into a database. Accessing a file in the filesystem is often faster than communicating with a database.

Databases also store files in the filesystem, but they come loaded with useful features, while they typically also handle concurrent users very well.

If you understand the security aspects and know how to handle it, then you probably do not need to use WP_Filesystem. The security problem it is trying to solve should not be handled on the CMS level—it should be handled on the server configuration level.

However, there are many ways you could make an application become weaker by relying too much on filesystem access. One of most serious problems has to do with so-called race conditions doing concurrent use of a file. If two or more users tries to access a file at the same time, data could be lost and corrupted in that file.

See also: How Safe is PHPs File Functions?

Do not just assume it is safe in your circumstances, because there will often be a tiny chance of concurrency issues, and it can be hard to avoid without some sort of file locking.

If you do decide to use the filesystem, be sure to save the files in a place where they will not be deleted when Wordpress or your plugin is updated.

File permissions

Now, the reason for Wordpress' Filesystem API, as far as I can tell, is to "hack around" a situation in some misconfigured hosting environments. The specifics has to do with file ownership in shared hosting environments, as also discussed by Otto in this tutorial: Tutorial: Using the WP_Filesystem

While I agree you probably should not be writing to the file system using the bare PHP file- functions, I do so for different reasons. Of course, if you want to share your plugin with the world, you should aim to use the native Wordpress functions for maximum portability. But there might be technical reasons why you do not want to use Wordpress' wp_filesystem.

When I looked at the code from wp_filesystem, I found they do not handle concurrency when reading or writing is done on files–this makes it hard to rely on wp_filesystem for anything other than "save-once-and-leave" type of files. It is probably fine for settings and theme related files, but files that are changed often could cause problems..

If you look at the source code of the wp_filesystem API, then you will find something like this:

public function put_contents( $file, $contents, $mode = false ) {
  $fp = @fopen( $file, 'wb' );
  if ( ! $fp ) {
    return false;
  }
  mbstring_binary_safe_encoding();
  $data_length = strlen( $contents );
  $bytes_written = fwrite( $fp, $contents );
  reset_mbstring_encoding();
  fclose( $fp );
  if ( $data_length !== $bytes_written ) {
    return false;
  }
  $this->chmod( $file, $mode );
  return true;
}

Source: class-wp-filesystem-direct.php

As you can see in this example, Wordpress is actually using the bare PHP file- functions themselves under the hood, so there is little advantage (besides security) of using wp_filesystem.

The problem with wp_filesystem is, it does not seem to, as of the writing of this article, handle concurrency correctly. The concurrency issue is just as important as the security, because if a race condition arises it could lead to corruption of data. This makes the WP_Filesystem API less than ideal in many circumstances.

Another minor issue is that they are suppressing errors in their functions, without at least handling the most common errors (I.e. is_writable()). This means, if a file read or write operation fails, the user will probably not be able to tell why it failed unless they are developers themselves.

public function get_contents( $file ) {
  return @file_get_contents( $file );
}

Note the use of "@" in front of the file_get_contents() function.

A decent file handler class should handle most errors, since it saves developers the trouble of having to do it themselves, and it avoids lazy solutions like using "@" to suppress errors that might help debug a problem. At a minimum, we should use an if/else to record the specific error, and make it available for easy output/use by the developer. The developer might want provide the error information as a notification to the user somewhere.

Checking for direct file access

Before you try to write to the filesystem, it is a good idea to check if direct access is available. You probably do not want to prompt the user to enter their FTP credentials as this is bad UX. It would also be non-intuitive behavior when what you really want is direct file access (I.e. file_put_contents).

Most users would probably also expect the CMS to silently write to the filesystem, without bugging them about FTP access. So, we should instead show an error message to the user, in case direct access is not possible for some reason.

// First we need to use the wp_loaded hook, making sure get_filesystem_method() is ready
add_action('wp_loaded', 'beamtic_direct_access');

function beamtic_direct_access()
{
    $access_type = get_filesystem_method();
    if (get_filesystem_method() !== 'direct') {
        // Show error if direct access is not available
        add_action('admin_notices', 'beamtic_err_direct_access');
    } else {
        // Direct access was possible
        // Write and read on files from here...
    }
}

And a function to show the admin notice:

function beamtic_err_direct_access() {
  $message = __('Direct access to the filesystem is needed!', 'my-plugin-name');
  $html = <<<HTML_TEMPLATE
  <div class="notice notice-error is-dismissible">
    <p>{$message}</p>
  </div>
HTML_TEMPLATE;

  echo $html;
}

File handling is hard

I am not claiming to know everything about file handling or Wordpress development, but I know you must at least handle concurrency. If you are not doing that, then you might loose data, and your application might crash or break if the data is critical for its operation. This is not a huge problem with Wordpress, since they probably only modify files in maintenance mode when updating–but it could be a problem for plugin developers who are not careful.

Often it is easier to use a database such as MySQL, since concurrency situations are already dealt with, leaving you to focus on coding the queries to communicate with the database.

This does not mean that we should give up. I am personally working on a file handler class to deal with concurrency, as well as learn more about proper file handling.

The concurrency problem was one of the first problems I ever considered as a PHP developer. Already when I was making my first Guestbook application, I realized that it could be a problem if two users attempted to post a comment simultaneously. This is like 15+ years ago now, and I am just starting to learn how to solve it.

The problem probably is, there are so many circumstances and issues to account for that it is hard to come up with a perfect, portable, file handler. This may be why PHP's build-in functions are not handling this stuff already.

Tell us what you think:

  1. How to insert code right after body opening tag, and creating a wrapper around the code in a page using PHP output buffering and the Wordpress shutdown hook.
  2. How to properly remove the title from a Wordpress post, and how to do it for specific post types.
  3. There is no function to select a post by slug name (post name), but we can create our own function to do it; find out how in this tutorial.
  4. How to properly customize Wordpress based website, where to add your own code, and how to override existing functionality.
  5. How to manually move a Wordpress website to another domain name, and copy the database and the files from the old site.

More in: WordPress Tutorials