The Wheel: Symfony Filesystem
Join the DZone community and get the full member experience.
Join For FreeThe 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).
Opinions expressed by DZone contributors are their own.
Comments