Expose Namespaced PHP Classes at the Root

First off, I sincerely hope that nobody ever finds this blog post useful. Alas, I’m sure there is some soul out there in a situation similar to my own: hang in there friend.

If you’ve been writing PHP for any length of time, you know how easy it is to write bad PHP: don’t bother with classes, include all your functions in a single 6,000 line file, and certainly don’t set up a folder structure in your git repository.

So, what do you do when you want to start bringing order to the chaos?

I found myself in a situation where I had a few hundred PHP files in the root of a repository; each file defining a class. Unfortunately, none were in a namespace.

Additionally, these were accessible through a manually-defined spl_autoload_register function which was part of a common.php file that got require_once‘d from tons of different places. Some of the places requiring this file were even outside the repository and completely unknown to me.

I wanted to clean things up, but without breaking unknown code.

The first thing I wanted to do was establish a namespace for these classes and to introduce a strategic folder hierarchy. A few google searches quickly proved my suspicion: you can’t present classes belonging to a namespace as if they were available at the root namespace (well, you can with use, but that is scoped only to the current file; I don’t want to change hundreds of files). See here and here

This would be a problem if I want to move in-use classes inside a namespace.

But then I discovered this comment and started musing: “hmmm, I can create a class in the root namespace that extends a class in a child namespace” and “spl_autoload_register lets me try to find a class before PHP gives up” and “what if I could enumerate all classes in a defined namespace and dynamically instantiate a root-namespace proxy?”

And that’s exactly what I did:

// since we tons of legacy files requiring common.php, 
// create an autoload proxy for root->ModelV1 until 
// we can add "use" statements to all necessary locations
spl_autoload_register(function ($class) {
	if (strpos($class,"\\")!== false) {
		// only attempt to autoload root-namespace'd classes
		return false;
	}
	$namespacesAvailableAtRoot = array("ModelV1");
	foreach ($namespacesAvailableAtRoot as $namespace) {
		if (class_exists(	$namespace."\\" . $class)) {
			$str = sprintf('Class %s extends %s\%s {}', $class, $namespace, $class);
			// this is uugly.  Yes; we're eval-creating new PHP classes at runtime where 
			// the new class exists in the root namespace and extends the class implementation from 
			// ModelV1.
			// Borrowed from: https://stackoverflow.com/a/13504972
			// and from: https://stackoverflow.com/a/19890311
			eval($str);
			return true;
		}
	}
	return false;
});

Again, I hope absolutely nobody finds this useful. In fact, I hope to thoroughly expunge this code In the Near Future ™ (but we all know how that goes)

Cheers!

Leave a Reply

Your email address will not be published. Required fields are marked *