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!