Filesystem abstraction in PHP, and why it sometimes is useful!

Phase 1: Ignorance and Bias

Recently I came across Flysystem, which is a Filesystem abstraction layer for PHP. Now if you haven’t worked with a Filesystem abstraction layer before (like me), you may ask yourself:

Why on earth should I add even more overhead for something basic as the file system?

Let me explain why it will be extremely useful. Say we have to build a small app in PHP which should download files from an FTP server, process them in some way, and store the result on the servers filesystem. No doubt, this can be done with PHPs build-in functions without a problem, you might say. Sure, but don’t forget the Unit Tes… dang! See?

Phase 2: Skepticism and Curiosity

The solution for our problem once more is: Abstraction. Abstract the process from any file system. Just use the same set of operations on some sort of adapter, no matter where or how the files are stored, and let the Abstraction Layer do the rest. You guessed it already, this is where Flysystem comes into play! Let’s start with a simple file download:


namespace My\Cool\Project;

use League\Flysystem\FilesystemInterface;

class Downloader
     * @var FilesystemInterface
    protected $remoteFileSystem;

     * @var FilesystemInterface
    protected $localFileSystem;

     * @param FilesystemInterface $remoteFileSystem
     * @param FilesystemInterface $localFileSystem
    public function __construct(
        FilesystemInterface $remoteFileSystem,
        FilesystemInterface $localFileSystem
    ) {
        $this->remoteFileSystem = $remoteFileSystem;
        $this->localFileSystem = $localFileSystem;

     * Download files from the remote file system
     * @param string $fileName The filename
     * @return bool
    public function download($fileName)
        $fileDownloaded = false;
        // This is the actual download part!
        if (!$this->localFileSystem->has($fileName)) {
            $fileDownloaded = $this->localFileSystem->writeStream(
        // Done!

        return $fileDownloaded;

Is that it?? Aww yeah, that’s it. When it’s with Flysystem, we only need a few lines of code, because it’s so intense!

To make the code it more clear, I called the filesystems $localFileSystem and $remoteFileSystem, but of course both filesystems could be remote ones, e.g. FTP and Amazon S3. This means that by using Flysystem, you completely decouple your code from the filesystem, so you’re prepared when the Business suddenly decides to “implement some totally easy changes”.

Of course our example is not 100%ly done, since we still have to create the Filesystem adapters we pass upon instantiation. But that’s ok, we need to do the configuration somewhere anyway. Below function is part of another class, which makes use of our Downloader class:

public function downloadSomeFile()
    $ftpAdapter = new \League\Flysystem\Adapter\Ftp([
        'host' => '',
        'username' => '123',
        'password' => 'abc'
    $remoteFileSystem = new \League\Flysystem\Filesystem($ftpAdapter);

    $localAdapter = new \League\Flysystem\Adapter\Local('/tmp');
    $localFileSystem = new \League\Flysystem\Filesystem($localAdapter);

    $downloader = new \My\Cool\Project\Downloader(

This is what I also like about using Flysystem: within the Downloader class we don’t have to deal with any path settings or such. It is completely done within the code which instantiates the Downloader, i.e. in the domain where it belongs.

it's so decoupled

Phase 3: Awesomeness and Why-didn’t-I-use-it-before-ness

I assume most of you didn’t even read until here, since you’ve been totally blown away and immediately started refactoring your code, giggling slightly insane. But wait, we’re not even fully through yet. Remember the question about Unit Tests? With Flysystem, it’s a breeze:

public function testCanDownloadFileFromRemoteFileSystem()
    // This mocks the remote server (could e.g. be an FTP) where we want to 
    // download the file from
    $remoteAdapter = new \League\Flysystem\Vfs\VfsAdapter(new Vfs);
    $remoteFileSystem = new \League\Flysystem\Filesystem($remoteAdapter);

    // Put a file on the virtual remote filesystem
    $remoteFileSystem->put('somefile.txt', 'just some content, does not matter');

    // Virtualize our local file system also, so no need to clean up or anything, 
    // since by using vfsStream, we don't even have to take care where to store 
    // the files during our tests.
    $localAdapter = new \League\Flysystem\Vfs\VfsAdapter(new Vfs);
    $localFileSystem = new \League\Flysystem\Filesystem($localAdapter);

    $downloader = new \My\Cool\Project\Downloader(


Flysystem even provides an adapter for vfsStream, a stream wrapper for PHP which helps greatly when you have to test filesystem-related functionality.

In simple terms, it simulates a file system in memory, which many (but not all) PHP functions can use.

Check it out here if you don’t know it yet: vfsStream on Github

So here we are: we just wrote a test for the FTP download in 5 minutes – without having to change a single line in the Downloader, without relying on a real FTP server. We could exchange the remote filesystem with Dropbox or even your Grandma’s closet within two minutes. Awesome. Now get a coffee and head over to your co-worker, who’s still struggling with the Downloader class and PHPs lovely native FTP support.

npm & Windows

Again, Windows can cause problems you would not have expected. In npm versions prior to 3, npm stores dependencies in deeply nested folder structures, causing errors on running ‘npm install’ such as

npm ERR! Error: EPERM: operation not permitted,
open '/var/www/my_longnamed_app/node_modules/

Obviously, this is a ridiculously long path and exceeds 260 characters – and Windows can’t handle that. Not sure why “they” (you know … MS…) came up with that idea, and why this is still not fixed in a modern OS like Windows 7 (I did not try versions above that). I guess it is just the pure essence of evil, the same diabolic power which brought the Internet Explorer amongst us. Let them burn, let them suffer, and let even pay money for it!

Even when you run npm on Linux in a VM on Windows with a Shared folder, you will still suffer from that. Oh, you beasts from the abyss, how … ok I’ll stop.

Luckily, npm3 supports flat dependencies, i.e. all dependencies are written into the top level of the node_modules folder. So if you run into the above problem, try

npm --version

If this still says 2.x, consider updating to npm3:

npm install -g npm

Delete your node_modules folder (not sure if this is really needed, but better go sure), then run

npm install

and all dependencies get installed without errors. Yay!

If you dare, you can have a look into the node_modules folder now. You’ll find loads of folders – the beauty of flat dependencies.

Local composer package development

A new feature on composer which I really appreciate is the “Path” repository type.

Imagine you are working on your super-cool-project, and one task requires new functionality where there is no composer package existing yet – yeah, this probably is a bit hard to imagine. Now you want to create your own super-awesome-package, because we all like it nice and separated and such.

So far, I would have developed the super-awesome-package directly inside the vendor folder of my super-cool-project – which somehow did not feel right. Obviously others felt the same, so now we can use the “path” resource type for packages which you develop right at the moment!

  "require": {
    "my-namespace/shoppingcart": "*@dev"
  "repositories": [
      "type": "path",
      "url": "../shoppingcart"

Instead of using VCS as package source, you simply specify the directory (relative or absolute path both work) where your super-cool-package is located on your machine. In my case, I just refer to the shoppingcart folder, where the package is currently developed.

Composer will then add a symlink in the vendor folder upon running composer update, so all your changes on that package can immediately be used in your super-cool-project.

Now that’s what I call convenient!


“Look at her! Isn’t she beautiful? Ahhh, that smell.” Carsten’s fingers were running gently across the black case of their new colleague, a Blade Server with dozens of cores and hundreds of Gigabyte of storage, inspecting all those little details here and there. “I think I’ll call her Babette!”

“Well, finally we got it.” Mike completely ignored Carsten’s gibberish. It has “only” been three month since their Virtual Servers have been “accidently” canceled by “the Admins” in “the HQ”, leaving half of the Software Development department without a reasonable development environment all of a sudden. Good for “the Admins” that HQ was about 800km from their office. Too far away for a spontaneous visit or, let’s say, a Team Building Exercise.

After that incident it was decided to place a server right in the office, so that the Development Team could set up Virtual Hosts as needed and without going the long route via “the Admins”. Everybody was quite excited, yes, even – happy! A way of happiness only developers can understand.

“So, when will the server be configured?” Stephen asked. A good question.

“The Admins told us this will be done next week.” Mike replied.

Just one month later, and after a couple of friendly reminders, they received an email from “the Admins”. The server is now ready to be tested, please let us know if you need anything, et cetera, blabla.

The Devs responded quickly: “Guys, we can’t even access the server with the credentials you gave us. Please tell us what we are doing wrong!”. Although they were no “Admins”, the Devs were a bit skilled with that stuff from their everyday work, but still there was a chance that the error was on their side.

Needless to say that the admins reply came promptly after another week, and yes, after some not-so-friendly-anymore reminders. “Oh well, the server has crashed somehow, but we fixed that now”.
“Buckle up guys, we’re almost there. I can access the server now!” Carsten shouted through the office.

Surely some useful stuff was missing, but at least the basic setup was ok. So everyone inspected it, writing down what would be needed for a standard development environment, which would then act as image for the Virtual Hosts later.

“All right, the email is out. It can now only be a matter of days until we can start working on it.” said Mike, still trying to be optimistic, but with a noticeable amount of skepticism. Too many lessons they learned the hard way.

Days passed.

“What do they mean, we are not allowed to setup the Virtual Hosts by ourselves?? I thought this is our development server, it is not even reachable from outside of the companies VLAN, why should we ask THEM all the time?!”. Dang. Situation got critical.

More days passed. Then they got another email…

“So it seems our new colleague hasn’t made it through the probation phase”. Mike sighed as he pulled the power. The only noise in the silence was the sound of the fans getting slower and slower.

The Dev Team has been informed that currently there are no spare resources to take care of the development server, and they shouldn’t expect it to be available within the next 6 months.

The fans finally stopped spinning.

Good bye, Babette.

Updating Typo3 CMS 6.2 to 7.2

Updating our companies major portals from Typo3 CMS 4.7 LTS to 6.2 LTS was a good piece of work, so I was not really keen on updating any websites again. A good excuse for us was the fact that RealURL was not yet available for Typo3 CMS 7, but now it is – no more excuses. We need to do it anyway.

Before you even start, you should check the requirements of Typo3 CMS 7. It requires at least PHP 5.5 and MySQL 5.5, which might not be available on your server. So check the Typo3 CMS 7 System requirements before you continue.

I started with a relatively small project, which was ideal to get some experience. Only a few dozens of pages, a couple of TypoScript files, and most important, no legacy extensions but only good maintained Extbase 6.2 extension. I was pleasently surprised that the whole migration with fixing all issues (see below) only took roughly one hour, so my thanks to the Typo3 CMS team for that great job!

If you have one or more pre Extbase 6.2 extensions which don’t yet use namespaces (i.e. classes are named like Tx_MyExtension_Domain_Model_Whatever instead of just Whatever), there is an extension called “compatibility6” which comes bundled with Version 7.2. You can also use it if you already screwed your Typo3 installation and you can’t login into the backend anymore: you can install it manually by editing the typo3conf/PackageStates.php, search for compatibility6 and change the state from inactive to active. Please note that the usage of this extension has a negative impact on performance, although I can’t quantify the impact. It might just be ok for smaller websites.

The update process is explained here: In our case, the basic steps were:

  • download the Typo3 sources
  • uninstall RealURL
  • delete typo3temp/Cache folder
  • switch typo3_src to new version
  • run all checks in the Install tool
  • login into the backend, update RealURL to the recent version and
  • install it again

Update problems

Of course the update did not go without any problems, so let’s discuss them in this section.

CSS missing!

After finishing the update work in the Install tool I went to the backend login page and was somewhat suprised – I didn’t expect that fresh oldschool style:


Ok wait, there’s something wrong. Obviously the t3skin.css was missing.

It turned out that somehow the PackageStates.php was mixed up, and although the t3skin system extension was marked as active, it obviously wasn’t. Doing some internet research revealed that this problem occurred for others as well. I ended up in simply deleting the PackageStates.php (after doing a backup) and reload the login page. This way, the PackageStates.php was regenerated from scratch. Once it was created again, I needed to re-activate almost all system extensions manually. All in all this was just a matter of minutes, so no big deal.

No leading backslashes please!

After fixing the CSS issues, I loaded the start page which includes one of our custom extensions, and ended up with another exception:

$className “\My\Nifty\Class” must not start with a backslash.

Some of our makeInstance() calls had a backslash before the FQCN, which whith Typo3 CMS 7 is not allowed anymore. Since the backslash is not needed, we simply removed it, e.g.

// works ok with Typo3 CMS 6.2, but not 7.2:
$myInstance = GeneralUtility::makeInstance('\\My\\Nifty\\Class');

// works ok with Typo3 CM 6.2 AND 7.2:
$myInstance = GeneralUtility::makeInstance('My\\Nifty\\Class');

This also applies for calls to the ObjectManagers get-function!

Stating the class name as a string leads to the ugly double backslashes, which nobody likes. With PHP 5.5+, we now can use the ::class constant:

$myInstance = GeneralUtility::makeInstance(My\Nifty\Class:class);

which I really prefer because it is more readable and you can now use your IDEs code completion.

When the views are gone

Next reload, the page loads … yay! No exceptions. Good. So on to the next issue (a real classic):

Sorry, the requested view was not found. 

The technical reason is: No template was found. View could not be resolved for action "index" in class "My\Nifty\Controller\IndexController".

This time the problem was in the Configuration\Typoscript\setup.txt of one extension, where we still used the old name for the view folders:

# this works until Typo3 6.2
templateRootPath = {$plugin.tx_mynifty.view.templateRootPath}
partialRootPath = {$plugin.tx_mynifty.view.partialRootPath}
layoutRootPath = {$plugin.tx_mynifty.view.layoutRootPath}

# with Typo3 7.x, use
templateRootPaths.10 = {$plugin.tx_mynifty.view.templateRootPath}
partialRootPaths.10 = {$plugin.tx_mynifty.view.partialRootPath}
layoutRootPaths.10 = {$plugin.tx_mynifty.view.layoutRootPath}

Extbase supports fallback paths for templates, layouts and partials, which is quite cool – when you know it. Fluid uses the highest index (in our case only one, the 10) as directory for looking up the templates, and tries out the next lower one if it fails. Oh, and also it is called RootPaths instead of RootPath for a while already.

Say “hi” to number seven

Updating to Typo3 CMS 7.2 caused far less trouble than the previous “LTS-Jump”. I really like the fresh look of the backend, and the fact that the Typo3 core has been cleaned up even further.

Surely the website I migrated was only a small one with no legacy extensions, but I think I covered some issues which might be helpful to others!

Typo3 CMS hooks and namespaced classes

During work on my extension cwenvbanner to make it compatible with Typo3 CMS 7.2, I came across a good old friend, the hooks array $GLOBALS[‘TYPO3_CONF_VARS’][‘SC_OPTIONS’]. Since I sometimes struggle with the way how to declare the class and function to be called, I decided to write it down here – once and for all :-)

In short, this is how a hook can be defined


The ampersand before the class name is optional. If you add it, the class will be instantiated using the Singleton pattern.

Let’s have some examples. Firstly, let’s have a look at the old version I had in my extension:

	= 'EXT:cwenvbanner/class.tx_cwenvbanner.php:&tx_cwenvbanner->contentPostProc_output';

For the new Typo3 CMS 7 compatible version of cwenvbanner I wanted to use a namespaced classes for the heck of it. I came up with the inspiring name “Main” which now resides in the Classes directory of the extension, so need I changed the path and filename accordingly. I also need to provide the fully qualified class name, i.e. Cw\Cwenvbanner\Main.

This is how it looks like now:

	= 'EXT:cwenvbanner/Classes/Main.php:&Cw\\Cwenvbanner\\Main->contentPostProc_output';

Fixing Locale issues on Debian

Business as usual. New feature developed, tested, packed and ready to go. Let me just deploy it to production, smoke test aaaand …. damn! What happened to the currency symbols!?

In my particular case, PHP’s setlocale() returned false – so obviously those darned locales stroke again. SearchEngineOfYourChoice(TM) to the rescue!

Luckily even I was ably to fix this problem quickly without needing to contact the Admins (who wouldn’t have been in the office anyway):

$ locale -a

Aha! It was missing almost all locales. However adding them is quite easy:

$ sudo dpkg-reconfigure locales

Generating locales (this might take a while)...
de_DE.ISO-8859-1... done
de_DE.UTF-8... done
de_DE.ISO-8859-15@euro... done
en_US.UTF-8... done
fr_FR.ISO-8859-1... done
fr_FR.UTF-8... done
Generation complete.

The dialog that appears after running this command shouldn’t be a problem at all, so I don’t mention it further here. Just select the locales you need there and go for it.

Afterwards check the results by again doing

$ locale -a


That’s already it. Don’t forget to restart your webserver!