DevOps Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

The Wheel: Symfony Filesystem

03.28.2013
| 4166 views |
  • submit to reddit

The Filesystem Symfony Component provides an abstraction layer over the plethora of primitive functions that let PHP interact with files and directories. In this issue of The Wheel  series, we will explore a little its API and evaluate its advantages and issues.

The API

The API consists of a single Filesystem object:

  $filesystem = new Filesystem();

This object exposes methods for the manipulation of files and directories basing on their absolute or relative paths.

  $this->filesystem->copy($sourceFilePath, $targetFilePath);

Exceptions are also a part of the API, as you can see from this unit test:

  /**
  * @expectedException \Symfony\Component\Filesystem\Exception\IOException
  */
  public function testMkdirCreatesDirectoriesFails()
  {
  $basePath = $this->workspace.DIRECTORY_SEPARATOR;
  $dir = $basePath.'2';

  file_put_contents($dir, '');

  $this->filesystem->mkdir($dir);
  }


As all Symfony Components, the library can be installed via Composer. It does not have external dependencies, but uses Composer to set*up its autoloading.

Pros

The main feature of this component is probably the portability of filesystem-related code between Unix system and Windows. Symfony, in fact, is an open source product that relies on it in order to be shippable on different operating systems.

Another Java-esque advantage of this little library is that introduces exceptions, raising IOException instances when encountering errors, and marking it with the interface Symfony\Components\Filesystem\ExceptionInterface. This seems to be the standard naming, for the interface of all exceptions thrown by the component.

However, besides portability and exception support, Filesystem also contains some little abstractions that you would have to implement over and over in your code. Every method has its gem:

  • copy() a file, but only if it's newer than the destination
  • make a directory with mkdir(), but recursively create parent directories if they do not exist (mkdir -p)
  • Check if a file exists(), or if many files all exist, transparently passing Traversable and arrays.
  • Recursive remove() a directory and all its contents (rm -rf filename)
  • mirror() functionality  to duplicate a directory (cp -r)

Synthesizing, the library avoids you having to make exec() calls to `cp`, `rm` and other commands for desperation. Not having to build the commands yourself means you don't have to escape shell arguments and can manage errors in a more precise way than by parsing the standard error of a command or mapping return codes to a list of messages.

Cons

In this move to provide objects over the native PHP API, I would have expected to find a File or Directory object as an abstraction, but only Filesystem is available. Introducing Filesystem is probably more consistent than the sparse primitive functions as all functionality is in the same place and you can search for new methods to call in a single file.

However, keep in mind that nowadays we are moving towards shared nothing servers that can be put in a cluster to work in parallel, so writing files is not that key functionality. I see these needs come up mostly in testing, build automation, and in a few components such as caches who may however require optimizations that skips the Filesystem object. If you do not need portability, or require Filesystem in Utility instead of Strategic code, it's probably going to be easier and more performant to just perform exec() calls to Unix commands for these isolated tasks.

Unit testing

Unit testing is made easier by this component, as the Filesystem object is a seam where you can break the test before it reaches a slow resource such as the filesystem. If you follow the Only Mock Type You Own principle, you should always define your own interface towards the Filesystem, implemented by an Adapter object composing Filesystem.

On the other hand, you will be able to reproduce errors by injecting a Test Double for Filesystem that throws IOException objects; it's going to be easier than to reproduce bound conditions that make unlink() or exec(). Speed is not the issue here as the risk is in the integration with the real resource; in a single shot you can make sure your code deals with IOException (by unit testing it) and that error conditions are signaled with these exceptions instead of parsing them yourself (by trusting open source code).

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)