TL;DR: To dynamically load classes by namespaces in PHP, you need a Registry, and a way to parse the concrete files from a directory based on its namespace.
Whenever I’m working on a project that has an object-oriented code base and that uses Subscribers and Services, I often use a Registry
. This makes it easy to
- register the subscribers with the core application whenever the code runs,
- de-couple any
Service
classes so they can be tested or even run isolation, - and maintain the code base whenever something has to be added or taken away.
One challenge with this approach though, at least in PHP, is that I’ve found myself having to go back into the Registry and set a reference to a given Subscriber
whenever I want to add it (or remove it whenever I want to, you know, remove it).
Ideally, I want my registry to know where the subscribers are and how to set them up. This way, I can focus on working on the rest of the code.
Load Classes by Namespace
First, I have to know where the subscribers are. For the purposes of one of the projects I’m currently working on, I have all of my subscribers located in a single directory.
That is, you can find them in src/Subscribers
. Within that directory, I’ll usually have an AbstractSubcriber
, not always, but I’m including it here so to illustrate the point in the code below.
Secondly, I like to make sure my namespaces follow the same directory structure. That is, the logical namespace matches the virtual namespace. So if I have a set a project called Acme
and a namespace called Subscribers
and an AbstractSubscriber.php
in the project, then the namespace will look something like this:
namespace Acme\Subscribers;
abstract class AbstractSubscriber {
// ...
}
This means that whenever the Registry
runs, it needs to know how to build a fully-qualified namespace to the file. How you implement this may vary, but this is important as the key to how your Registry
works is going to be based on the directory structure and the paths to the file.
Finally, whenever the Registry
runs (let’s say it has a simple run
method) then it will look something like this:
public function run()
{
array_map(
function ($subscriber) {
(new $subscriber())->subscribe();
},
$this->subscribers
);
}
Specifically, this function iterates through an array of subscribers and then calls the subscribe
function that each one has. How each of these subscribers has this function is defined by the nature of it extending an abstract subscriber.
Anyway, to get to this point, though, we have to build an array of subscribers. And to do that dynamically, we need to be able to:
- get the name of each subscriber without a suffix,
- know the directory path to the file,
- create a string that consists of the fully-qualified path name,
- an array to store all of the registered subscribers.
Given everything I’ve outlined above, the following function should do exactly that:
private function getSubscribers(): array
{
$subscribers = [];
$files = scandir($this->subscriberPath, SCANDIR_SORT_ASCENDING);
foreach ($files as $file) {
// Ignore directories and abstract classes.
if (is_dir($file) || 0 === stripos($file, 'Abstract')) {
continue;
}
// Get the name of the file without the suffix.
$file = explode('.', $file);
$file = $file[0];
array_push(
$subscribers,
$this->subscriberNamespace . $file
);
}
return $subscribers;
}
This creates an array in which all subscribers will reside, looks for all of the files within a given directory, ignores anything that’s not a concrete subscriber, and then pushes a string referencing the path to the file into the array before returning it to its caller.
Obviously, if your directories are structured differently then your code will look a little bit different. You may even have separate functions depending on how you have your subscribers organized (or how many you have).
But the point of how this code works remains and the gist of how to achieve should work in many situations.