I am developing a web framework in PHP, and I want to make it fully extendable. My goal is to make it possible for a developer to change the behavior of any component without having to modify the "core" codebase at all. Developers should be able to encapsulate all of their work in a separate directory. And, I want to do this without using "hooks" (i.e., event listeners), which can quickly turn into a huge mess.
For most components, I have this figured out:
- The Pimple DI container allows you to
extend
existing services. - I'm using Twig as my template engine, which allows templates to be overridden.
- Front controller routes can be overridden by redefining them.
- I'm using an extendable uniform resource locator to allow developers to override or add search paths for resources (assets, configuration, schema, etc).
The problem I face is extending the functionality of a class when there are places in the core codebase that already instantiate that class (because my framework is a full-fledged application rather than a library, even with IoC, I still need to instantiate the class at some point).
Inheritance alone won't solve this problem, because I'm still instantiating the base class in my core controllers/routes/whatever. It would seem that I need to introduce an additional layer of abstraction.
One idea I have is to just use PHP's dynamic class instantiation feature. For example, if I have a base User
class, and I want to allow developers to replace all instances with a CustomUser
class:
// In a config file (which *can* be overridden)
$userClassName = "User";
...
// Anywhere I need a User, or User subclass:
$user = new $userClassName(...);
Now, a dev simply needs to override the value of $userClassName
and set it to the name of their child class.
Alternatively, I could wrap this all in a Factory pattern:
class UserFactory
{
public function createUser($type = "User", $args)
{
return new $type($args);
}
}
The advantage of the Factory class would be that I could do some type-checking, but then I'd need a separate Factory for each base class.
Are either of these approaches the right way to implement this kind of extensibility?