Love Fuel?    Donate

Thoughts, ideas, random notes, ramblings...

Anything about PHP in general, and FuelPHP in particular. Sometimes serious, sometimes with a big wink. But always with a message. Do you have an opinion about an article? Don't forget to comment!

Recently we've had lots of feedback about  some of the ways in which we handle things likes namespaces, underscores in classes and the occasional bit of PascalCase in our class names. This article intends to clear up any confusion and explain to you why each decision made is a direct improvement to Fuel.

Let's first go over how autoloading works in Fuel:

  • Namespaces are linked to an "area" of Fuel: Fuel\\Core is linked to fuel/core, the global namespace is left for your app, then packages and modules each have their own.
  • Underscores are translated to directory separators, which means Controller_Example is in app/classes/controller/example.php
  • Filenames and directories are all lowercase

We know this isn't fully compatible with proposed standards like PSR-0, but a framework is entitled to its own standards and conventions and we chose ours carefully. I'll explain below why the choices were made, but it's also important to know that our autoloader is very efficient. You can add any other autoloader on top of ours and once ours is done after 1 or even no filesystem checks the other one will kick in. Thus compatibility with PSR-0 is easily added without meaningful overhead.

Now on to the choices we made for Fuel.

Namespaces

We've tried multiple implementations, among which were having the App in its own namespace and converting sub-namespaces to directory separators. But in the end we kept running into one thing we felt was a problem: PHP doesn't allow you to import all classes from another namespace into your current one. Which means for example that if you're in "Fuel\\App\\Controller" you'll have to import ("use" in PHP terminology) all classes from "Fuel\\App" or call them with full namespace attached. And this goes for all sub-namespaces.

In the end a choice was made we felt wasn't perfect but it was the best of multiple evils caused by the way PHP has implemented namespaces. Your App resides in the global namespace, something we feel is acceptable because everything else should be namespaced while your App is the main part and has a right to be in global.

The Core was given its own namespace in "Fuel\\Core", and its classes are automatically aliased to global when called from global and unknown there. For example calling the class "View" will fail if you have no such class in your App, but then it looks into the "core_namespaces" in the autoloader and finds a class "Fuel\\Core\\View" which is loaded and aliased to "View". (you can also add more core namespaces which will function the same way, the Auth package works like that as well for example)

Because of all this the utility classes are always available in the global namespace, and when you call them from a Module or Package you can be sure that they're available like "View" and not "Fuel\\App\\View".

Then came packages and modules. These were some more complex decisions but it came down to this: we feel that classes from either a package or a module should be usable everywhere else as well (when you've loaded the package/module that is). If we put those in "Fuel\\Packages\\Pkg_name\\Classname" they would be awful to reach, which is why we decided to put them in just a single namespace. "Pkg_name\\Classname" is easy to use and to remember.

Cascading FileSystem (CFS) and how namespaces changed that

One of the things that was inspired by Kohana is the way we've setup our filesystem. The basic idea was that you can copy your core/packages/app all into each other and things would still work. The way this was done was by prefixing all core classes with "Kohana_" and have empty extensions of those classes without the prefixes. Now if you were to extend such a class you would just create the class without the prefix in your App and extend the original class with the prefix (which would be taken from core). Namespaces changed all that.

When we decided to start using namespaces we also decided to bind them to specific areas within Fuel. This means that there's no longer a problem when you have an extension in your app and one or more packages. The App will always take precedence over anything else, but the others can still be used when you include the full namespace. This made the use of another prefix like "Fuel_" unnecessary as the namespace bound to a specific location already did that job.

The Fuel CFS is no longer the same as Kohana's CFS, but its remnants are still visible. The same ease of understanding to where a class can be found still applies, because you know exactly where to find any class. It will be a direct representation of its class name, in a subdir of classes of its namespace "area".

Note: views are still loaded in the same way as Kohana's CFS. Config and lang files are loaded from core, packages and app and merged with the later ones taking precedence over the earlier locations (current module comes last when the current controller is part of a module).

Underscores

We've explained why namespaces don't simply map to directories and why we don't make extensive use of sub-namespaces. But we still need to have a clear directory structure. By keeping this pre-namespaces convention of translating underscores to directory separators we allow the advantages of having things in the same namespace (no importing classes, no fully namespaced class calls).

Fully lowercase paths

This decision was taken because basically we feel it leaves less room for mistakes. A common error in other frameworks is that files that should be uppercased on the first letter end up lowercase and vice versa. In many development envronments such as OS X the file system is often cases insensitive so this does not error, but as soon as the project is deployed to a case-sensitive live server things all fall apart.

Instead of having to check if you got every uppercase/lowercase character right in a full path, you only have to check if they're all lowercase.