The Power of Uniform Resource Location in PHP

Last week, I read a post by Beau Simensen about resource location in PHP. It was interesting, because I had already pondered over this topic in the context of Symfony2. Thinking about how to make Beau's proposal work for Symfony2 brought me to a result that hit me like a stone. Both he and Amy Stephen made me realize how much this could change not just Symfony2, but the PHP ecosystem as a whole. After PSR-0 and Composer, this is the next important step to ensure interoperability between PHP libraries. And it could almost completely replace the new and hotly debated autoloading PSR. Let me take a few minutes of your time to explain why.

Resource Location

In almost every PHP application, you need to locate some kind of resource. A resource, in this context, could be a template file, a configuration file, an image or any other kind of tangible asset in your project. Zend Framework 2, for example, ships translation files for its validation messages. The configuration for using these files looks like this:

$translator = new Zend\I18n\Translator\Translator();

You provide an absolute path to the file and the translator will know where to find it. This works well and is generic. Unfortunately, it is also verbose and couples the configuration to the specific directory structure of the "zendframework/zendframework" package.

Resource Identifiers

Many libraries and frameworks reduce verbosity by inventing some kind of identifier for resources. Let's take an example from the CakePHP documentation:

class UsersController extends AppController {
    public function view_active() {
        $this->layout = '';

The string "" is the identifier of a layout template that is going to be used as layout for the view_active action. This template must be located in one of the following paths:

  • /app/Plugin/Contacts/View/Layout/contact.ctp
  • /app/View/Plugin/Contacts/Layout/contact.ctp

So it can be shipped with the Contacts plugin, but also overridden in the application. Essentially, CakePHP creates a mapping from resource identifiers (such as "") to paths in the file system. What happens if you want to use a *.ctp file that is located somewhere else, for example in a package you just installed via Composer? That's not possible.

Let's look at another example from Symfony2. In Symfony2's configuration system, you can import configuration files into other configuration files. Let's look at a typical routing configuration:

    resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
    prefix:   /_wdt

Again, a custom kind of resource identifier is used to refer to the wdt.xml file in WebProfilerBundle. What if you want to import a routing configuration file from some place that is not in a bundle? Not possible (except if you use the absolute path, but that's not portable so I'll dismiss this solution).

This list goes on, but the pattern repeats.


Let me take a short excursion to talk about autoloading. As you probably know, autoloading refers to the dynamic loading of class files when classes are used for the first time. PSR-0 did a great deal to unify autoloading among PHP projects, and a new PSR ("PSR-X") is being discussed to improve PSR-0.

The core of PSR-0 is the mapping of class names to paths on the file system. For example:

require __DIR__.'/vendor/composer/ClassLoader.php';

$classLoader = new Composer\Autoload\ClassLoader();
$classLoader->add('Acme\\Demo', '/path/to/acme/demo');

// loads /path/to/acme/demo/Controller/ContactController.php
$controller = new Acme\Demo\Controller\ContactController();

Do you see the pattern? Mapping identifiers of classes to PHP files and namespaces to directories is essentially the same as all the other mappings described before.

Let's extend this example: What if I want to load some file from a location that is known to the autoloader? For example, I know that config.ini is located in the same directory as the class Acme\Demo\Application. The only way to do this right now is by using reflection:

$reflClass = new \ReflectionClass('Acme\\Demo\\Application');

$file = dirname($reflClass->getFileName()).'/config.ini';

But reflection is slow.

Uniform Resource Location

What if we could solve all of the above problems and use cases with one simple pattern? Our basic requirements are:

  1. Map resource identifiers to one or more paths on the file system.
  2. Use the same identifier pattern across different PHP libraries to make them interoperable.

The good thing: A specification for Uniform Resource Identifiers (URI) already exists (RFC 3986). Why not reuse it?

Let's see how we could leverage URIs to locate PHP classes and files or directories relative to PHP classes:

$locator = new ResourceLocator();
$locator->addPath('classpath', '/Acme/Demo/', '/path/to/acme/demo');

echo $locator->findResource('classpath:///Acme/Demo/Parser.php');
// => /path/to/acme/demo/Parser.php

echo $locator->findResource('classpath:///Acme/Demo/resources');
// => /path/to/acme/demo/resources

echo $locator->findResource('classpath:///Acme/Demo/resources/config.ini');
// => /path/to/acme/demo/resources/config.ini

This is basically the same thing that autoloaders are doing today, but a little more generic. The presented URIs have two parts:

  • a scheme: "classpath"
  • a path: "/Acme/Demo/Parser.php"

The "authority" ("host:port") part is empty, which is why the double slash ("//") is immediately followed by the initial slash of the path. Other URI parts like the query ("?query") are not required as well, but could be added for custom (non-interoperable) implementations.

An autoloader can be based on such a resource locator by turning backslashes into forward slashes:

spl_autoload_register(function ($class) use ($locator) {
    try {
        include $locator->findResource('classpath:///'.strtr($class, '\\', '/').'.php');
    } catch (\Exception $e) {

Many of PHP's standard functions can be used to work with URIs:

echo dirname('classpath:///Acme/Demo/Parser.php');
// => classpath:///Acme/Demo

echo basename('classpath:///Acme/Demo/Parser.php');
// => Parser.php

parse_url() unfortunately cannot be used, because it explicitly does not support URIs, just URLs.

As you see, URIs are a very strong concept that decouples resource location completely from the mechanisms of individual frameworks.

More URI Schemes

To see the use of URIs in practice, let's replace Symfony's resource identifiers:

    resource: "classpath:///Symfony/Bundle/WebProfilerBundle/Resources/config/routing/wdt.xml"
    prefix:   /_wdt

That's already a lot more generic. But it is also a lot more verbose than the previous code. To fix this problem, we can add a context-specific scheme, for example "config":


Like before, the first argument of addPath() is the URI scheme, the second the URI path prefix and the last one the actual file path. As URI path prefix I used the Composer package name.

The new scheme allows to simplify the configuration quite a lot:

    resource: "config:///symfony/web-profiler-bundle/routing/wdt.xml"
    prefix:   /_wdt

If the configuration parser assumes that resource URIs are in the "config" scheme by default (unless an explicit scheme is given), we can even reduce it to:

    # The scheme config:// is assumed if no other is given
    resource: /symfony/web-profiler-bundle/routing/wdt.xml
    prefix:   /_wdt

Now we completely replaced the custom identifier by a generic, framework-independent one. And this genericity opens up many possibilities.

Composer Integration

Currently, Composer already generates an autoloader for you. Imagine that it would also generate a resource locator for you. Each package could specify in its composer.json where the directories for (a) namespace prefixes and (b) custom schemes are located:

    "name": "acme/demo",
    "autoload": "psr-x",
    "resources": {
        "classpath": {
            "Acme\\Demo\\": "src/"
        "config": "resources/config/",
        "view": "resources/templates/",
        "lang": "resources/translations/"

With the generated locator you can easily locate resources in any package and use them anywhere that resource URIs are supported, independent from the internal directory structure of that package! Sit back and think about the consequences for a second. For example:

    # The scheme config:// is assumed if no other is given
    resource: /acme/demo/routing.xml
    prefix:   /demo

Or for loading translations from the "zendframework/zendframework" package:


Or for using custom templates from a webmozart/theme package in CakePHP:

class UsersController extends AppController {
    public function view_active() {
        // The scheme view:// is assumed if no other is given
        // .ctp is automatically appended
        $this->layout = '/webmozart/theme/layout';

And so on. This will have a huge impact on developing with PHP.

Stream Wrappers

Although I see this more of a toy, a consequence of using URIs for identifying resources is that you could wrap a stream wrapper around the resource locator. For example:


stream_wrapper_register('classpath', 'ResourceLocatorBasedStreamWrapper');
stream_wrapper_register('view', 'ResourceLocatorBasedStreamWrapper');

echo file_get_contents('classpath:///Acme/Demo/Parser.php');
echo file_get_contents('view:///webmozart/theme/layout.ctp');


Yesterday, I created a PSR proposal for uniform resource location. I would be happy about feedback in the corresponding Google Groups topic, especially from the people currently discussing PSR-X. By specifying the "classpath" scheme in detail, this PSR would almost completely replace PSR-X.

The next steps I see are:

  1. Finish the framework survey (help appreciated!)
  2. Specify the "classpath" scheme
  3. Implement a sample locator
  4. Vote on and release the PSR
  5. Rebase PSR-X on this PSR
  6. Integrate both into Composer
  7. Integrate resource URIs into member projects

I'm excited. Are you yet? What possibilities do you foresee?